1、星巴克咖啡订单项目
咖啡种类/单品咖啡:
Espresso
(意大利浓咖啡)、ShortBlack
、LongBlack
(美式咖啡)、Decaf
(无因咖啡)
调料:
Milk
、Soy
(豆浆)、Chocolate
要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
使用OO的来计算不同种类咖啡的费用:
- 客户可以点单品咖啡,也可以单品咖啡+调料组合(例如:
Milk+Soy
,1对多)。
桥接是2个维度,一对一。 这个是一对多(一个种类可以对应多个调料),所以可以聚合一个List<>
接口(这里对应 List<调料>
)
2、方案一,问题
-
Drink
是一个抽象类,表示饮料 -
des
就是就是对咖啡的描述,比如咖啡的名字 -
cost()
方法就是计算费用,Drink
类做成一个抽象类 -
Decaf
就是单品咖啡,继承Drink
,并实现cost
-
Espress&&Milk
就是单品咖啡+调料,这个组合很多 -
问题:
- 这样设计,会有很多的类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸(类似排列组合)
3、方案二,问题
前面分析到方案1
- 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,
- 将调料内置到
Drink
类,这样就不会造成类数量过多。 - 从而提高项目的维护性(如图)
- 方案
2
可以控制类的数量,不至于造成很多的类 - 再增加或者删除调料种类时,代码的维护量还是很大
- 考虑到用户可以添加份 调料时,可以将
hasMilk
返回一个对应int
- 考虑使用 装饰者模式
4、基本介绍
装饰者模式:
- 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
这里提到的动态的将新功能附加到对象和ocp原则
5、原理类图
- 装饰者模式就像打包一个快递
- 主体:比如:陶瓷,衣服(
Component
)//被装饰者 - 包装:比如:报纸填充,塑料泡沫,纸板,木板(
Decorator
)//装饰者
- 主体:比如:陶瓷,衣服(
Component
- 主体:比如类似前面的
Drink
- 主体:比如类似前面的
ConcreteComponent
- 具体的主体,比如前面的各个单品咖啡
Decorator
- 装饰者,比如各调料,用调料去装饰咖啡,最终也是主体(咖啡),所以要去继承主体
- 在如图的
Component
与ConcreteComponent
之间,- 如果
ConcreteComponent
类很多,还可以设计一个缓冲层, - 将共有的部分提取出来,抽象层一个类。
- 如果
6、解决
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源码
/*
* 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
)角色:由ByteArrayInputStream
、FileInputStream
、PipedInputStream
、StringBufferInputStream
等类扮演。它们实现了抽象构件角色所规定的接口。 -
抽象装饰(
Decorator
)角色:由FilterInputStream
扮演。它实现了InputStream
所规定的接口。 -
具体装饰(
ConcreteDecorator
)角色:由几个类扮演,分别是BufferedInputStream
、DataInputStream
以及两个不常用到的类LineNumberInputStream
、PushbackInputStream
。
设计模式详解——装饰者模式 - 简书 (jianshu.com)
8、半透明的装饰者模式
装饰者模式和适配器模式都是“包装模式(Wrapper Pattern
)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。
理想的装饰者模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。
装饰者模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。
- 透明的装饰者模式也就是理想的装饰者模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
- 相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰者模式也是可以接受的,称为“半透明”的装饰模式,如下图所示。
在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽。
显然,半透明的装饰者模式实际上就是处于适配器模式与装饰者模式之间的灰色地带。如果将装饰者模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰者模式倒可以成为这种合并后的“包装模式”的代表。
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
的对象。这就破坏了使用装饰者模式的原始用意。
现实世界与理论总归是有一段差距的。纯粹的装饰者模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰者模式。