一、星巴克订单案例
有下面的需求:
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合
二、咖啡案例的第一个方案
设计一个Drink抽象类,表示饮料,然后des就是对咖啡的描述,比如咖啡的名字。cost()方法就是计算费用,在Drink类中做成一个抽象方法。Decaf就是单品咖啡,继承Drink,并实现cost(),Espress&&Milk就是单品咖啡+调料,这个组合很多,详情看下图:
这样设计的话有一个很大的问题就是,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸。
三、咖啡案例的第二个方案
前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性,如下图所示:
但是这个方案也有问题,比如在增加或删除调料种类时,代码的维护量很大。
对于这个案例我们可以使用装饰者模式来进行改进。
四、装饰者模式
1、装饰者模式的定义
- 装饰者模式就是动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则。
2、装饰者模式的原理
装饰者模式就像打包一个快递
- 主体(被装饰者)比如:陶瓷、衣服
- 包装(装饰者)比如:报纸填充、塑料泡沫、纸板、木板
如下图所示:
- Component:主体,比如类似咖啡案例中的Drink
- ConcreteComponent和Decorator
ConcreteComponent:具体的主题,比如前面的各个单品咖啡
Decorator:装饰者,比如各调料 - 在图中的Component与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将总有的部分提取出来,抽象层是一个类。
五、用装饰者模式实现咖啡案例
1、案例类关系图
在这个案例之中,类与类之间的关系如下:
- Drink类就是前面说的抽象类(被装饰者)
- ShortBlack等就是单品咖啡
- Decorator是一个装饰者,含有一个被装饰的对象(Drink drink)
- Decorator的cost方法进行一个费用的叠加计算,递归的计算价格
比如点:2份巧克力+1份牛奶的LongBlack,那么关系图如下:
这图表明了:
- Milk包含了一个LongBlack
- 1份Chocolate包含了(Milk+LongBlack)
- 再来1份Chocolate包含了(Chocolate+Milk+LongBlack)
这样的话不管是什么形式的单品咖啡+调料组合,都能通过递归方式方便的组合和维护。
2、案例代码实现
(1)定义被装饰者
先定义一个抽象的被装饰者Drink类:
package com.cxc.decorator;
public abstract class Drink {
public String des; //描述
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
/**
* 计算费用的抽象方法
* @return
*/
public abstract float cost();
}
coffee类:
package com.cxc.decorator;
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
三个单品咖啡类:
package com.cxc.decorator;
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
package com.cxc.decorator;
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
package com.cxc.decorator;
public class ShortBlack extends Coffee {
public ShortBlack() {
setDes("shortblack");
setPrice(4.0f);
}
}
(2)定义装饰者
定义装饰者
package com.cxc.decorator;
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink) { //组合
this.drink = drink;
}
@Override
public float cost() {
//getPrice() : 自己的价格
//drink.cost() : 单品咖啡的价格
return getPrice() + drink.cost();
}
@Override
public String getDes() {
//装饰者描述+装饰者价格+被装饰者描述
return des + " " + getPrice() + " && " + drink.getDes();
}
}
定义具体的装饰者子类(调料类):
package com.cxc.decorator;
/**
* 具体的装饰者,在这里是调料
*/
public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDes(" 巧克力 ");
setPrice(3.0f); //调味品价格
}
}
package com.cxc.decorator;
public class Soy extends Decorator {
public Soy(Drink drink) {
super(drink);
setDes("豆浆");
setPrice(1.5f);
}
}
package com.cxc.decorator;
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(2.0f);
}
}
主方法:
package com.cxc.decorator;
public class CofferBar {
public static void main(String[] args) {
//1.点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用1="+order.cost());
System.out.println("描述="+order.getDes());
//2.加入一份牛奶
order = new Milk(order);
System.out.println("加入一份牛奶费用="+order.cost());
System.out.println("加入一份牛奶描述="+order.getDes());
//3.加一份巧克力
order = new Chocolate(order);
System.out.println("加入一份牛奶再加一份巧克力费用="+order.cost());
System.out.println("加入一份牛奶再加一份巧克力描述="+order.getDes());
//4.又加一份巧克力
order = new Chocolate(order);
System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力费用="+order.cost());
System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力描述="+order.getDes());
}
}
结果如下:
这样改进后,当我们需要再添加一个新的单品咖啡,只需要扩展一个新的单品咖啡类然后去继承coffee即可;添加新的调料也是扩展新的调料类然后去继承装饰者即可。
六、JDK源码使用到装饰者模式的场景:IO流
Java的IO结构,FilterInputStream就是一个装饰者:
- InputStream是一个抽象类,类似我们前面讲的Drink。
- FileInputStream等是InputStream子类,类似我们前面的LongBlack。
- FilterInputStream是InputStream子类,类似我们前面的Decorator装饰者。
- DataInputStream是FilterInputStream子类,具体的装饰者,类似前面的Mile,Soy等。
- FilterInputStream类有 InputStream成员变量,即含有被装饰者。
由上面的分析,可以得出其用的就是装饰者模式。