装饰器(Decorator)模式

 Decorator 设计模式是典型的结构型模式(在 GOF 的那本模式的 Bible 中将模式分为: 1. 创建型模式; 2. 结构型模式; 3. 行为模式三种)。它的主要用意是:动态地 为对象添加 一些额外的功能 。(记住上面两种颜色的词汇,理解装饰器模式的精髓所在!)下面是 GOF 的《 Element of reusable Object-Oriented Software 》中对Decorator 用意的概述:

Decorator Pattern ――Attaches additional responsibilities to an object dynamically . Decorators provide a flexible alternative to subclassing for extending functionality .

1 何时需要使用装饰器模式

       GOF 的那本 Bible 中关于装饰器模式列举的是一个文本组件与边框的例子(在这里我就不举了,主要是因为我会在书中举一个相似的,但却非常有说服力的例子,它对 Swing 中的某些本来应该使用 Decorator 却没有使用的对象的改进。同时会提出内包装、外包装的概念。看到这个例子后大家仔细体会吧!通过例子告诉大家一点:任何设计不是一成不变的、模式的应用是极其灵活的 …… )。下面我举一个“三明治”的例子!

       很多人都吃过三明治(我除外!“没吃过猪肉,俺可听过猪叫”),都会知道三明治必不可少的是两块面包片,然后可以在夹层里加上蔬菜、沙拉、咸肉等等,外面可以涂上奶油之类的。假如现在你要为一个三明治小店构造一个程序,其中要设计各种三明治的对象。可能你已经创建了一个简单的 Sandwich 对象,现在要产生带蔬菜的就是继承原有的 Sandwich 添加一个蔬菜的成员变量,看起来很“正点”的做法,以后我还要带咸肉的、带奶油的、带蔬菜的又分为带青菜的、带芹菜的、生菜的 …… 还是一个一个继承是吧!假如我们还需要即带蔬菜又带其它肉类,设置我们还要求这些添加成分的任意组合,那你就慢慢继承吧!

       读过几年书的会下面这个算术,我们有 n 种成分,在做三明治的时候任意搭配,那么有多少种方案呢?!算算吧!你会有惊人的发现。 N 种成分,什么都不要是 Cn0 种方案吧!要 1 种是 Cn1 吧! ….. n 种是 Cnn 吧!加起来不就是吗? Cn0+Cn1+……+Cnn-1+Cnn 还不会啊!牛顿莱布尼兹公式记得吧!(可惜 Word 的公式编辑器安装不了)总共 2 n 次方案。有可能前面 10 天写了 K 个类,老板让你再加一种成分你就得再干 10 天,下一次再加一种你可得干 20 天哦!同时你可以发现你的类库急剧地膨胀!(老板可能会说你: XXX K 天你加了 n 个成分,怎么现在这么不上进呢?后 K 天只加了 1 个成分啊?!!可能你会拿个比给老板算算,老板那么忙会睬你吗?!有可能你的老板会说:不管怎么样我就要你加, K 天你还给我加 n 个成分!!呵呵,怎么办啊!跳槽啊 ! 跳槽了也没人要你!!人家一看就知道你没学设计模式)。下面我们就使用装饰器模式来设计这个库吧!下图是我们的设计图:

 

 

       下面是以上各个类的意义:

1.         Ingredient (成分):所有类的父类,包括它们共有的方法,一般为抽象类且方法都有默认的实现,也可以为接口。它有 Bread Decorator 两个子类。这种实际不存在的,系统需要的抽象类仅仅表示一个概念,图中用红色表示。

2.         Bread (面包):就是我们三明治中必须的两片面包。它是系统中最基本的元素,也是被装饰的元素,和 IO 中的媒质流(原始流)一个意义。在装饰器模式中属于一类角色,所以其颜色为紫色。

3.         Decorator (装饰器):所有其它成分的父类,这些成分可以是猪肉、羊肉、青菜、芹菜。这也是一个实际不存在的类,仅仅表示一个概念,即具有装饰功能的所有对象的父类。图中用蓝色表示。

4.         Pork (猪肉):具体的一个成分,不过它作为装饰成分和面包搭配。

