0

Are there any hard and fast rules on the use of this pattern or is it solely intended as a way to achieve additional functionality within method calls without using inheritance?

I have amended the example below that I took from a SO post to demonstrate what I am considering.

 public interface Coffee {
     public double getCost();
     public String getIngredients();
 }
public class SimpleCoffee implements Coffee {
   @Override
   public double getCost() {
    return 1;
   }
   @Override
   public String getIngredients() {
    return "Coffee";
   }
}

 public class CoffeeDecorator implements Coffee {
   protected final Coffee decoratedCoffee;

   public CoffeeDecorator(Coffee c) {
    this.decoratedCoffee = c;
   }
   @Override
   public double getCost() { 
    //you can add  extra functionality here.
    return decoratedCoffee.getCost();
  }
   @Override
   public String getIngredients() {
    //you can add  extra functionality here.
    return decoratedCoffee.getIngredients();
   }

   public boolean methodNotDefinedInInterface() {
       //do something else
       return true;
   }
}

So with the example above in mind, is it viable to:

a) use the simple Coffee whenever you see fit without decorating it

b) Add additional functionality that is not defined in the Coffee interface to decorator objects such as the methodNotDefinedInInterface()

Could someone also explain where the composition comes into this pattern as the SimpleCoffee is something that can exist in its own right, but it seems to be the decorator that actually 'owns' any object.

Although without the SimpleCoffee class (or some concrete implementation of Coffee) the decorator doesnt have any purpose, so aggregation doesnt seem to be what is occurring here.

berimbolo
  • 3,319
  • 8
  • 43
  • 78
  • This does not directly answer your question , but is closely related https://stackoverflow.com/questions/18618779/differences-between-proxy-and-decorator-pattern – Vasily Liaskovsky Feb 20 '18 at 09:12

2 Answers2

1

The description of the pattern includes intent which makes it pretty clear what the pattern is for:

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.

As for "hard and fast rules" - I generally don't think that there are "hard and fast rules" in patterns at all. Like, if you don't implement it exactly as GoF described, there will be no "pattern police" punishing you. The only point is that if you follow the classic guidelines, other developers will have less problems recognizing patterns in your code.

Your example is quite OK from my point of view.

SimpleCoffee is not a decorator, so no composition there. CoffeeDecorator has decoratedCoffee as a component (here you have your composition)

lexicore
  • 42,748
  • 17
  • 132
  • 221
  • hahaha thanks! Your comment about the "pattern police" made me laugh, they may not exist as a real unit, but every organisation has several self appointed members I'm sure! Good answer and I take it from the rather basic example I provided the classic guidelines are still adhered to? The description you posted says 'can be used...' I guess thats good enough to say it doesnt have to be used exactly in this manner. Cheers! – berimbolo Feb 20 '18 at 09:23
  • 1
    @berimbolo I have now "Pattern police" playing in my mind on the Radiohead "Karma police" melody. – lexicore Feb 20 '18 at 09:26
1

a) use the simple Coffee whenever you see fit without decorating it

Yes, of course.

b) Add additional functionality that is not defined in the Coffee interface to decorator objects such as the methodNotDefinedInInterface()

You can add more methods just like adding new methods to SimpleCoffee class, but note that you would need to use those additional methods somewhere in the decorator class.


Personally, I find this pattern useful when someone gives you an instance of Coffee (i.e. you didn't instantiate it). If you need to change its behavior at runtime, the only way is to wrap it inside another object of Coffee type. This is when you can throw it into the decorator class. The decorator can expose some of the original behavior while providing some new behaviors.

Jai
  • 8,165
  • 2
  • 21
  • 52
  • Ok thanks, perhaps my use case isnt suitable after all. I am doing some refactoring of a hard to understand set of classes where multiple layers of Abstraction have been used making the code a bit of a nightmare to work with. In this instance there are multiple different types of request defined with many duplicated methods and properties, I refactored down to just a basic request and then used decorators to add functionality to more specific request types without going down the path of more layers of Abstraction... – berimbolo Feb 20 '18 at 09:27
  • 1
    @berimbolo If the different behaviors are fixed (determined) at compile time, then you can use normal inheritance (maybe with strategy pattern etc). Decorator is really useful when someone throws you an object at runtime, and you need to change or add a small functionality, then thereafter pretend its the original object. – Jai Feb 20 '18 at 09:32