When to Use the Decorator Pattern?
Asked Answered
S

10

87

I am going over my design patterns, and one pattern I have yet to seriously use in my coding is the Decorator Pattern.

I understand the pattern, but what I would love to know are some good concrete examples of times in the real world that the decorator pattern is the best/optimal/elegant solution. Specific situations where the need for the decorator pattern is really handy.

Thanks.

Schroth answered 11/10, 2009 at 3:11 Comment(1)
Possible duplicate of When do we need decorator pattern?Tomasatomasina
B
45

The decorator pattern is used a lot with streams: you can wrap a stream with a stream to get added functionality. I've seen this with the .Net framework - as far as I know this occurs elsewhere. My favourite is using GZipStream around a FileStream, for added compression.

Bricklaying answered 11/10, 2009 at 3:14 Comment(3)
Similarly, Java streams are just a giant collection of decorators decorating decorators that have decorated something that decorates. My face just melted.Shandrashandrydan
I thought streams would be a better example than the coffee example in my Head First: Design Patterns book on my desk.Bricklaying
Can anyone explain why the decorator pattern is the optimal solution for this situation?Aeolis
H
100

The Decorator Pattern is used for adding additional functionality to an existing object (i.e. already instantiated class at runtime), as opposed to object's class and/or subclass. It is easy to add functionality to an entire class of objects by subclassing an object's class, but it is impossible to extend a single object this way. With the Decorator Pattern, you can add functionality to a single object and leave others like it unmodified.

In Java, a classical example of the decorator pattern is the Java I/O Streams implementation.

FileReader       frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);

The preceding code creates a reader -- lrdr -- that reads from a file and tracks line numbers. Line 1 creates a file reader (frdr), and line 2 adds line-number tracking.

Actually, I'd highly encourage you to look at the Java source code for the Java I/O classes.

Harsho answered 11/10, 2009 at 3:32 Comment(0)
B
45

The decorator pattern is used a lot with streams: you can wrap a stream with a stream to get added functionality. I've seen this with the .Net framework - as far as I know this occurs elsewhere. My favourite is using GZipStream around a FileStream, for added compression.

Bricklaying answered 11/10, 2009 at 3:14 Comment(3)
Similarly, Java streams are just a giant collection of decorators decorating decorators that have decorated something that decorates. My face just melted.Shandrashandrydan
I thought streams would be a better example than the coffee example in my Head First: Design Patterns book on my desk.Bricklaying
Can anyone explain why the decorator pattern is the optimal solution for this situation?Aeolis
H
39

I've recently used the decorator pattern in a web service which uses the following CommandProcessor interface:

public Command receive(Request request);
public Response execute(Command command);
public void respond(Response response);

Basically, the CommandProcessor receives a Request and creates the proper Command, executes the Command and creates the appropriate Response, and sends the Response. When I wanted to add timing and log it, I created a TimerDecorator that used an existing CommandProcessor as its component. The TimerDecorator implements CommandProcessor interface, but just adds timing and then calls its target, which is the real CommandProcessor. Something like this:

public class TimerDecorator implements CommandProcessor {
   private CommandProcessor target;
   private Timer timer;

   public TimerDecorator(CommandProcessor processor) {
      this.target = processor;
      this.timer = new Timer();
   }

   public Command receive(Request request) {
      this.timer.start();
      return this.target.receive(request);
   }

   public Response execute(Command command) {
      return this.target.execute(command);
   }

   public void respond(Response response) {
      this.target.response(response);
      this.timer.stop();
      // log timer
   }

}

So the real CommandProcessor is wrapped inside TimerDecorator, and I can treat TimerDecorator just like the target CommandProcessor, but now timing logic has been added.

Havens answered 11/10, 2009 at 3:28 Comment(0)
E
20

Decorator pattern dynamically changes the functionality of an object at run-time.

Composition and inheritance have been effectively used to achieve the goal.

A real world example: Compute the price of beverage, which may contain multiple flavours.

