设计模式之 Decorator - 装饰模式

装饰模式,The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

 

       让我们来理解这句话。

 

我们系统中设计了这个类。 <!--[if !vml]--><!--[endif]-->

现在,在系统的一个地方需要一个能够报警的Door,你来怎么做呢?你或许写一个Door的子类AlarmDoor,在里面添加一个子类独有的方法alarm()。嗯,那在使用警报门的地方你必须让客户(使用者)知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了Liskov替换原则。

也许你要说,那就把这个方法添加到Door里面,这样不就统一了?但是这样所有的门都必须有警报,至少是个哑巴警报。而当你的系统仅仅在一两个地方使用了警报门,这明显是不合理的——虽然可以使用缺省适配器来弥补一下。

       这时候,你可以考虑采用装饰模式来给门动态的添加些额外的功能。

       下面我们来看看装饰模式的组成,不要急着去解决上面的问题,到了下面自然就明白了!

 

Component
原有类的接口
ConcreteComponent
功能实现类。Component的具体实现类
Decorator
饰抽象类。与ConcreteComponent一样都继承了Component接口,但实现的方式与ConcreteComponent有区别。 ConcreteComponent通过单纯继承的方式来实现;而Decorator则通过对Component对象的封装与动作委让方式来实现。
下面是一个典型的Decorator的代码为:
清单1
public class Decorator implement Component {
    Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    public void doSomething() {
        ...
        this.component.doSomething();
        ...
    }
}
ConcreteDecoratorA ConcreteDecoratorB
具体的装饰类。Decorator的具体实现类

 

下面是类图


下面看一个更具体的例子:

       Central Perk的名字因为《老友记》而享誉全球,他们的分店几乎开遍世界各地。他们发展的实在是太快了,所以他们此时正在急于实现一套由计算机管理的自动化记账系统。

对于一家咖啡店来说,都有那些变化点呢?调味品的品种和价格会变吗?咖啡的品种和价格会变吗?咖啡和调味品的组合方式会变吗?YES! 对于一家咖啡店来说,这些方面肯定会经常发生改变的!那么,当这些改变发生的时候,我们的记账系统要如何应对呢? 如果调味品发生改变,那么我们只能从代码的层次重新调整Beverage基类,这太糟糕了;如果咖啡发生改变,我们可以增加或删除一个子类即可,这个似乎还可以忍受;那么咖啡和调味品的组合方式发生改变呢?如果顾客点了一杯纯黑咖啡外加两份砂糖和一份巧克力,或者顾客点了一杯脱咖啡因咖啡(Decaf)外加三份炼乳和一份砂糖呢?

 

       那么回到装饰者模式,跟前面介绍过的模式一样,装饰者同样是一个很简单的模式,特别是画出类图和顺序图之后,一切都很清楚明了。这里只有一个地方需要特殊强调一下:Decorator是装饰者模式里非常特殊的一个类,它既继承于ComponentIS A关系】,又维护一个指向Component实例的引用【HAS A关系】,换个角度来说,DecoratorComponent之间,既有动态组合关系又有静态继承关系,WHY? 这里为什么要这么来设计?上面我们说过,组合的好处是可以在运行时给对象增加职责,DecoratorHAS AComponent的目的是让ConcreteDecorator可以在运行时动态给ConcreteComponent增加职责,这一点相对来说还比较好理解;那么Decorator继承于Component的目的是什么?在这里,继承的目的只有一个,那就是可以统一装饰者和被装饰者的接口,换个角度来说,不管是ConcretComponent还是ConcreteDecorator,它们都是 Component,用户代码可以把它们统一看作Component来处理,这样带来的更深一层的好处就是,装饰者对象对被装饰者对象的功能职责扩展对用户代码来说是完全透明的,因为用户代码引用的都是Component,所以就不会因为被装饰者对象在被装饰后,引用它的用户代码发生错误,实际上不会有任何影响,因为装饰前后,用户代码引用的都是Component类型的对象,这真是太完美了!装饰者模式通过继承实现统一了装饰者和被装饰者的接口,通过组合获得了在运行时动态扩展被装饰者对象的能力。

我们再举个生活中的例子,俗话说“人在衣着马在鞍”,把这就话用装饰者模式的语境翻译一下,“人通过漂亮的衣服装饰后,男人变帅了,女人变漂亮了;”。对应上面的类图,这里人对应于ConcreteComponent,而漂亮衣服则对应于ConcreteDecorator;换个角度来说,人和漂亮衣服组合在一起【HAS A】,有了帅哥或美女,但是他们还是人【IS A】,还要做人该做的事情,但是可能会对异性更有吸引力了(扩展功能)    

从装饰者模式的角度来考虑问题,咖啡和调味品的关系应该 是:咖啡是被装饰对象而调味品是装饰者,咖啡和调味品可以任意组合,但是不管怎么组合,咖啡还是咖啡!原来这么简单啊!

