天天看点

软件设计模式 | 结构型之装饰者模式详解

目录

    • 星巴兹咖啡的故事
    • 装饰者模式
      • 一、模式介绍
      • 二、模式实现
      • 三、模式设计
      • 四、模式的优缺点
      • 五、使用场景:
      • 六、装饰者用到的设计原则:

星巴兹咖啡的故事

  1. 现在有一个咖啡馆,他有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:
    软件设计模式 | 结构型之装饰者模式详解
  2. 每一种饮料都继承抽象饮料类 Beverage,计算价钱只需要调用相应子类的 cost() 方法即可,其中 description 是其相应的饮料描述。饮料少的情况下完全没问题,但咖啡馆为了吸引更多的顾客,在系统中允许顾客选择加入不同的调料,如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。如果说依旧用原来的设计那么整个系统会出现爆炸的状况。(当然我只画了一小部分,再多点 visio 就崩溃了 /(ㄒoㄒ)/ )
    软件设计模式 | 结构型之装饰者模式详解
  3. 这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……)
    软件设计模式 | 结构型之装饰者模式详解
    软件设计模式 | 结构型之装饰者模式详解

    这种设计虽然满足了现在的需求,但也存在非常大的局限性:

      最主要的问题就是,如果顾客选择了双份的奶泡(配料),该怎么办?

      其次,出现了新的材料,那么还需要修改父类中的 cost() 方法,这违反了开放关闭原则(类应该对扩展开放,对修改关闭)。

       …

      那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。

装饰者模式

一、模式介绍

  装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

  这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

  我们通过下面的实例来演示装饰器模式的用法。

二、模式实现

以摩卡和奶泡深焙咖啡为例:

步骤:

  1. 拿到一个深焙咖啡(DarkRoast)对象
  2. 以摩卡(Mocha)对象去装饰它
  3. 以奶泡(Whip)对象去装饰它
  4. 调用 cost() 方法

图示步骤:

  1. 拿到一个深焙咖啡(DarkRoast)对象
    软件设计模式 | 结构型之装饰者模式详解
  2. 顾客要摩卡(Mocha),所以建立 Mocha 对象,并用它将 DarkRoast 对象装起来
    软件设计模式 | 结构型之装饰者模式详解
  3. 顾客要奶泡(Whip),所以建立 Whip 对象,并用它将以 Mocha 装饰的 DarkRoast 对象装起来
    软件设计模式 | 结构型之装饰者模式详解
      可以把 Mocha 装饰之后的 DarkRoast 仍然看成是一个 Beverage ,原因在于多态!无论是装饰者摩卡(Mocha)、奶泡(Whip)还是被装饰者深焙咖啡 归根结底都是继承着顶层的抽象类(Beverage)。因此无论 DarkRoast 被多少装饰者装饰着包装着都是一个Beverage。
  4. 调用 cost() 方法,递归实现计算最终价钱。
软件设计模式 | 结构型之装饰者模式详解

三、模式设计

装饰者模式的模板类图:

软件设计模式 | 结构型之装饰者模式详解

装饰者模式所包含的角色如下:

  1. Component : 抽象构件角色,定义对象的接口,可以给这些对象动态增加职责。
  2. ConcreteComponent : 被装饰者,它是我们将要动态地加上新行为的对象,它扩展自Component。
  3. Decorator : 这是装饰者共同实现的接口(抽象类)。
  4. ConcreteDecator : 具体的装饰者,可以加上新的方法。新行为使用过在旧行为前面或者后面做一些计算来添加的,并且每一个装饰者 都有一个实例变量,它可以记录所装饰的事物。

根据以上模板类图来绘制出订单系统的类图:

软件设计模式 | 结构型之装饰者模式详解

代码如下:

Beverage : 抽象饮料类

/**
 * 抽象饮料类
 */
public abstract class Beverage {

    String name = "Unknown Beverage";

    //返回饮料名字
    public String getName() {
        return name;
    }

    //cost方法是用来返回饮料的价钱,需在具体类中自己实现
    public abstract int cost();
}
           

具体饮料类:被装饰者,包括:独家调配的咖啡(HouseBlend)、深焙咖啡(DarkRoast)、浓缩咖啡(Espresso)、低咖啡因咖啡类(Decaf)

/**
 * 独家调配的咖啡类(一种具体饮料)
 */
public class HouseBlend extends Beverage {
    //说明他是HouseBlend饮料
    public HouseBlend() {
        name = "独家调配的咖啡";
    }
    //实现cost方法,用来返回 独家调配的咖啡 的价格
    @Override
    public int cost() {
        return 9;
    }
}
           
/**
 * 深焙咖啡类(一种具体饮料)
 */
public class DarkRoast extends Beverage {

    //说明他是DarkRoast饮料
    public DarkRoast() {
        name = "深焙咖啡";
    }

    //实现cost方法,用来返回 深焙咖啡 的价格
    @Override
    public int cost() {
        return 11;
    }
}
           
/**
 * 浓缩咖啡类(一种具体饮料)
 */
public class Espresso extends Beverage {

    //说明他是Espresso饮料
    public Espresso() {
        name = "浓缩咖啡";
    }

