文章优先发表在个人博客:https://www.xdx97.com/article/706183532841533440
一、引入装饰者模式
我们现在有一个卖咖啡的需求。我们有咖啡,我们也有配料(巧克力、牛奶…)。现在我们要设计一个程序计算用户这杯咖啡的价格。(咖啡 + 配料)
1-1:思路一:排列组合
我们把每种组合都列举出来。比如:咖啡+巧克力、咖啡+双份巧克力、咖啡+牛奶 …
这样的确很简单的就达到了我们的需求但是有一个问题就是产生很多的类,当我们新增了一种配料的时候又会新增很多种组合,维护性差。
1-2:思路二:使用类组合
我们在每种咖啡里面定义每个配料的属性,比如我们有一个类:咖啡,在里面把每种配料都作为属性存放进去
public class Coffee{
private String name;
private String price;
private int chocolates;
private int milk;
}
通过上面的例子我们可以看到每次计算价格的时候我们需要判断对应的配料数是否大于0。
上面这种思路的确可以解决类很多的问题,但是我们新增一种配料的时候还是需要去修改之前的类,这不符合设计的OCP原则。
2、使用装饰者模式
装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
当我们需要一个 咖啡 的时候:
- Coffee coffee = new Coffee();
当我们需要一个 咖啡 + 牛奶 的时候:
- Coffee coffee = new Coffee();
- coffee = new Milk(coffee );
当我们需要一个 咖啡 + 牛奶 + 巧克力 的时候:
- Coffee coffee = new Coffee();
- coffee = new Milk(coffee);
- coffee = new Chocolates(coffee);
可能这样并不是那么好理解,下面写代码。当你看完代码再来看上面的图和代码就好理解了。
Drink
@Data
public abstract class Drink {
// 名称
public String name;
// 价格
public float price;
// 返回费用
public abstract float cost();
// 订单描述
public abstract String getDesc();
}
Coffee
public class Coffee extends Drink {
public Coffee(){
setName("咖啡");
setPrice(10f);
}
@Override
public float cost() {
return super.getPrice();
}
@Override
public String getDesc() {
return super.getName() + super.getPrice();
}
}
Decorator
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink){
this.drink = drink;
}
@Override
public float cost() {
return super.getPrice() + drink.cost();
}
@Override
public String getDesc(){
return super.getName() + super.getPrice() + drink.getDesc();
}
}
Milk
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setName("牛奶");
setPrice(2f);
}
}
Chocolates
public class Chocolates extends Decorator {
public Chocolates(Drink drink) {
super(drink);
setName("巧克力");
setPrice(3f);
}
}
测试 (咖啡 + 牛奶 + 巧克力 + 牛奶)
public class Main {
public static void main(String[] args) {
Drink order = new Coffee();
order = new Milk(order);
order = new Chocolates(order);
order = new Milk(order);
System.out.println(order.cost());
System.out.println(order.getDesc());
// 打印结果
// 17.0
// 牛奶2.0巧克力3.0牛奶2.0咖啡10.0
}
}
分析
1、每个咖啡里面都是单独的,它的 cost() 就是返回当前自己的价格,它的 getDesc() 方法就是返回自己名字和单价。
2、每一个装饰者,里面都包含一个Drink,它的 cost() 返回自己的价格 + Drink的价格,它的 getDesc() 方法就是返回自己名字和单价 + Drink名字和单价。(这个Drink是父类,所以它可以代表咖啡、也可以代表配料)
按照上面的分析,我们不管要套多少层都没得关系,反正是一个递归的方式输出嘛。
拓展
思考一下:假如我们的这个咖啡下面还有子类,比如美式咖啡、拿铁咖啡?那么应该怎么扩展呢?
我们只需要让美式咖啡、拿铁咖啡继承咖啡就好了。然后 new 的时候我们不 new 咖啡,new 美式咖啡、拿铁咖啡就好了。