Design Pattern学习笔记之装饰(Decorator Pattern)

Design Pattern学习笔记之装饰(Decorator Pattern)

1.    引子--Whois?

a.      它隶属于模式中的creating,就是为了应对创建对象时的变化。

b.      它使用组合技术赋予运行时改变对象行为的能力。

c.      它可为对象添加任意新的行为和特性,而不用改动原来运行良好的代码

d.      它提供了灵活扩充类功能的同时,引入了大量小型类,增加了系统的复杂度。

2.    问题引入—星巴兹咖啡店

 以下陈述未经证实:星巴兹咖啡店是近年来风头最劲的咖啡店,扩张最快,几乎遍布郑州的所有街角。随着业务的扩张,为了满足顾客的需要,原有的四种饮料(HouseBlend,DarkRoast,Decaf,Espresso)都可以根据顾客的爱好添加这几种美味的调料:蒸奶(Steamed Milk) 、豆浆(Soy)、摩卡(Mocha) 或奶泡。顾客可以在以上调料中选择不同种类,添加到任意一种饮料中;当然,多加一种调料就要多付该调料的钱,你想加多少种就可以加多少种调料(只要愿意付钱,且喜欢那种味道)。

需求弄明白了,我们来看看原有一直运行良好的Beverage类的结构:

抽象类Beverage的description变量用于描述本产品,getDescription方法提供一种默认的description,抽象的cost方法要求每个子类都实现具体的价格计算。

HouseBlend、DarkRoast、Decaf、Espresso四个子类在构造方法中初始化description,cost方法按照成本计算自己的价格。

当每一种饮料都能增加奶(Steamed Milk) 、豆浆(Soy)、摩卡(Mocha) 或奶泡时,如果还新增子类来满足变化,事情会让人大吃一惊,以下是满足部分要求的类图:

每一种类型的饮料组合一种调料就是一个新的类,该类实现cost方法,在该方法累加饮料价格和所加调料价格,就是该种饮料的价格。

3.    问题引入—减少子类怎么样?

如果使用上面的方法解决问题,光排列组合出来的子类数量都让人头疼,当任何一种调料或者饮料自身的价格发生变化时,要修改若干子类,无法维护。

我们把可以添加的各种调料都抽取到父类中,由父类负责计算添加调料对价格的影响,子类只负责计算不同类型饮料的价格,再加上添加调料的价格,事情看起来美好了不少。下面是类图:

在父类中增加milk、soy、mocha、whip四种调料的布尔变量,提供一组has和set方法来判断和设置是否有该调料,cost方法根据四种调料的布尔变量和各种调料的价格来计算调料添加对饮料价格的影响;在子类中设置调料布尔变量的值,cost方法累计本调料的价格和父类cost方法的结果。

         有哪些问题?

a.      调料价格的变化需要修改已有代码

b.      增加新品种的调料时,要在父类中增加新方法,并修改已有的cost方法

c.      以后可能会扩展新的饮料品种,可能新的饮料品种并不适合添加这四种调料,但是还要继承这四种调料的相关方法。

d.      无法多次添加同一种调料。

4.    理论篇—TheOpen-Close Principle

OO原则中最重要的原则之一:Classesshould be open for extension, but closed for modification.

类应该对扩展开发,对修改封闭。

我们应该设计这样的类,它可以很方便的扩展新的行为来满足需求的变化,而不用修改已有的代码,这样就能做到对变化的封装。

没有傻问题

Q:对扩展开发,对修改封闭,这听起来有点诡异,太对立了吧?如何才能做到?

A:这正是设计模式的魔力所在,我们可以回想下已经学过的观察者模式,添加observer时并不需要修改已有代码。

Q:如何让我们应用的每个部分都遵循O-C准则?

A:通常情况我们不需要对程序的每个部分都应用该准则,因为应用该准则会增加程序的复杂度,增加成本;我们要做的就是识别出最可能变化的部分,对该部分应用该原则。

Q:如何识别出最容易变化的、重要的内容?

A:经验和相关领域的业务知识,另外参照已有系统和其他系统也是个好方法。

5.    星巴兹的进化--设想

我们已经看到在之前的例子中,继承并不能很好的解决问题,单纯的继承不够灵活,会带来类爆炸,增加新的调料时,需要修改已有代码。