具体看下面的类图:

 



       如图所示,Beverage还是所有饮料的基类,它对应于装饰者模式类图里的Component,是所有被装饰对象的基类;HouseBlend, DarkRoast, Espresso, Decaf是具体的饮料(咖啡)种类,对应于前面的ConcreteComponent,即是具体的被装饰对象;CondimentDecorator对应于前面的Decorator,是装饰者的抽象类;而MilkMochaSoyWhip则都是具体的调味品,对于前面的ConcreteDecorator,也就是具体的装饰者。下面我们通过具体的代码再进一步理解一下基于装饰者模式的记账系统的实现。  


Beverage还是所有饮料的基类,它对应于装饰者模式类图里的Component,是所有被装饰对象的基类

 


package zieckey.designpatterns.study.decorator;

/**
 *
 * 设计模式之Decorator - 装饰模式
 *
 * Beverage 是所有饮料的基类,它对应于装饰者模式类图里的Component
 *
 * @author zieckey
 *
 */

public abstract class Beverage {

    public String getDescription()
    {
        return "Unknown Beverage";
    }
    
    public abstract double cost();
}


HouseBlend, DarkRoast, Espresso, Decaf是具体的饮料(咖啡)种类,对应于前面的ConcreteComponent,即是具体的被装饰对象;

package zieckey.designpatterns.study.decorator;

/**
 * DarkRoast咖啡,具体的Component
 * @author zieckey
 *
 */

public class DarkRoast extends Beverage {

    @Override
    public double cost() {
        // TODO Auto-generated method stub

        return 0.88;
    }

    public String getDescription() {
        return "DarkRoast";
    }    
}


package zieckey.designpatterns.study.decorator;

/**
 * Expresso咖啡,具体的Component
 * @author zieckey
 *
 */

public class Expresso extends Beverage {

    @Override
    public double cost() {
        // TODO Auto-generated method stub

        return 0.95;
    }

    public String getDescription() {
        return "Expresso";
    }
    
    
}


CondimentDecorator对应于前面的Decorator,是装饰者的抽象类

package zieckey.designpatterns.study.decorator;

/**
 * 装饰抽象类
 * @author zieckey
 *
 */

public abstract class CondimentDecorator extends Beverage{
    Beverage beverage;
    
    public CondimentDecorator( Beverage beverage )
    {
        this.beverage = beverage;
    }
    
    public double cost()
    {
        return beverage.cost();
    }
    
}


MilkMochaSoyWhip则都是具体的调味品,对于前面的ConcreteDecorator,也就是具体的装饰者


package zieckey.designpatterns.study.decorator;

//mocha咖啡,具体的Decorator

public class MochaDecorator extends CondimentDecorator {
    public MochaDecorator(Beverage beverage) {
        super(beverage);
    }

    public String getDescription() {
        return beverage.getDescription() + "+Mocha";
    }

    public double cost() {
        return 0.20 + beverage.cost();
    }
}


package zieckey.designpatterns.study.decorator;

/**
 * soy咖啡,具体的Decorator
 * @author zieckey
 *
 */

public class SoyDecorator extends CondimentDecorator {

    public SoyDecorator(Beverage beverage) {
        super(beverage);
    }

    public String getDescription() {
        return beverage.getDescription() + "+Soy";
    }

    public double cost() {
        return 0.15 + beverage.cost();
    }
}


好了。下面给出测试代码:

package zieckey.designpatterns.study.decorator;

public class ClientTest {

    /**
     * @param args
     */

    public static void main(String[] args) {
        Beverage expresso = new Expresso();
        CondimentDecorator soyDecorator1 = new SoyDecorator( expresso );
        CondimentDecorator mochaDecorator1 = new MochaDecorator( soyDecorator1 );
        
        System.out.println( mochaDecorator1.getDescription() + ", costs, " + mochaDecorator1.cost() );
        
        Beverage darkRoast = new DarkRoast();
        CondimentDecorator soyDecorator2 = new SoyDecorator( darkRoast );
        CondimentDecorator mochaDecorator2 = new MochaDecorator( soyDecorator2 );
        System.out.println( mochaDecorator2.getDescription() + ", costs, " + mochaDecorator2.cost() );
        
    }

}


输出:

Expresso+Soy+Mocha, costs, 1.2999999999999998
DarkRoast+Soy+Mocha, costs, 1.23


至此应该讲清楚了什么是装饰模式了吧。

说明:
本文是对以下三个连接的博文进行整理得到的,相信看一下三篇博文会有更大的收获。

参考:

http://blog.csdn.net/ai92/archive/2005/03/01/307046.aspx

http://www.cnblogs.com/justinw/archive/2007/06/11/779356.html

http://www.lifevv.com/sysdesign/doc/20071116210104656.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值