abstract class Beverage {
    protected String name;
    protected int price;
    public Beverage(){
        
    }
    public  Beverage(String name){
        this.name = name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    protected void setPrice(int price){
        this.price = price;
    }
    protected int getPrice(){
        return price;
    }
    protected abstract void decorateBeverage();
    
}
class Tea extends Beverage{
    public Tea(String name){
        super(name);
        setPrice(10);
    }
    public void decorateBeverage(){
        System.out.println("Cost of:"+ name +":"+ price);
        // You can add some more functionality
    }
}
class Coffee extends Beverage{
    public Coffee(String name){
        super(name);
        setPrice(15);
    }
    public void decorateBeverage(){
        System.out.println("Cost of:"+ name +":"+ price);
        // You can add some more functionality
    }   
}
abstract class BeverageDecorator extends Beverage {
    protected Beverage beverage;
    public BeverageDecorator(Beverage beverage){    
        this.beverage = beverage;   
        setName(beverage.getName()+"+"+getDecoratedName());
        setPrice(beverage.getPrice()+getIncrementPrice());
    }
    public void decorateBeverage(){
        beverage.decorateBeverage();
        System.out.println("Cost of:"+getName()+":"+getPrice());
    }   
    public abstract int getIncrementPrice();
    public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
    public SugarDecorator(Beverage beverage){
        super(beverage);
    }
    public void decorateBeverage(){
        super.decorateBeverage();
        decorateSugar();        
    }
    public void decorateSugar(){
        System.out.println("Added Sugar to:"+beverage.getName());
    }
    public int getIncrementPrice(){
        return 5;
    }
    public String getDecoratedName(){
        return "Sugar";
    }
}
class LemonDecorator extends BeverageDecorator{
    public LemonDecorator(Beverage beverage){
        super(beverage);
    }
    public void decorateBeverage(){
        super.decorateBeverage();
        decorateLemon();    
    }
    public void decorateLemon(){
        System.out.println("Added Lemon to:"+beverage.getName());       
    }
    public int getIncrementPrice(){
        return 3;
    }
    public String getDecoratedName(){
        return "Lemon";
    }
}

public class VendingMachineDecorator {  
    public static void main(String args[]){
        Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
        beverage.decorateBeverage();
        beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
        beverage.decorateBeverage();
    }
}

output:

Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon

This example computes cost of beverage in Vending Machine after adding many flavours to the beverage.

In above example:

Cost of Tea = 10, Lemon = 3 and Sugar = 5. If you make Sugar + Lemon + Tea, it costs 18.

Cost of Coffee =15, Lemon = 3 and Sugar = 5. If you make Sugar + Lemon + Coffee, it costs 23

By using same Decorator for both beverages ( Tea and Coffee ), the number of sub-classes have been reduced. In absence of Decorator pattern, you should have different sub classes for different combinations.

The combinations will be like this:

SugarLemonTea
SugarTea
LemonTea

SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino

etc.

By using same Decorator for both beverages, the number of sub-classes have been reduced.

Related SE question:

Use Cases and Examples of GoF Decorator Pattern for IO

Excusatory answered 28/5, 2016 at 21:51 Comment(0)
B
8

The decorator is simple yet extremely powerful. It is key in achieving separation of concerns and is an essential tool for the Open Closed Principle. Take a common example of placing an order for a product:

IOrderGateway
{
    void PlaceOrder(Order order);
{

Main implementation: AmazonAffiliateOrderGateway

Possible decorators could be:

  • IncrementPerformanceCounterOrderGateway
  • QueueOrderForLaterOnTimeoutOrderGateway
  • EmailOnExceptionOrderGateway
  • InterceptTestOrderAndLogOrderGateway

A more detailed example from here illustrates a decorator that saves contacts for orders that were created using a gift card while completing the order:

class OrderWithGiftCardGateway extends OrderGatewayDecorator
{
    ...

    public function createOrder(CreateOrderRequest $order)
    {
        if ($this->containsGiftCard($order))
        {
            $this->addContactToFolder($order);
        }

        return parent::createOrder($order);
    }
}
Bersagliere answered 11/10, 2009 at 4:44 Comment(0)
F
4
  1. to add responsibilities to individual objects dynamically and transparently.
  2. for responsibilities that can be withdrawn.
  3. when extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination.
Fade answered 29/1, 2016 at 0:11 Comment(0)
C
1

GOF definition :

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

This pattern says that the class must be closed for modification but open for extension, a new functionality can be added without disturbing existing functionalities. The concept is very useful when we want to add special functionalities to a specific object instead of the whole class. In this pattern, we use the concept of object composition instead of inheritance.

Generic example :

public abstract class Decorator<T> {
    private T t;

    public void setTheKernel(Supplier<? extends T> supplier) {
        this.t = supplier.get();
    }

    public T decorate() {
        return Objects.isNull(t) ? null : this.t;
    }

}

The implementation

public interface Repository {
    void save();
}

public class RepositoryImpl implements Repository{
    @Override
    public void save() {
        System.out.println("saved successfully");
    }
}

public class EnhancedRepository<T> extends Decorator<T> {
    public void enhancedSave() {
        System.out.println("enhanced save activated");
    }
}

public class Main {
    public static void main(String[] args) {
        EnhancedRepository<Repository> enhanced = new EnhancedRepository<>();
        enhanced.setTheKernel(RepositoryImpl::new);
        enhanced.enhancedSave();
        enhanced.decorate().save();
    }
}
Cummins answered 2/3, 2022 at 13:32 Comment(0)
R
0

Decorator pattern is used by C# language itself. It is used to to decorate the Stream I/O class of C#. The decorated versions are BufferedStream, FileStrem, MemoryStrem, NetworkStream and CryptoStream classed.

These subclasses inherit from the Stream class and also contain an instance of the Stream class.

Read more here

Rarefy answered 6/12, 2018 at 10:39 Comment(0)
M
0

When you want to add multiple functionality to a class/object and you want to have flexibility to add them whenever you need, Decorator comes handy. you can extend the base class and add your desired changes but this way you come up with lot of sub-classes that could blow your mind. but with decorator you can have your desired changes but still have a simple, understanding flow. your design is easily open for extension but close to modification with really simple logic. The best example could be the Stream objects that are implemented in Java and C#. for example you have a File Stream and in one use-case You want to Encrypt that, then Zip that, then Log that and finally do something fancy with it. then in another class you decide to do something else You want to Convert that, then Encrypt that, then get the timing, bluh, bluh, bluh. again you have another flow in other classes. if you want to use Inheritance you have to create at least 3 sub-classes and if any other requirement is needed you have to add more sub-classes that in this case (Stream) you will end up dozens of sub-classes for small changes.

class EncryptZipLogStream{}
class ConvertEncryptTimeStream{}
class MoreStreamProcess{}
class OtherMoreStreamProcess{}
...

And in each use-case you have to remember what class is needed and try to use it. But imagine you have used Composition rather than Inheritance and you have Decorator classes for each Stream process, you can easily combine your desired wrappers and have any desired processes with least amount of effort and max simplicity.

class WhereIWannaUseUseCaseOne {
    EncryptStream(ZipStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

then you come up with another use-case:

class WhereIWannaUseUseCaseSecond {
    ConvertStream(TimeStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

And so on and so forth, you have the flexibility to do what ever you want at run-time with a simple flow and understandable logic.

Maryland answered 14/6, 2020 at 7:35 Comment(0)
K
-1

A very real example for me :

I had to update a class that was heavily used in a project, but that class was in a library and the source code was lost in the netherworld.

I could either decompile the entire library to create another version of it or use the decorator design pattern, which I did. This enabled me to add the lacking functionality and simply update the configuration.

It is a very useful pattern when used responsibly.

This particular case inspired me to make this video where I explain the pattern.

Karrah answered 14/4, 2020 at 4:38 Comment(1)
I might not have been clear enough with my answer, so let me rephrase. If one of your classes named A depends on another class named B and you can't update B yourself or request and updated version then the decorator pattern can help out. The decorator pattern ensures you are providing A with an instance of B, ensuring compatibility while modifying the original behavior of B. The decorated class named C acts as a wrapper around B. When calling C's methods you can manipulate the input before delegating to B and manipulate B's output before returning much like an aspect.Karrah

© 2022 - 2024 — McMap. All rights reserved.