换一种思路:我们使用装饰模式,开始的时候使用饮料(HouseBlend、DarkRoast、Decaf、Espresso),在运行过程中使用调料来装饰饮料,过程如下:

a.      取得一个DarkRoast

b.      使用Mocha装饰DarkRoast

c.      使用Whip装饰DarkRoast

d.      调用DarkRoast的cost方法,并通过调用装饰类的cost方法实现价格计算

我们来看看图示的整个过程:

整个过程如下:

1.      我们先调用最外层Whip的cost方法

2.      Whip的cost方法又调用Mocha的cost方法

3.      Mocha的cost方法调用DarkRoast的cost方法

4.      DarkRoast的cost方法返回该种饮料的价格99美分

5.      Mocha在DarkRoast的价格基础上增加本身价格20美分,返回总值1.19美元

6.      Whip在Mocha返回的价格基础上再增加本身价格10美分,返回总值1.29美元

从以上的过程,能发现decorating有哪些特征?

1.      装饰类和被装饰类具有同样的类型(从同一抽象父类继承而来,或者实现同一接口)

2.      一个类可以被多个装饰类装饰

3.      正由于装饰类和被装饰类是同一类型,因此我们可以使用装饰类来取代被装饰类的使用

4.      装饰类在调用被装饰类完成剩余工作之前(或之后),增加新特性

5.      可以动态添加任意装饰类

6.    理论篇—装饰模式(the Decorator Pattern)

The DecoratorPattern attaches additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extendingfunctionality.

装饰模式无需修改类的已有代码就能为该类的实例附加新的特性,从而提供了一种比子类更灵活地扩展对象功能的方式。

 类图:

1.      Component为抽象类或接口,定义了我们要处理的业务对象的类型(如星巴兹例子中的Beverage)

2.      ConcreteComponent为具体的一种Component(如星巴兹例子中的一种具体饮料),是被装饰的对象

3.      Decorator是继承自Component的接口或抽象类,定义所有装饰类的类型;同时,说明每一个Decorator都是Component

4.      ConcreteDecoratorA是一个具体的装饰类,能作为一个component使用;持有一个被装饰类的引用wrappedObj,同时增加了新的特性newBehavior。

7.    星巴兹的进化—应用DecoratorPattern

类图:

来看看代码:

public abstract class Beverage {

    String description = "UnknownBeverage";

 

    public String getDescription() {

       return description;

    }

 

    public abstract double cost();

}

原来运行良好的抽象类Beverage保持不变,具有描述该饮料的description,获取饮料描述的方法和抽象的获取该饮料价格的抽象方法。

public abstract class CondimentDecorator extends Beverage {

    public abstract String getDescription();

}

新增抽象类CondimentDecorator为所有装饰类定义“类型”,继承自Beverage,重新定义了获取饮料描述的getDescription方法,要求所有子类覆盖该方法(以实现在装饰类中修改描述的功能)。

public class DarkRoast extends Beverage {

    public DarkRoast() {

       description = "Dark RoastCoffee";

    }

 

    public double cost() {

       return .99;

    }

}

原来运行良好的具体饮料DarkRoast类保持不变,在构造器中初始化产品描述,对外提供计算价格的方法cost。

public class Milk extends CondimentDecorator {

    Beverage beverage;

 

    public Milk(Beverage beverage) {

       this.beverage = beverage;

    }

 

    public String getDescription() {

       return beverage.getDescription() + ", Milk";

    }

 

    public double cost() {

       return .10 + beverage.cost();

    }

}

增加Milk类,用于装饰具体饮料类,以实现在饮料中增加milk的功能。该装饰类持有一个Beverage的引用,重载父类的getDescription方法(增加milk的信息),实现cost方法,在该方法中通过调用beverage的cost方法获取被装饰的对象价格+milk价格,完成混合milk后的价格计算。

来看看现在的星巴兹咖啡店:

public static void main(String args[]) {

       Beverage beverage = new Espresso();

       System.out.println(beverage.getDescription()

              + " $" + beverage.cost());

 

       Beverage beverage2 = new DarkRoast();

       beverage2 = new Mocha(beverage2);

       beverage2 = new Mocha(beverage2);

       beverage2 = new Whip(beverage2);

       System.out.println(beverage2.getDescription()

              + " $" + beverage2.cost());

 

       Beverage beverage3 = new HouseBlend();

       beverage3 = new Soy(beverage3);

       beverage3 = new Mocha(beverage3);

       beverage3 = new Whip(beverage3);

       System.out.println(beverage3.getDescription()

              + " $" + beverage3.cost());

    } 

没有傻问题:

1.      Q:我们看到星巴兹咖啡店的例子应用装饰模式后运行良好,但是我觉得关键的一点是所有对饮料的引用,使用的都是抽象类Beverage,换成具体类(HouseBlend)时,是否应用装饰模式就会有问题?

A:确实这样,运用Decorator Pattern关键的一点就是装饰类和被装饰类拥有一样的“类型”(接口和抽象类),我们可以在任何应用被装饰类的地方使用装饰类来替代,如果我们的代码依赖于具体类,那就不适合使用该模式。

2.      Q:在应用装饰模式的过程中,要使用很多装饰类来一层层包装,是不是可能会导致遗漏?怎样避免这种现象?

A:以后将要介绍的Factory和Builder模式可以解决该问题,应用这两个模式可以将层层的装饰过程很好的封装起来。

8.    JDK中的装饰模式—Java I/O

JDK中的I/O包正是装饰模式的典型应用,详细大家对以下语句都不陌生:

BufferdInputStream is = new BufferedInputStream(new FileInputStream("xxx.txt"));

你知道上一句代码中哪一个是装饰类?哪一个是被装饰类?装饰类增加了什么新特性?

我们来看看I/O包的类结构:

结构跟星巴兹咖啡店的结构很类似,InputStream作为抽象类,定义了所有输入流应该具备的行为,左侧的类是所有输入流的具体实现(ByteArrayInputStream…);右侧的FilterInputStream为所有的装饰类定义了“类型”,同时持有一个要被装饰类的实例inputStream;右侧的BufferedInputStream等子类是具体的装饰类,添加缓存等特性。

写个自己的装饰器:

public class LowerCaseInputStream extends FilterInputStream {

 

    public LowerCaseInputStream(InputStream in) {

       super(in);

    }

 

    public int read() throws IOException {

       int c = super.read();

       return (c == -1 ? c : Character.toLowerCase((char)c));

    }

      

    public int read(byte[] b, int offset, int len) throws IOException {

       int result = super.read(b, offset, len);

       for (int i = offset; i < offset+result; i++) {

           b[i] = (byte)Character.toLowerCase((char)b[i]);

       }

       return result;

    }

}

测试下看看:

public class InputTest {

    public static void main(String[] args) throws IOException {

       int c;

 

       try {

           InputStream in =

               newLowerCaseInputStream(

                  new BufferedInputStream(

                     new FileInputStream("test.txt")));

 

           while((c = in.read()) >= 0) {

              System.out.print((char)c);

           }

 

           in.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

9.    DecoratorPattern—不是题外话

1.  继承是类扩展的一种方式,由于不够灵活,因此很多时候并不是一种好方式

2.  使用装饰模式提供一种不修改原有代码,执行期间扩展类的方式

3.  在装饰模式中使用组合技术实现灵活扩展

4.  装饰模式提供一组装饰类,可使用多个装饰类对具体类进行装饰

5.  每个装饰类都拥有和被装饰类相同的“类型”

6.  装饰类通过在调用被装饰类的方法之前(或之后),做额外工作来实现为被装饰类增加新功能

7.  装饰类会导致生成很多小型类,从而增加了复杂度,不好理解(如jdk的i/o包)

 

10.             Review

The DecoratorPattern attaches additional responsibilities to an object dynamically.Decorators provide a flexible alternative to sub classing for extendingfunctionality.

装饰模式无需修改类的已有代码就能为该类的实例附加新的特性,从而提供了一种比子类更灵活地创建对象的方式。

OO准则:

a. 封装变化,encapsulate what varies

b. 组合优于继承, favorcomposition over inheritance

c. 面向接口编程,program to interfaces, not implementation

d. 致力于实现交互对象之间的松散耦合, strive for loosely coupled designs between objects that interact

e. 类应该对于扩展开发,对于修改封闭, classes should be open for extension but closed for modification

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值