2

Recently I read about the decorator design pattern but I was left with some unanswered questions that I couldn't find online. I'm not going to show code because I don't want to make this question more complicated than it really is. I'll simply give an example:

Subway Store:

Component --> SubSandwich

ConcreteComponent --> 15cmSub, 30cmSub

Decorator --> Ingredients

ConcreteDecorator --> White Cheese, Yellow Cheese, Jam, Chicken.

This is just the way a Subway store works. Pick your core sandwhich size and then add all the toppings you like. But I still have some questions:

  1. What if there is an invalid combination of ingredients? For example, Subway policy says that there can't be two kinds of cheeses in the same sub. Now let's imagine that there are 10000 possible combinations for the toppings and ONLY one is invalid. Does this completely break the decorator pattern?

  2. What if two ingredients are dependant. For example, if you order lettuce THEN you need some other kind of vegetable to make a "valid" Sub.

  3. When is it better to use the decorator pattern instead of a SubSandwich class with an ArrayList of Ingredient? I know here the Ingredients don't add behaviour, which makes the Subway example not accurate, but let's assume they do.

  4. Why extending? Why not using interfaces?

AFP_555
  • 2,392
  • 4
  • 25
  • 45

4 Answers4

4

The patterns in their original structure do not solve any practical problem. You need to mix them up with general SOLID principles. For the example problem, you can implement Builder pattern on top of the Decorators. And you will have some DSL type of code e.g.

Builder.aPizza().withCheeze(someCheeze).addTopping(someTopping).and(someOtherTopping).build();

Using this pattern/style you can validate the intermediate state of object or even at the final stage when you call build() method.

Satyendra Kumar
  • 357
  • 2
  • 13
2
  1. Patterns don't work for every use case. The decorator pattern is actually not that useful to build up a multi component structure because it combines everything into 1 object with just 1 interface. This works only well for cases where new behavior is merely "decoration".

    A good example is Collections.synchronizedList(List list) which "decorates" all methods by wrapping a synchronized block around them.

    You could probably still use a decorator for the usecase you describe but each decorating step would have to check if its ok to apply and throw if not.

    I honestly don't even know why these kinds of examples are so popular. I've not seen the decorator pattern used like that. It's not made for adding components.

  2. see 1. It might not even be possible since the lettuce decorator can't enforce that there is another decorator afterwards. This is clearly not a good use of the pattern.

  3. In cases where you like it better or it works better. Like for a sandwich factory.

  4. Because that's the trick of the decorator. It decorates behavior of an existing thing. You don't want to add a new interface. A place where that's useful is when you are man-in-the-middle:

    E.g. library A produces ListItem objects and you want to use library B to display them. You can't change either what A produces nor how B consumes them. But when you don't like for example how A implemented the toString() method that B uses to display, you can simply wrap a new toString() method around. It's still a ListItem so B won't notice, just like A doesn't notice anything either.

zapl
  • 63,179
  • 10
  • 123
  • 154
2

Decorator pattern's intent and your application's intent are not matching. Decorator is not right pattern for your use case.

enter image description here

Source: https://en.wiki2.org/wiki/Decorator_pattern

The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time

You have missed the point of "indenpendently" in the intent of Decorator. If you want to establish co-relation or mutual exclusion, Decorator is not useful.

On the other hand, Builder_pattern suits your requirement since you can set mandatory & optional parameters to build the object.

You can go through these SE questions:

When would you use the Builder Pattern?

Builder Pattern in Effective Java

Community
  • 1
  • 1
Aditya W
  • 652
  • 8
  • 20
1
  1. What if there is an invalid combination of ingredients? For example, Subway policy says that there can't be two kinds of cheeses in the same sub.

Then the Decorator pattern is probably not what you're looking for. Decorator is about composing behavior without changing interface. It is not about concatenating attributes, and it doesn't work well for that because only the outermost decorator instance is (usually) directly available to the user. If you need somehow to know whether a SubWithProvolone wraps a SubWithTurkey -- or any other particular kind of sub -- then you're doing it wrong.

The best example I know is exhibited by the Java I/O classes InputStream, OutputStream, Reader, and Writer and all their subclasses. Any InputStream can be made buffered by decorating it with a BufferedInputStream; any InputStream can be used as a Reader by decorating it with an InputStreamReader; etc..

But if you started throwing in rules such as "a BufferedWriter must not be decorated by another BufferedWriter" (hypothetical) then it breaks -- there's just no good way to enforce it, and in truth, there's no bona fide need for the rule. If you are trying to model a situation where there is a true need for rules like that about how components can be composed, then Decorator is not a very good fit.

  1. What if two ingredients are dependant. For example, if you order lettuce THEN you need some other kind of vegetable to make a "valid" Sub.

This is essentially the same question as #1. You could probably kludge something together to solve this problem, and the previous one, but if such problems present themselves at all then Decorator is not a good fit for the situation.

  1. When is it better to use the decorator pattern instead of a SubSandwich class with an ArrayList of Ingredient? I know here the Ingredients don't add behaviour, which makes the Subway example not accurate, but let's assume they do.

The other aspects of your question are a bit broad for SO, but this one is far too broad. We answer specific questions about programming details.

  1. Why extending? Why not using interfaces?

You inquire about a false dichotomy. That Java distinguishes between interfaces and pure abstract classes is a superficial language-design detail that serves mainly to support Java's restriction to single inheritance of implementation. There is no deep difference between interfaces and pure abstract classes that would survive if your question were redirected to C++ instead, to which design patterns are equally relevant. You can implement the Decorator pattern around a base type that is a Java interface.

With that said, the Java I/O classes I already mentioned do use classes rather than interfaces for the base type. There are both advantages and disadvantages, but one of the advantages is that it allows them to use the Template Method pattern to provide for easier implementation of concrete implementation classes. Even there, however, abstract base classes could have been provided for that purpose even if the decorator type were declared as an interface.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157