《Head First设计模式》第三章笔记 装饰者模式

装饰者模式(Decorator Pattern)

    *利用组合(composition)和委托(delegation)可以在运行时实现继承行为的效果,动态地给对象加上新的行为。

    *利用继承扩展子类的行为,是在编译时静态决定的;利用组合的做法,可以在运行时动态地扩展对象的行为。

软件设计原则:类应该对扩展开放,对修改关闭。这就是我们常说的开放关闭原则。

    *开放-关闭原则使类容易扩展,在不修改代码的情况下,通过搭配实现新的行为。这样的设计可以应对改变,比如增加新功能或需求发生变更。

OO设计技巧:允许系统在不修改代码的情况下,进行功能扩展。
    装饰者模式:动态地将责任加到对象身上。如果要扩展功能,装饰者模式提供了比继承更有弹性的替代方案。
    装饰者模式中,装饰者可以在被装饰者的行为之前或之后,加上自己的行为,以实现特性的目的。

装饰者模式的几个缺点:

(1)有时在设计中加入大量的小类,变得不容易理解。
(2)有的客户端代码依赖于特定的类型(这是个比较糟糕的习惯,违反了“针对接口编程,而不是针对实现编程”的设计原则),当服务器端引入装饰者模式时,客户端就会出现状况。
(3)装饰者模式使得实例化组件的复杂度提升。
*遵循开放-关闭原则设计系统,努力使关闭的部分(不变)和开放的部分(变化)隔离开来。

问题背景:

设计一个咖啡订单系统。咖啡可以加配料,不同的配料收费不同。如果一个顾客想要摩卡(Mocha)和奶泡(Whip)深焙咖啡(DarkRoast)该怎样去计算费用呢?

类结构图

Beverage(饮料)是一个抽象类,店内所提供的饮料都必须继承此类。cost()方法是抽象的,子类必须定义自己的实现。description(叙述)的实例变量,由每个子类设置,用来描述饮料,如“超优深焙咖啡豆”。利用getDescription()方法返回此叙述。购买咖啡时,可以要求在其中加入各种调料,如:牛奶、豆浆、摩卡等。咖啡馆会根据所加入的调料收取不同的费用。如果直接用继承,会造成类爆炸。如果从基类Beverage下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡……),看起来是不错,但是不符合我们的设计原则。如调料价钱改变会使我们更改基类代码、一旦出现新的调料,我们就需要加上新方法,并改变超类中的cost()等,所以我们使用到了装饰者模式。

先从Beverage类下手

1

2

3

4

5

6

7

8

9

public abstract class Beverage{

    publicstring description="Uknown Beverage"

    //叙述方法已经实理

    public String getDescription(){

        return description;

    }

    //价线必须在子类中实理

    public abstract double cost();

}

实现调料(Condiment)类,同时这个类也是个装饰者类

//必须让CondimentDecorator 能够取代Beverage public abstract class CondimentDecorator extends Beverage{ //所有转饰者必须重新实现getDescription public abstract String getDescription(); }基类已经创建好了,让我们来实现那些饮料(Beverage)类

//让Espresso扩展自Beverage,因为Espresso是一种饮料

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class Espresso extends Beverage {

    //设置饮料的描述

    public Espresso() {

        description = "Espresso";

    }

    //计算Espresso的价钱,现在不需要管调料的价钱

    public double cost() {

    return 1.99;

    }

}

public class HouseBlend extends Beverage {

    public HouseBlend() {

        description = "HouseBlend";

    }

    public double cost() {

        return .89;

    }

}

我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在,我们就来实现具体装饰者:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//Mocha是一个装饰者,所以扩展自CondimentDecorator

public class Mocha extends CondimentDecorator {

    //用一个实例变量记录饮料,也就是被装饰者

    Beverage beverage;

    //把饮料当做构造器参数,再由构造器将此饮料记录在实例变量中

    public Mocha(Beverage beverage) {

        this.beverage = beverage;

    }

    //利用委托的做法,得到一个叙述,然后在其后加上调料的叙述

    public String getDescription() {

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

    }

    //首先把调用委托给被装饰者对象,然后再加上Mocha的价钱

    public double cost() {

        return .20 + beverage.cost();

    }

}

测试代码:

1

2

3

4

5

6

7

8

9

10

11

public class App {

    public static void main(String[] args) {

        //订一杯Espresso,不需要调料

        Beverage espresso = new Espresso();

        System.out.println(espresso.getDescription() + " $" + espresso.cost());

        //订一杯调料为摩卡的HouseBlend咖啡

        Beverage houseBlend = new HouseBlend();

        houseBlend = new Mocha(houseBlend);

        System.out.println(houseBlend.getDescription() + " $" + houseBlend.cost());

    }

}

输出为:

Espresso $1.99

HouseBlend,Mocha $1.09

总结

装饰者模式:动态地将责任附件到对象上。若要扩展功能,装饰者提东了比继承更有弹性的替代方案。

*装饰者和被装饰对象有相同的超类型

*你可以用一个或者多个装饰者包装一个对象。

*既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。

*装饰者可以在所委托被装饰者的行为前与/或之后,加上自己的行为,已达到特定的目的。

*对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

 

设计原则:类应该对扩展开放,对修改关闭(开放-关闭原则)。

注意:遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。在选择需要被扩展的代码部分时要小心,每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解。

要点:

    • 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
    • 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码。
    • 组合和委托可用于在运行时动态地加上新的行为。
    • 除了继承,装饰者模式也可以让我们扩展行为,
    • 装饰者模式意味着一群装饰者类,这些类用了包装具体组件。
    • 装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
    • 装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
    • 你可以用无数个装饰者包装一个组件。
    • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
    • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页