11.装饰者模式

1、星巴克咖啡订单项目

咖啡种类/单品咖啡:

  • Espresso(意大利浓咖啡)、ShortBlackLongBlack(美式咖啡)、Decaf(无因咖啡)

调料:

  • MilkSoy(豆浆)、Chocolate

要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便

使用OO的来计算不同种类咖啡的费用:

  • 客户可以点单品咖啡,也可以单品咖啡+调料组合(例如:Milk+Soy,1对多)

桥接是2个维度,一对一。 这个是一对多(一个种类可以对应多个调料),所以可以聚合一个List<> 接口(这里对应 List<调料>

2、方案一,问题

image-20221009140250537

  • Drink 是一个抽象类,表示饮料

  • des就是就是对咖啡的描述,比如咖啡的名字

  • cost() 方法就是计算费用,Drink 类做成一个抽象类

  • Decaf就是单品咖啡,继承Drink,并实现cost

  • Espress&&Milk就是单品咖啡+调料,这个组合很多

  • 问题:

    • 这样设计,会有很多的类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸(类似排列组合

3、方案二,问题

前面分析到方案1

  • 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,
  • 将调料内置Drink类,这样就不会造成类数量过多。
  • 从而提高项目的维护性(如图)

image-20221009141301272

  • 方案2可以控制类的数量,不至于造成很多的类
  • 再增加或者删除调料种类时,代码的维护量还是很大
  • 考虑到用户可以添加份 调料时,可以将hasMilk 返回一个对应int
  • 考虑使用 装饰者模式

4、基本介绍

装饰者模式:

  • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)

这里提到的动态的将新功能附加到对象ocp原则

5、原理类图

image-20221009152619374

  • 装饰者模式就像打包一个快递
    • 主体:比如:陶瓷,衣服(Component)//被装饰者
    • 包装:比如:报纸填充,塑料泡沫,纸板,木板(Decorator)//装饰者
  • Component
    • 主体:比如类似前面的Drink
  • ConcreteComponent
    • 具体的主体,比如前面的各个单品咖啡
  • Decorator
    • 装饰者,比如各调料,用调料去装饰咖啡,最终也是主体(咖啡),所以要去继承主体
  • 在如图的ComponentConcreteComponent之间,
    • 如果ConcreteComponent类很多,还可以设计一个缓冲层,
    • 将共有的部分提取出来,抽象层一个类。

6、解决

image-20221009152327750

image-20221009150655548

Milk包含了LongBlack

一份Chocolate包含了(Milk+LongBlack)

一份Chocolate包含了(chocolate+Milk+LongBlack)

这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。

public class CoffeeBar {
    public static void main(String[] args) {
        //2份巧克力+一份牛奶的LongBlack

        //1、点一份LongBlack
        Drink order = new LongBlack("美食咖啡", 2);
        System.out.println("费用1 " + order.cost()
                + "描述: " + order.des());//费用1 2.0描述: 美食咖啡

        //2、order 加一份牛奶
        order = new Milk(order);
        System.out.println("费用2 " + order.cost()
                + "描述: " + order.des());//费用2 4.0描述: 牛奶 2.0 && 美食咖啡

        //3、order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("费用3 " + order.cost()
                + "描述: " + order.des());//费用2 10.0描述: 巧克力 6.0 && 牛奶 2.0 && 美食咖啡
    }
}
public class Decorator extends Drink {
    private Drink obj;

    public Decorator(Drink obj) {//组合,没有默认构造器
        this.obj = obj;
    }

    @Override
    public float cost() {
        //price自己的价格,递归,自己操作自己定
        return super.price() + obj.cost();
    }

    @Override
    public String des() {
        //obj.des() 被装饰者的描述信息,递归,具体操作自己定
        return super.des() + " " + super.price() + " && " + obj.des();
    }
}
public class Milk extends Decorator{

    public Milk(Drink obj) {
        super(obj);
        setDes("牛奶");
        setPrice(2);
    }

}
public class Coffee extends Drink {
    public Coffee() {
    }

    public Coffee(String des, float price) {
        super(des, price);
    }

    @Override
    public float cost() {
        return super.price();
    }
}
public class LongBlack extends Coffee {
    public LongBlack(){
    }

    public LongBlack(String des, float price) {
        super(des, price);
    }
}

7、装饰者模式-jdk源码

img

image-20221009154111240

/*
 * 1、InputStream 是抽象类,类似于我们前面讲的 Drink,即构件
 * 2、FileInputStream 是 InputStream 子类,类似我们前面的Decaf,LongBlack,即具体的构件角色
 * 3、FilterInputStream 是 InputStream 子类,类似于我们前面的 Decorator 修饰者
 * 4、DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy等
 * 5、FilterInputStream 类 由 protected volatile InputStream in; 即含被装饰者
*/
  • 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

  • 具体构件(ConcreteComponent)角色:由ByteArrayInputStreamFileInputStreamPipedInputStreamStringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

  • 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

  • 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStreamDataInputStream以及两个不常用到的类LineNumberInputStreamPushbackInputStream

设计模式详解——装饰者模式 - 简书 (jianshu.com)

8、半透明的装饰者模式

装饰者模式和适配器模式都是“包装模式(Wrapper Pattern)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。

理想的装饰者模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。

装饰者模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。

  • 透明的装饰者模式也就是理想的装饰者模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
  • 相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰者模式也是可以接受的,称为“半透明”的装饰模式,如下图所示。

img

在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽。

显然,半透明的装饰者模式实际上就是处于适配器模式与装饰者模式之间的灰色地带。如果将装饰者模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰者模式倒可以成为这种合并后的“包装模式”的代表。

8.1、InputStream类型中的装饰者模式

InputStream类型中的装饰者模式是半透明的。为了说明这一点,不妨看一看作装饰者模式的抽象构件角色的InputStream的源代码。这个抽象类声明了九个方法,并给出了其中八个的实现,另外一个是抽象方法,需要由子类实现。

public abstract class InputStream implements Closeable {

    public abstract int read() throws IOException;

 
    public int read(byte b[]) throws IOException {}

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

    public long skip(long n) throws IOException {}

    public int available() throws IOException {}
    
    public void close() throws IOException {}
    
    public synchronized void mark(int readlimit) {}
    
    public synchronized void reset() throws IOException {}

    public boolean markSupported() {}
}

下面是作为装饰模式的抽象装饰角色FilterInputStream类的源代码。可以看出,FilterInputStream的接口与InputStream的接口是完全一致的。也就是说,直到这一步,还是与装饰模式相符合的。

public class FilterInputStream extends InputStream {
    protected FilterInputStream(InputStream in) {}
    
    public int read() throws IOException {}

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

    public long skip(long n) throws IOException {}

    public int available() throws IOException {}

    public void close() throws IOException {}

    public synchronized void mark(int readlimit) {}

    public synchronized void reset() throws IOException {}

    public boolean markSupported() {}
}

下面是具体装饰角色PushbackInputStream的源代码。

public class PushbackInputStream extends FilterInputStream {
    private void ensureOpen() throws IOException {}
    
    public PushbackInputStream(InputStream in, int size) {}

    public PushbackInputStream(InputStream in) {}

    public int read() throws IOException {}

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

    public void unread(int b) throws IOException {}

    public void unread(byte[] b, int off, int len) throws IOException {}

    public void unread(byte[] b) throws IOException {}

    public int available() throws IOException {}

    public long skip(long n) throws IOException {}

    public boolean markSupported() {}

    public synchronized void mark(int readlimit) {}
 
    public synchronized void reset() throws IOException {}

    public synchronized void close() throws IOException {}
}

查看源码,你会发现,这个装饰类提供了 额外的方法unread(),这就意味着PushbackInputStream是一个半透明的装饰类。

换言之,它破坏了理想的装饰者模式的要求。如果客户端持有一个类型为InputStream对象的引用in的话,那么如果in的真实类型是 PushbackInputStream的话,只要客户端不需要使用unread()方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就 必须进行向下类型转换

in的类型转换成为PushbackInputStream之后才可能调用这个方法。但是,这个类型转换意味着客户端必须知道它 拿到的引用是指向一个类型为PushbackInputStream的对象。这就破坏了使用装饰者模式的原始用意

现实世界与理论总归是有一段差距的。纯粹的装饰者模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰者模式。

额外的方法unread(),这就意味着PushbackInputStream是一个半透明的装饰类。

换言之,它破坏了理想的装饰者模式的要求。如果客户端持有一个类型为InputStream对象的引用in的话,那么如果in的真实类型是 PushbackInputStream的话,只要客户端不需要使用unread()方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就 必须进行向下类型转换

in的类型转换成为PushbackInputStream之后才可能调用这个方法。但是,这个类型转换意味着客户端必须知道它 拿到的引用是指向一个类型为PushbackInputStream的对象。这就破坏了使用装饰者模式的原始用意

现实世界与理论总归是有一段差距的。纯粹的装饰者模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰者模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值