1 问题引出
1.1 咖啡馆订单项目
咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
调料:Milk、Soy(豆浆)、Chocolate
要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。
1 方案一
Drink 是一个抽象类,表示饮料
des 就是对咖啡的描述, 比如咖啡的名字
cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
Decaf 就是单品咖啡, 继承 Drink, 并实现 cost
Espresso && Milk 就是单品咖啡+调料, 这个组合很多
问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
2 方案二
前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
说明: milk,soy,chocolate 可以设计为 Boolean,表示是否要添加相应的调料.
2 定义
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
这里提到的动态的将新功能附加到对象和 ocp 原则
3 原理
装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服 (Component) // 被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
Component 主体:比如类似前面的 Drink
ConcreteComponent:具体的主体, 比如前面的各个单品咖啡
Decorator: 装饰者,比如各调料
如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。
4 问题解决
4.1 图解
说明
- Drink类就是前面说的抽象类,Component
- ShortBlack就单品咖啡
- Decorator是一个装饰类,含有一个被装饰的对象(Drink obj)
- Decorator 的cost方法 进行一个费用的叠加计算,递归的计算价格
说明
- Milk包含了LongBlack
- 一份Chocolate包含了(Milk+LongBlack)
- 一份Chocolate包含了(Chocolate+Milk+LongBlack)
- 这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。
4.2 代码实现
Drink
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;}// 计算费用的抽象方法// 子类来实现public abstract float cost();
}
Chocolate
// 具体的 Decorator, 这里就是调味品
public class Chocolate extends Decorator {public Chocolate(Drink obj) { super(obj);setDes(" 巧克力 ");setPrice(3.0f); // 调味品 的价格}
}
Coffee
public class Coffee extends Drink {
@Overridepublic float cost() {return super.getPrice();}
}
DeCaf
public class DeCaf extends Coffee {public DeCaf() {setDes(" 无因咖啡 "); setPrice(1.0f);}
}
Decorator
public class Decorator extends Drink {private Drink obj;
public Decorator(Drink obj) { // 组合this.obj = obj;}
@Overridepublic float cost() {// getPrice 自己价格return super.getPrice() + obj.cost();}
@Override public String getDes() {// obj.getDes() 输出被装饰者的信息return des + " " + getPrice() + " && " + obj.getDes();}
}
Espresso
public class Espresso extends Coffee {public Espresso() {setDes(" 意大利咖啡 ");setPrice(6.0f);}
}
LongBlack
public class LongBlack extends Coffee {public LongBlack() {setDes(" longblack "); setPrice(5.0f);}
}
Milk
public class Milk extends Decorator {public Milk(Drink obj) { super(obj);setDes(" 牛奶 ");setPrice(2.0f);}
}
ShortBlack
public class ShortBlack extends Coffee{public ShortBlack() {setDes(" shortblack "); setPrice(4.0f);}
}
Soy
public class Soy extends Decorator{public Soy(Drink obj) { super(obj); setDes(" 豆浆 ");setPrice(1.5f);}
}
CoffeeBar
public class CoffeeBar {
public static void main(String[] args) {// TODO Auto-generated method stub// 装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack
// 1. 点一份 LongBlackDrink order = new LongBlack();System.out.println("费用 1=" + order.cost());System.out.println("描述=" + order.getDes());
// 2. order 加入一份牛奶order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入 2 份巧克力 费用 =" + order.cost());System.out.println("order 加入一份牛奶 加入 2 份巧克力 描述 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
}
}
5 优缺点
5.1 优点
- 扩展灵活:使用装饰者模式比继承更加灵活,可以在运行时为对象添加新的行为,无需修改原有代码,符合开闭原则。
- 排列组合:通过使用不同的具体装饰类及其排列组合,设计师可以创造出多种不同行为的组合,从而增加系统的灵活性和可扩展性。
- 低耦合:装饰者与被装饰对象之间是松耦合关系,易于替换和扩展,有利于降低系统各部分之间的依赖关系。
- 避免类爆炸:相较于继承,装饰者模式避免了大量子类的产生,简化了系统的设计。
- 易于撤销:可以随时为对象添加或撤销功能,这在实现一些需要动态调整功能的场景中非常有用。
5.2 缺点
- 多对象生成:使用装饰者模式会产生更多的对象,这些对象如果管理不当,可能会使查错变得困难,尤其是当这些对象看上去都很相似时。
- 复杂性增加:虽然装饰者模式可以减少类的数目,但会增加对象的数量,从而在一定程度上增加了系统的复杂性。
- 过度使用问题:如果过度使用装饰者模式,会导致设计中出现许多小对象,从而使程序变得复杂。
6 应用场景
- 在java.io 包中,定义了大量的类,这些类可以创建流对象以进行读写操作。其中许多类的设计都使用了装饰者模式。例如,将BufferedReader 装饰到 FileReader 上,这样就可以从文件中读取文本了。
- Spring Session 中的装饰者模式 Spring Session 提供了一种透明的方式来支持多种存储方式,如Redis、Memcached等。其核心就是使用装饰者模式来包装Session接口的实现。
- MyBatis缓存机制中的装饰者模式 为了方便用户自定义或扩展缓存机制,MyBatis在其内部通过装饰者模式来实现缓存机制。具体地,MyBatis允许用户提供一个自定义的Cache接口实现,然后通过装饰者包装这个实现,以便在查询结果之前清空缓存。
7 总结
综上所述,装饰者模式是一种结构型设计模式,它允许动态地向一个现有的对象添加新的功能,同时又不改变其结构。这种模式提供了一种灵活且低耦合的方法来扩展对象的功能,但它也可能带来一定的复杂性,因此在使用时应权衡利弊,合理设计。