Hycz's Blog

Life is a game. Why so serious?

设计模式之装饰器模式(Decorator Pattern)

在看Cassandra源码的过程中,遇到了使用Decorator Pattern的代码,相关介绍讲的比较完整的地方是维基百科(http://en.wikipedia.org/wiki/Decorator_pattern)。下面,做简单介绍,不过,最直观的,还是看他的例子。

一、介绍

Decorator Pattern用来在run-time中扩展一个对象的功能,并且不会影响其他同class的实例,这个class仅在design time中提供基本功能。话听起来很拗口,其实呢,意思就是,当你需要一个对象拥有新的功能时,你不需要更改这个对象的源码,而是把新加的功能另写在一个类中,就像装饰一样,当然,有很多装饰,于是,在真正调用使用这个对象的代码中,就可以随心意选择需要的功能包在这个对象之外,而不需要对这些额外功能的每种组合都新写一个类。

下面这段话引用于此(http://www.dotblogs.com.tw/atowngit/archive/2010/03/04/13880.aspx),“

(原文出處 大話設計模式 )
裝飾者模式 : 動態的給物件加入一些額外的職責。
書上舉了個類似洋娃娃的例子,洋娃娃要穿甚麼衣服端看它的主人心情而定。
因為我們可以自由的任意搭配(今天嘻哈風、明天居家風),所以我們不應該將裝飾的功能寫死在類別裡。
一樣依據開放-封閉原則,將裝飾的寫成一個類別,用來擴充物件的功能。
是的,我表達能力不好。可參考

二、如何设计一个decorator pattern

简而言之,需要设计一个新的decorator class,包装在原始 class外

包装的方法需要遵循以下步骤:

  1. 首先建一个需要被装饰的类(称为Component)的子类,作为原始的装饰器(Decorator);
  2. 在装饰器类中,有一个属性,是Component的指针;
  3. 需要传递一个Component给Decorator的构造器,使得Decorator能够初始化那个Component的指针,于是,被装饰的对象总是作为参数放在了括号里,装饰的方法在外面,这使得整个代码看起来更像是一种包装;
  4. 在Decorator class中,将所有Component中的方法重定向到那个指针中去,因为这些方法暂时是不需要改动的,例如,decorated.xxxx();
  5. 最后就是具体的Decorator class,这些类需要继承那个原始的装饰器,然后,根据需要override那些需要改动的方法,或者增加新的方法。

这种模式让多个装饰器如同栈一样一个叠在另一个之上,每个装饰器都增加新的功能,方式或者是override已有的方法,或者是增加新的方法。

三、与子类的不同

装饰器模式和子类方式是两种相似然而不尽相同的方案。子类方式在编译时(compile time)增加新的行为,并且这种更改会影响到所有的实例;装饰器则是在运行时增加新的行为(run-time),并且只针对单个对象

四、例子

wiki上的例子很好,直接复制了(仅仅Java部分):

First Example (window/scrolling scenario)

The following Java example illustrates the use of decorators using the window/scrolling scenario.

// the Window interface
interface Window {
    public void draw(); // draws the Window
    public String getDescription(); // returns a description of the Window
}

// implementation of a simple Window without any scrollbars
class SimpleWindow implements Window {
    public void draw() {
        // draw window
    }

    public String getDescription() {
        return "simple window";
    }
}

The following classes contain the decorators for all Window classes, including the decorator classes themselves.

// abstract decorator class - note that it implements Window
abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated

    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    public void draw() {
        decoratedWindow.draw();
    }
}

// the first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }

    public void draw() {
        decoratedWindow.draw();
        drawVerticalScrollBar();
    }

    private void drawVerticalScrollBar() {
        // draw the vertical scrollbar
    }

    public String getDescription() {
        return decoratedWindow.getDescription() + ", including vertical scrollbars";
    }
}

// the second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }

    public void draw() {
        decoratedWindow.draw();
        drawHorizontalScrollBar();
    }

    private void drawHorizontalScrollBar() {
        // draw the horizontal scrollbar
    }

    public String getDescription() {
        return decoratedWindow.getDescription() + ", including horizontal scrollbars";
    }
}

Here’s a test program that creates a Window instance which is fully decorated (i.e., with vertical and horizontal scrollbars), and prints its description:

public class DecoratedWindowTest {
    public static void main(String[] args) {
        // create a decorated Window with horizontal and vertical scrollbars
        Window decoratedWindow = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator(new SimpleWindow()));

        // print the Window's description
        System.out.println(decoratedWindow.getDescription());
    }
}

The output of this program is “simple window, including vertical scrollbars, including horizontal scrollbars”. Notice how the getDescription method of the two decorators first retrieve the decorated Window‘s description and decorates it with a suffix.

Second Example (coffee making scenario)

The next Java example illustrates the use of decorators using coffee making scenario. In this example, the scenario only includes cost and ingredients.

// The Coffee Interface defines the functionality of Coffee implemented by decorator
public interface Coffee {
    public double getCost(); // returns the cost of the coffee
    public String getIngredients(); // returns the ingredients of the coffee
}

// implementation of a simple coffee without any extra ingredients
public class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }

    public String getIngredients() {
        return "Coffee";
    }
}

The following classes contain the decorators for all Coffee classes, including the decorator classes themselves..

// abstract decorator class - note that it implements Coffee interface
abstract public class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;
    protected String ingredientSeparator = ", ";

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    public double getCost() { // implementing methods of the interface
        return decoratedCoffee.getCost();
    }

    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

// Decorator Milk that mixes milk with coffee
// note it extends CoffeeDecorator
public class Milk extends CoffeeDecorator {
    public Milk(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() { // overriding methods defined in the abstract superclass
        return super.getCost() + 0.5;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Milk";
    }
}

// Decorator Whip that mixes whip with coffee
// note it extends CoffeeDecorator
public class Whip extends CoffeeDecorator {
    public Whip(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() {
        return super.getCost() + 0.7;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Whip";
    }
}

// Decorator Sprinkles that mixes sprinkles with coffee
// note it extends CoffeeDecorator
public class Sprinkles extends CoffeeDecorator {
    public Sprinkles(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    public double getCost() {
        return super.getCost() + 0.2;
    }

    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Sprinkles";
    }
}

Here’s a test program that creates a Coffee instance which is fully decorated (i.e., with milk, whip, sprinkles), and calculate cost of coffee and prints its ingredients:

public class Main
{
    public static void main(String[] args)
    {
        Coffee c = new SimpleCoffee();
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Milk(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        c = new Whip(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());

        // Note that you can also stack more than one decorator of the same type
        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
    }
}

The output of this program is given below:

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
Cost: 2.4; Ingredients: Coffee, Milk, Sprinkles, Whip
Cost: 2.6; Ingredients: Coffee, Milk, Sprinkles, Whip, Sprinkles

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: