2

Given the classic coffee decorator example (copied from Wikipedia).

public interface Coffee {
    public double getCost();
}

public class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }
}

public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;
    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee c) {
        super(c);
    }
    public double getCost() {
        return super.getCost() + 0.5;
    }
}

Let's say now the price of all decorators (e.g. Milk) depends on some attribute that all coffees will have (say the size of the coffee) and that the size of coffee is NEVER used elsewhere. Where should add coffee size to the class hierarchy?

I can put it in the Coffee Interface

public interface Coffee {
    public double getCost(); // Returns the cost of the coffee
    public/protected double size;
}

If it's set to public, the size is unnecessarily exposed

If it's set to protected, decorators can't really access it through decoratedCoffee (see this post Java: cannot access a protected member of the superclass in the extending subclass and Why can't a derived class call protected member function in this code?)

I can put it in CoffeeDecorator, but then I would have to modify the constructor to

public CoffeeDecorator(Coffee c) {
    if c is of type CoffeeDecorator
        size = c.size;
    this.decoratedCoffee = c;
}

Which somehow doesn't seem like the most elegant solution... (Obviously digging through the chain of decoratedCoffees until I find one with non-null size is not an option either)

I can put it in each decorator which just goes against the design principles.

I'm pretty sure this scenario comes up quite often, I'd like to know what is the best way of handling such case?

Thanks in advance.

--- Edit 31/3/2016 ---

Clarify that the certain attribute (previously cup size, now renamed to coffee size) is something that all coffees should have.

Community
  • 1
  • 1
Rufus
  • 5,111
  • 4
  • 28
  • 45

1 Answers1

0

I don't think that adding the Cup size into any of these classes is a good idea. It just doesn't fit in there, because the coffee knows nothing about cups.

The Cup can be a separate class (theat code as pseudo-code, I am not very familiar with java syntax):

public class Cup {
    private Coffee coffee;

    public Cup(Coffee c) {
        this.coffee = c;
    }

    public getCost() {
        return this.getSize() * c.getCost();
    }

    public getSize() {
        return 1; // standard cup
    }
}

public class BigCup extends Cup {

    public getSize() {
        return 2; // double size
    }

}

So now you can do new BigCup(new WithMilk(new Coffee())). Alternatively the Cup can also be a decorator, it makes sense in terms of programming, but maybe a bit less sense in terms of real life (because now the Cup also implements Coffee, sounds fun):

public class Cup extends CoffeeDecorator {

    public Cup(Coffee c) {
        super(c);
    }

    public getCost() {
        return this.getSize() * super.getCost();
    }

    public getSize() {
        return 1; // standard cup
    }
}

public class BigCup extends Cup {

    public getSize() {
        return 2; // double size
    }

}
Borys Serebrov
  • 15,636
  • 2
  • 38
  • 54
  • Perhaps coffee size would be a better term, the key is that the decorators depend on "some attribute" that is present in all coffees, I'll clarify – Rufus Mar 31 '16 at 04:27
  • @Woofas that's still not clear, what does "coffee size" mean? And how exactly the decorators depend on it? And why can't you add it the same way as `getCost()` (some `getSize()` method) or, even better, do not expose this attribute at all and tell ([tell, don't ask](http://martinfowler.com/bliki/TellDontAsk.html)) `Coffee` object to do something depending on this `size`? In general the example with `coffee size` still doesn't look realistic, so it is not clear what exactly is the problem. – Borys Serebrov Mar 31 '16 at 11:21