    //实现cost方法,用来返回 浓缩咖啡 的价格
    @Override
    public int cost() {
        return 12;
    }
}
           
/**
 * 低咖啡因咖啡类(一种具体饮料)
 */
public class Decaf extends Beverage {

    //说明他是Decaf饮料
    public Decaf() {
        name = "低咖啡因咖啡";
    }

    //实现cost方法,用来返回 低咖啡因咖啡 的价格
    @Override
    public int cost() {
        return 10;
    }
}
           

Dector : 装饰者共同实现的接口

/**
 * 调料装饰着抽象类(继承自饮料抽象类)
 */
public abstract class Decorator extends Beverage {

    /**
     * 所有的调料装饰者都必须重新实现getDescription()方法
     * 这样才能够用递归的方式来得到所选饮料的整体描述
     */
    public abstract String getName();
}
           

ConcreteDecorator : 具体的装饰者,包括:牛奶(Milk)、摩卡(Mocha)、豆浆(Soy)、奶泡(Whip)

/**
 * 牛奶调料类(继承自 Decorator)
 */
public class Milk extends Decorator {
    //用一个实例变量记录饮料,也就是被装饰者
    Beverage beverage;

    //构造器初始化饮料变量
    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    //在原来饮料的基础上添加上Milk描述(原来的饮料加入Milk调料,被Milk调料装饰)
    @Override
    public String getName() {
        return beverage.getName() + ",牛奶";
    }

    //在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
    @Override
    public int cost() {
        return beverage.cost() + 3;
    }
}
           
/**
 * 摩卡调料类(继承自 Decorator)
 */
public class Mocha extends Decorator {

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

    //构造器初始化饮料变量
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    //在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
    @Override
    public String getName() {
        return beverage.getName() + ",摩卡";
    }

    //在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
    @Override
    public int cost() {
        return beverage.cost() + 2;
    }
}
           
/**
 * 豆浆调料类(继承自 Decorator)
 */
public class Soy extends Decorator {

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

    //构造器初始化饮料变量
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    //在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
    @Override
    public String getName() {
        return beverage.getName() + ",豆浆";
    }

    //在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
    @Override
    public int cost() {
        return beverage.cost() + 1;
    }
}
           
/**
 * 奶泡调料类(继承自 Decorator)
 */
public class Whip extends Decorator {

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

    //构造器初始化饮料变量
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    //在原来饮料的基础上添加上 Milk 描述(原来的饮料加入 Whip 调料,被 Whip 调料装饰)
    @Override
    public String getName() {
        return beverage.getName() + ",奶泡";
    }

    //在原来饮料的基础上加上 Whip 的价格(原来的饮料加入Soy调料,被 Whip 调料装饰)
    @Override
    public int cost() {
        return beverage.cost() + 4;
    }
}
           

Client : 客户端演示代码

public class Client {
    public static void main(String[] args) {
        System.out.println("饮料价格如下:");
        System.out.println("独家调配的咖啡类:9、低咖啡因咖啡:10、深焙咖啡类:11、浓缩咖啡类:12");
        System.out.println("调料价格如下:");
        System.out.println("豆浆:1、摩卡:2、牛奶:3、奶泡:4\n");
        //订一杯浓缩咖啡类Espresso(12) 对象,不需要调料,打印出它的描述与价钱。
        Beverage beverage1 = new Espresso();
        System.out.println("饮料描述: " + beverage1.getName() + "  价格为: " + beverage1.cost());
        System.out.println("---------------------------------------------------");

        //制造出一个深焙咖啡类DarkRoast(11) 对象,用摩卡Mocha(2)装饰它,用第二个摩卡Mocha(2)装饰它,用奶泡Whip(4)装饰它,打印出它的描述与价钱。
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        System.out.println("饮料描述: " + beverage2.getName() + "  价格为: " + beverage2.cost());
        System.out.println("---------------------------------------------------");

        //也可用这种形式
        Beverage beverage3 = new Mocha( new Mocha(new DarkRoast()));
        System.out.println("饮料描述: " + beverage3.getName() + "  价格为: " + beverage3.cost());
        System.out.println("---------------------------------------------------");

        //再来一杯 低咖啡因咖啡Decaf(10)对象,并用豆浆Soy(1)、摩卡Mocha(2)、奶泡Whip(4),打印出它的描述与价钱。
        Beverage beverage4 = new Decaf();
        beverage4 = new Soy(beverage4);
        beverage4 = new Mocha(beverage4);
        beverage4 = new Whip(beverage4);
        System.out.println("饮料描述: " + beverage4.getName() + "  价格为: " + beverage4.cost());
    }
}
           

结果展示:

软件设计模式 | 结构型之装饰者模式详解

四、模式的优缺点

优点: 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点: 多层装饰比较复杂。

  

五、使用场景:

  1. 扩展一个类的功能。
  2. 动态增加功能,动态撤销。

      

六、装饰者用到的设计原则:

  1. 多用组合,少用继承。
  2. 对扩展开放,对修改关闭。

本文的主要思路以及开篇引述的故事都借鉴于下面这篇博客,博主写的非常清晰,如果没有看懂的地方,可前往链接查看详情。

参考博客:https://www.cnblogs.com/of-fanruice/p/11565679.html

继续阅读