问题引出
如果我们想喝一杯咖啡,而咖啡又分为意大利浓咖啡(Espresso),美式咖啡,无因咖啡,同时又有牛奶、豆浆(别问我为什么加豆浆,加就对了)、巧克力等配料,所以我们的咖啡就有很多种选择,这时程序应该怎么设计?
很蠢的方法:
-
Drink是一个抽象类,表示饮料
-
des是对咖啡的描述,比如说咖啡的名字
-
cost()方法时计算费用
-
咖啡种类+配料,这个组合有很多,每一个组合为一个类
问题:这样设计会有很多的类,相当于一个排列组合,最终会造成类爆炸(就是有很多类的意思),会维护的你怀疑人生
不是那么蠢的方法
- 将配料内置到Drink类中,通过方法设置是否添加配料,这样就不会造成类的数量过多了
问题:在增加或者删除配料种类时,代码的维护量很大
那么重点来了,有没有什么不蠢的方法🐴?
答:那就是装饰者模式
装饰者模式
简介
-
通过委托机制,复用系统中的各个组件,在运行时,可以将这些功能组件进行叠加,构造一个拥有这些组件所有功能的对象
-
动态的将新功能附加到对象上。在对象功能扩展方面,他比继承更加有弹性,同时装饰者模式也体现了开闭原则
注:继承是一种紧密耦合,任何父类都会影响其子类,不利于维护。而委托则是松散耦合,只要接口不变,委托类的改动并不会影响其上层对象
使用装饰者模式解决咖啡问题
// 抽象饮品类
public abstract class Drink {
public String des; // 描述
private float price = 0.0f;
// 计算费用的抽象方法,子类去实现
public abstract float cost();
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 class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
// 具体咖啡类:美式咖啡
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利浓咖啡");
setPrice(10.0f);
}
}
// 具体咖啡类:美式咖啡
public class LongBlack extends Coffee {
public LongBlack() {
setDes("美式咖啡");
setPrice(8.0f);
}
}
// 具体咖啡类:无因咖啡
public class Decaf extends Coffee {
public Decaf() {
setDes("无因咖啡");
setPrice(12.0f);
}
}
// 装饰者类
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) { // 组合
this.obj = obj;
}
@Override
public float cost() {
// getPrice()为自己的价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
return des + " " + getPrice() + "&&" + obj.getDes();
}
}
// 具体的Decorator,这里就是调味品巧克力
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力");
setPrice(3.0f); // 配料的价格
}
}
// 具体的Decorator,这里就是调味品牛奶
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes("牛奶");
setPrice(1.5f);
}
}
// 具体的Decorator,这里就是调味品豆浆
public class Soy extends Decorator {
public Soy(Drink obj) {
super(obj);
setDes("豆浆");
setPrice(1.0f);
}
}
// 客户端,模拟客人点咖啡
public class CoffeeBar {
public static void main(String[] args) {
// 装饰者模式下的订单:两份巧克力+一份牛奶的美式咖啡
// 1、点一份美式咖啡
Drink order = new LongBlack();
System.out.println("订单一:" + order.getDes()+ ",价格为" +order.getPrice());
// 2、加入一份牛奶
order = new Milk(order);
System.out.println("加入一份牛奶过后:" + order.getDes()+ ",价格为" +order.cost());
// 3、加入一份巧克力
order = new Chocolate(order);
System.out.println("加入一份巧克力过后:" + order.getDes()+ ",价格为" +order.cost());
// 3、再加入一份巧克力
order = new Chocolate(order);
System.out.println("再加入一份巧克力过后:" + order.getDes()+ ",价格为" +order.cost());
}
}
// 运行结果
订单一:美式咖啡,价格为8.0
加入一份牛奶过后:牛奶 1.5&&美式咖啡,价格为9.5
加入一份巧克力过后:巧克力 3.0&&牛奶 1.5&&美式咖啡,价格为12.5
再加入一份巧克力过后:巧克力 3.0&&巧克力 3.0&&牛奶 1.5&&美式咖啡,价格为15.5
优势:假如此时我们再加入一个咖啡类,只需要使这个类继承Drink类就可以直接使用了,配料同理
扩展
在JDK的实现中,有不少组件也是利用装饰者模式实现,其中最经典的例子是InputStream和OutputStream类族的实现。以OutputStream为例,OutputStream对象提供的方法比较简单,功能也比较弱,但通过各种装饰者(BufferOutputStream、DataOutputStream等)的增强,OutputStream对象可以被赋予很多强大的功能
其中,OutputStream就相当于我们的咖啡类,FIleOutputStream相当于我们的具体咖啡类,装饰者(BufferOutputStream、DataOutputStream等)相当于我们的配料类