装饰者模式
1.实例:咖啡订餐项目
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡),ShortBlack,LongBlack(美式咖啡),Decaf(无因咖啡):
- 调料: Milk(牛奶),Soy(豆浆),Chocolate(巧克力)
- 要求: 订单结算用户点的咖啡价格(用户可以单点咖啡,也可以点咖啡+调料的组合 。。比如点一杯美式咖啡+巧克力)
- 注: 最好在扩展新的咖啡种类和调料类的同时,不需要改动过多代码,具有良好的扩展性。
2.解决方法
-
方案1:
-
1. 设计Drink为一个抽象类,表示饮料 2. description就是对咖啡的简介 3. cost()就是计算费用,在Drink中作为一个抽象方法 4. Decaf就是单品咖啡,继承Drink并且实现cost 5. Espresso+Milk 就是单品咖啡+调料,这种组合很多 6. 分析: 这样设计会出现很多的类(类爆炸) 7. 因此: 不提倡使用该种方法
-
方案2:
- 将调料内置入Drink抽象类内,不会造成类的数量过多
- 再使用继承关系,保证了所有的咖啡内部存在若干调味品
- 这种方法可以控制类的数量,不至于造成过多的类(不会引起类爆炸)
- 在增加或者删除调料种类时,代码的维护量过大
- hasXxx调料,作为用户使用调料的开关
方案3
考虑使用装饰者模式解决问题
3.装饰者模式介绍
特点: 动态地将新功能附加到对象上,在对象扩展方便比继承更好,装饰者模式也体现了ocp原则(保证代码的维护性和扩展性)
介绍: 装饰者模式就像给游戏打补丁一样,让它变得越来越强大
角色:
- 主体(被装饰者): Component(比如前面的Drink)
- 具体的主体: ConcreteComponent(比如前面的各种单体咖啡实例)
- 装饰者: Decorator 【在装饰者中关联了被装饰者的抽象类】
- 具体的装饰者: 具体装饰细节【前面的Chocolate,Soy等】
uml
代码演示:
//Drink 待强化(拓展)的抽象类 Component
package com.liz.GOF23.decorate.drink;
public abstract class Drink {
public String des;//对drink的描述
//价格
private float prices = 0.0f;
public void setDes(String des) {
this.des = des;
}
public String getDes() {
return des;
}
public float getPrices() {
return prices;
}
public void setPrices(float prices) {
this.prices = prices;
}
//计算费用
public abstract float cost();
}
.
//抽象层和实现层之间建立的一个缓冲层
public class Coffee extends Drink {
@Override
public float cost() {
return this.getPrices();
}
}
//具体的咖啡实体; ConcreteComponent
public class Espresso extends Coffee {
//创建的时候就给予价格
public Espresso(){
setDes("意大利咖啡");
setPrices(6.0f);
}
}
public class LongBlack extends Coffee {
public LongBlack(){
setDes("longblack");
setPrices(5.0f);
}
}
public class ShortBlack extends Coffee{
public ShortBlack(){
setDes("short black");
setPrices(4.0f);
}
}
//抽象装饰层 Decorator 内部关联了抽象实体类
package com.liz.GOF23.decorate.decorator;
import com.liz.GOF23.decorate.drink.Drink;
//装饰者要实现被装饰者的顶层抽象
public class Decorator extends Drink {
private Drink obj;
//!!!开始组合(体现出组合关系)
public Decorator(Drink obj){
this.obj = obj;
}
@Override
public float cost() {
//!!!自己的价格 + 单品咖啡的价格组合
return getPrices() + obj.cost();
}
@Override
public String getDes() {
//描述(装饰者信息 + 被装饰者信息)
return this.des+" "+this.getPrices()+" "+"&&"+obj.getDes()+obj.getPrices();
}
}
//装饰实例:
//巧克力调味
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力调味品");
setPrices(3.0f);//调味品的价格
}
}
//牛奶调味
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes("牛奶");
setPrices(2.0f);
}
}
//豆浆调味
public class Soy extends Decorator {
public Soy(Drink obj) {
super(obj);
setDes("豆浆");
setPrices(1.5f);
}
}
//使用装饰者模式解决咖啡订单问题
public class CoffeeStore {
//装饰者模式下订单: 2份巧克力 + 一份牛奶 的LongBlack
public static void main(String[] args) {
//1.LongBlack
Drink order = new LongBlack();
System.out.println("单品信息:"+order.getDes()+" 费用:"+order.cost());
//2.加入一份牛奶(装饰者)
order = new Milk(order);
System.out.println("订单1:"+order.getDes()+" 费用:"+order.cost());
//3.加入两份巧克力
//加入第一份
order = new Chocolate(order);
//加入第二份
order = new Chocolate(order);
System.out.println("订单2:"+order.getDes()+" 费用:"+order.cost());
//链式思想
}
4.总结一波
装饰者模式可以做到在不修改任何底层代码的情况下,给对象增加新的方法,拓展类的功能。
优点: 装饰者模式比继承灵活,在不改变原有对象的情况下给对象扩展功能,符合开闭原则。
缺点:
- 装饰模式会导致设计出大量的ConcreteDecorator类【所谓的装饰实体类 类似上面具体的Soy Chocolate等具体强化逻辑】,增加系统的复杂性。
- 对于多次装饰的对象,一旦出现错误,排错繁琐;
5.java源码中的装饰者模式
java中的io流中装饰者模式就体现的淋漓尽致,举个栗子:
DataInputStream dis = new DataInputStream(new FileInputStream("D://xxx.txt"));
结构简图:
FileInputStream 具体的实现类(类似于上述的单品咖啡)
DataInputStream 具体的装饰类(类似于上述的调味品实例)
FilterInputStream 抽象的装饰类(类似于上述的抽象调味品)[含有被装饰者]
InputStream 抽象的实现类(类似于上述的抽象Drink)
在源码中可以看出,FilterInputStream继承了InputStream,同时继承自FilterInputStream的FileInputStream等一系列具体类用来强化流的功能
tip: 装饰抽象类FilterInputStream 继承了抽象主体类InputStream,同时内部组合了InputStream,用来强化主题的功能(FilterInputStream内部有各种具体的装饰类: bufferinputStream DataInputStream 等…)