5.         Mutton (羊肉):同上。

6.         Celery (芹菜):同上。

7.         Greengrocery (青菜):同上。

总结一下装饰器模式中的四种角色: 1. 被装饰对象( Bread ); 2. 装饰对象(四种); 3. 装饰器( Decorator ); 4. 公共接口或抽象类( Ingredient )。其中 1 2 是系统或者实际存在的, 3 4 是实现装饰功能需要的抽象类。

       写段代码体会其威力吧!(程序很简单,但是实现的方法中可以假如如何你需要的方法,意境慢慢体会吧!)

         //Ingredient.java

public abstract class Ingredient {

         public abstract String getDescription();

         public abstract double getCost();    

         public void printDescription(){        

                   System.out.println(" Name      "+ this.getDescription());

                   System.out.println(" Price RMB "+ this.getCost());

         }

}

       所有成分的父类,抽象类有一个描述自己的方法和一个得到价格的方法,以及一个打印自身描述和价格的方法(该方法与上面两个方法构成模板方法哦!)

 

//Bread.java

public class Bread extends Ingredient {

         private String description ;

         public Bread(String desc){

                   this.description=desc ;

         }

         public String getDescription(){

                   return description ;

         }       

         public double getCost(){

                   return 2.48 ;

         }       

}

       面包类,因为它是一个具体的成分,因此实现父类的所有的抽象方法。描述可以通过构造器传入,也可以通过 set 方法传入。同样价格也是一样的,我就很简单地返回了。

 

//Decorator.java

public abstract class Decorator extends Ingredient {

     Ingredient ingredient ;

     public Decorator(Ingredient igd){

              this.ingredient = igd;     

     }       

     public abstract String getDescription();

     public abstract double getCost();

}

       装饰器对象,所有具体装饰器对象父类。它最经典的特征就是: 1. 必须有一个它自己的父类自己的成员变量; 2. 必须继承公共父类。这是因为装饰器也是一种成分,只不过是那些具体具有装饰功能的成分的公共抽象罢了。在我们的例子中就是有一个 Ingredient 作为其成员变量。 Decorator 继承了 Ingredient 类。

 

//Pork.java

public class Pork extends Decorator{

         public Pork(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"/n"+"Decrocated with Pork !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double porkPrice = 1.8;

                   return        basePrice + porkPrice ;

         }

}

       具体的猪肉成分,同时也是一个具体的装饰器,因此它继承了 Decorator 类。猪肉装饰器装饰可以所有的其他对象,因此通过构造器传入一个 Ingredient 的实例,程序中调用了父类的构造方法,主要父类实现了这样的逻辑关系。同样因为方法是具体的成分,所以 getDescription 得到了实现,不过由于它是具有装饰功能的成分,因此它的描述包含了被装饰成分的描述和自身的描述。价格也是一样的。价格放回的格式被装饰成分与猪肉成分的种价格哦!

       从上面两个方法中我们可以看出,猪肉装饰器的功能得到了增强,它不仅仅有自己的描述和价格,还包含被装饰成分的描述和价格。主要是因为被装饰成分是它的成员变量,因此可以任意调用它们的方法,同时可以增加自己的额外的共同,这样就增强了原来成分的功能。

      

//Mutton.java

public class Mutton extends Decorator{

         public Mutton(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"/n"+"Decrocated with Mutton !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double muttonPrice = 2.3;

                   return        basePrice + muttonPrice ;

         }

}

       羊肉的包装器。

 

//Celery.java

public class Celery extends Decorator{

         public Celery(Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"/n"+"Decrocated with Celery !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double celeryPrice =0.6;

                   return        basePrice + celeryPrice ;

         }

}

       芹菜的包装器。

 

//GreenGrocery.java

public class GreenGrocery extends Decorator{

         public GreenGrocery (Ingredient igd){

                   super(igd);

         }

         public String getDescription(){

                   String base = ingredient.getDescription();

                   return base +"/n"+"Decrocated with GreenGrocery  !";

         }

