装饰者模式

188 阅读5分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

提出需求

下面是某家咖啡店的价格表,顾客可以自由组合出不同的Coffee和添加任意配料,现在我们需要进行设计Coffee店的订单系统。

CoffeePrice
HouseBlend0.89
DarkRoast0.99
Decaf1.05
Espresso1.99
CondimentPrice
Milk0.1
Mocha0.2
Soy0.15
Whip0.1

我们首先想到之前使用的工厂类,但是不同的是,本次我们核心需要并不是得到大量的对象,而是如何设计订单系统,即根据用户需求得到不同的结果。这就需要我们自己进行模式的探索,我们想到了使用继承

abstract class Beverage {
    getDescription();
    cost();
    //
}

class HouseBlendWithMilkandMocha extends Beverage {
    cost();
}

class HouseBlendWithSoyandMochaandWhip extends Beverage {
    cost();
}
//

按照这种设计,我们需要实现64个类,但如果允许顾客选择Double Mocha或者Coffee店有了新产品,那么这个子类数量级的增加是可怕的

装饰者模式

日常使用的订单系统中,店员会根据需要先在价格中加上咖啡,然后根据顾客选择的配料每次在前一个价格上进行累加,这和装饰者模式是类似的。

简单来说,我们需要一个基础类,然后在这个基础类中进行修饰,DoubleMocha加Whip的HouseBlend式咖啡可以如下表示

image.png

我们可以看出装饰者模式主要思想:动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。

利用装饰者模式实现

我们先定义一个Beverage抽象类,所有coffee都是这个类的子类,作为接口为所有的coffee进行类型的匹配。

public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

然后我们要实现coffee,coffee只需要按照抽象类具体它的描述和价格

public class HouseBlend extends Beverage {
    HouseBlend() {
        description = "House Blend Coffee";
    }
    @Override
    public double cost() {
        return .89;
    }
}

public class Espresso extends Beverage {
    Espresso() {
        description = "Espresso";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}

public class Decaf extends Beverage {
    Decaf() {
        description = "Decaf";
    }
    @Override
    public double cost() {
        return 1.05;
    }
}
// ...

然后我们实现配料的接口,他的目的仅仅在于约束配料类的方法

public abstract class CondimentDecorator extends Beverage{
    public abstract String getDescription();
}

下面实现配料的具体类,他们是具体的装饰者,给对象进一步添加描述并修改价格

public class Milk extends CondimentDecorator {
    Beverage beverage;

    Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Milk";
    }

    @Override
    public double cost() {
        return beverage.cost() + .10;
    }
}

public class Mocha extends CondimentDecorator {
    private Beverage beverage;

    Mocha(Beverage beverage){
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }

    @Override
    public double cost() {
        return .10 + beverage.cost();
    }
}

// ...

然后我们测试这个订单系统

public class Starbucks {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        Order(beverage);

        Beverage beverage1 = new HouseBlend();
        beverage1 = new Mocha(beverage1);
        beverage1 = new Mocha(beverage1);
        beverage1 = new Whip(beverage1);
        Order(beverage1);

        Beverage beverage2 = new Decaf();
        beverage2 = new Soy(beverage2);
        beverage2 = new Milk(beverage2);
        Order(beverage2);
    }

    private static void Order(Beverage beverage1) {
        System.out.println(beverage1.getDescription() + " $" + beverage1.cost());
    }
}

image.png

装饰者模式的组成

装饰者模式是一个比较简单的模式,逻辑框图也很容易理解,Component是得到所有对象的统一接口,进行装饰和装饰后得到的对象都是Component类型,在具体装饰者中,会有一个Component类型的变量来记录所装饰的对象。

image.png

装饰者模式框图装饰者模式大多有着上述结构。

image.png 装饰者模式创建对象

Java I/O

java.io是我们常用的包,这个包对I/O方法很好的用到了DecoratorPattern

image.png

  • 基本组件

    • FileInputStream 读取文件
    • ByteArrayInputStream 读取字节数组
  • 装饰者

    • BufferedInputStream 先读取到缓冲区中
    • DataInputStream 读入大数字
    • PushbackInputStream 可以在内存中回退
// 组件
InputStream inputStream = new FileInputStream("");
// 缓冲流 装饰器
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

总结

代替继承

  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展
  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性,他所采用的关联关系不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

缺点

用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。

应用环境

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承,如final类