When to Use the Decorator Pattern?

2019-01-02 17:30发布

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.

8条回答
人间绝色
2楼-- · 2019-01-02 18:04

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

Take a look here for a more detailed example.

查看更多
人间绝色
3楼-- · 2019-01-02 18:13

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

查看更多
忆尘夕之涩
4楼-- · 2019-01-02 18:16
  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.
查看更多
牵手、夕阳
5楼-- · 2019-01-02 18:23

Decorator pattern dynamically changes the functionality of an object at run-time without impacting the existing functionality of the objects.

Key use cases:

  1. Add additional functionalities/responsibilities dynamically
  2. Remove functionalities/responsibilities dynamically
  3. Avoid too much of sub-classing to add additional responsibilities.

Drawbacks:

  1. Overuse of Open Closed principle ( Open for extension and Closed for modification). Use this feature sparingly where the code is least likely changed.
  2. Too many small classes and will add maintenance overhead.

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. It's possible due to composition rather than inheritance concept used in this pattern.

Related SE question:

Decorator Pattern for IO

Useful links:

design-patterns-decorator by dzone

decorator by sourcemaking

oodesign article

查看更多
还给你的自由
6楼-- · 2019-01-02 18:25

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.

查看更多
孤独总比滥情好
7楼-- · 2019-01-02 18:25

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.

查看更多
登录 后发表回答