         public double getCost(){

                   double basePrice = ingredient.getCost();

                   double greenGroceryPrice = 0.4;

                   return        basePrice + greenGroceryPrice ;

         }

}

       青菜的包装器。

      

       下面我们就领略装饰器模式的神奇了!我们有一个测试类,其中建立夹羊肉的三明治、全蔬菜的三明治、全荤的三明治。(感觉感觉吧!很香的哦!)

 

public class DecoratorTest{

         public static void main(String[] args){

                   Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread")));              

                   compound.printDescription();

                  

                   compound = new Celery(new GreenGrocery(new Bread("Bread with milk")));    

                   compound.printDescription();

                  

                   compound = new Mutton(new Pork(new Bread("Bread with cheese")));

                   compound.printDescription();

                  

         }

}

       以上就是一个简单的装饰器类!假如你对想中国式的吃法,可以将加入馒头、春卷皮、蛋皮 …… 夹菜可以为肉丝 …… 突然想到了京酱肉丝。

      

2 装饰器模式的结构

       在谈及软件中的结构,一般会用 UML 图表示( UML ANT JUnit 等都是软件设计中基本的工具,会了没有啊!)。下面是一个我们经常看到的关于 Decorator 模式的结构图。

 

1.      Component 就是装饰器模式中公共方法的类,在装饰器模式结构图的顶层。

2.      ConcreateComponent 是转换器模式中具体的被装饰的类,IO 包中的媒体流就是此种对象。

3.      Decorator 装饰器模式中的核心对象,所有具体装饰器对象的父类,完成装饰器的部分职能。在上面的例子中Decorator 类和这里的对应。该类可以只做一些简单的包裹被装饰的对象,也可以还包含对Component 中方法的实现…… 他有一个鲜明的特点:继承至Component ,同时包含一个Component 作为其成员变量。装饰器模式动机中的 动态地 增加功能是在这里实现的。

4.      ConcreteDecoratorA ConcreteDecoratorB 是两个具体的装饰器对象,他们完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法,加上装饰对象自身的方法。这是装饰器模式动机中的 添加额外功能 的关键。

 

从上面图中你可能还会发现: ConcreteDecoratorA ConcreteDecoratorB 的方法不一样,这就是一般设计模式中谈及装饰器模式的“透明装饰器”和“不透明装饰器”。“透明装饰器”就是整个 Decorator 的结构中所有的类都保持同样的“接口”(这里是共同方法的意思),这是一种极其理想的状况,就像餐饮的例子一样。现实中绝大多数装饰器都是“不透明装饰器”,他们的“接口”在某些子类中得到增强,主要看这个类与顶层的抽象类或者接口是否有同样的公共方法。 IO 中的 ByteArrayInputStream 就比 Inputstrem 抽象类多一些方法,因此 IO 中的装饰器是一个“不通明装饰器”。下面是 IO 中输入字节流部分的装饰器的结构图。

 

1.         InputStream 是装饰器的顶层类,一个抽象类!包括一些共有的方法,如: 1. 读方法―― read 3 个); 2. 关闭流的方法―― close 3.mark 相关的方法―― mark reset markSupport 4. 跳跃方法―― skip 5. 查询是否还有元素方法―― available 。图中红色的表示。

2.         FileInputStream PipedInputStream… 五个紫色的,是具体的被装饰对象。从他们的“接口”中可以看出他们一般都有额外的方法。

3.         FilterInputStream 是装饰器中的核心, Decorator 对象,图中蓝色的部分。

4.         DataInputStream BufferedInputStream… 四个是具体的装饰器,他们保持了和 InputStream 同样的接口。

5.         ObjectInputStream IO 字节输入流中特殊的装饰器,他不是 FilterInputStream 的子类(不知道 Sun 处于何种意图不作为 FileterInputStream 的子类,其中流中也有不少的例子)。他和其他 FilterInputStream 的子类功能相似都可以装饰其他对象。  

IO 包中不仅输入字节流是采用装饰器模式、输出字节流、输入字符流和输出字符流都是采用装饰器模式。关于 IO 中装饰器模式的实现可以通过下面的源代码分析从而了解细节。

 

  推荐网站:好巴鹿(http:// www.haobalu.com

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值