装饰者模式:动态的给一个对象添加一些额外的职责。就增加功能来说,它相比生成子类更为灵活
下面通过一个例子来具体了解
早餐店点餐系统:
早点种类:手抓饼、鸡蛋灌饼、煎饼果子
可加配料:生菜、鸡蛋、火腿肠
顾客首先选择饼的种类,然后在选择可加的配料,我们需要描述出顾客选择的早点种类和添加配料名称,及其花费的总价
比较简陋的方案:
首先定义了一个抽象的Food父类,里面定义出一个抽象的cost()方法用于返回消费的总价,用setDescription来描述各个消费商品的名称,使用getDescrption将客户的消费订单打印,后面又将大概的客户消费可能出现情况一一列举出来,比如手抓饼加蛋、煎饼果子加肠等。但是由于这个消费组合情况太多,没有用代码描述,各个消费情况集成Food类,实现cost()抽象方法,使用父类super.getPrice()返回总价,在构造中使用super.setDescriptin()和super.setPrice()分别去设置自己的产品名称及其价格。图中我们可以看到加入有许多种情况,我们需要定义太多的类。真正实现中太过麻烦,每个顾客的组合都要重新定义。稍微好点的方案:
从上图可以看到基本和第一种方案基本类似,只是将配料的种类在父类中体现,以boolean类型修饰是否添加某种配料。如果顾客需要在饼中加入鸡蛋,只需要调用setEgg()方法,egg为true,使用hasEgg()方法判断是否有某种调料,但是就像图中所示,如果增加配料种类,则需要修改父类方法,并且无法添加双份配料。
本章重点--使用装饰者模式解决上述问题
public abstract class Food {
public String description = "";
private int price = 0;
public abstract int cost();
public String getDescription() {
return description + "---" + this.getPrice();
}
public void setDescription(String description) {
this.description = description;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
public class Bing extends Food {
@Override
public int cost() {
return super.getPrice();
}
}
public class JianBingGuoZi extends Bing {
public JianBingGuoZi(){
super.setDescription("煎饼果子");
super.setPrice(6);
}
}
public class JiDanGuanBing extends Bing{
public JiDanGuanBing(){
super.setDescription("鸡蛋灌饼");
super.setPrice(7);
}
}
public class ShouZhuoBing extends Bing {
public ShouZhuoBing(){
super.setDescription("手抓饼");
super.setPrice(5);
}
}
从上面可以看出首先定义一个抽象的food父类,用于设置各种饼的名称及价格,然后定义出一个中间类Bing类继承food类,重写了cost()方法,用于返回单品(不添加任何配料)的价格,各个饼种需要继承Bing类在构造函数中设置的单品的名称及价格,接下来看下装饰者的应用:
public class Decorator extends Food {
Food food;
public Decorator(Food food) {
this.food = food;
}
@Override
public int cost() {
return super.getPrice()+this.food.cost();
}
@Override
public String getDescription() {
return super.getDescription()+" \n "+this.food.getDescription();
}
}
可以看到装饰者代码继承自food类,构造函数中将其带入作为参数,重写cost与getDescription方法,使用迭代的形式将每次添加的配料累加,形成一个循环,将其返回:
public class Egg extends Decorator {
public Egg(Food food) {
super(food);
super.setDescription("鸡蛋");
super.setPrice(1);
}
}
public class Lettuce extends Decorator {
public Lettuce(Food food) {
super(food);
super.setDescription("生菜");
super.setPrice(1);
}
}
public class Sausage extends Decorator {
public Sausage(Food food) {
super(food);
super.setDescription("香肠");
super.setPrice(2);
}
}
各个配料类需要继承自Decorator装饰这类,然后在构造函数中带入food类作为参数,传入父类,并需要设置其价格及其描述
现在来看下测试效果
public class BreakfastTest {
public static void main(String[] args) {
Food jianBingGuoZi = new JianBingGuoZi();
System.out.println("order1 desc: "+jianBingGuoZi.getDescription());
System.out.println("order1 price "+jianBingGuoZi.cost());
System.out.println("``````````````````````````````");
Food food = new ShouZhuoBing();
food = new Egg(food);
food = new Sausage(food);
System.out.println("order2 desc: "+food.getDescription());
System.out.println("order2 price "+food.cost());
}
}
这个例子可以抽象的理解为下图①行new了手抓饼这个对象,然后根据Decorator的属性可以知道,配料中包含有food这个属性,因此②中的鸡蛋实例包含了手抓饼这个food实例,接着③行将鸡蛋+手抓饼作为一个food实例,共同作为香肠的一个属性,继续封装香肠对象,因此最后的food对象包含以下信息:
从上面的代码中我们可以看出使用装饰者后,我们无论需要什么配料都可以累加,假如有一天有了大饼鸡蛋或者配料添加了肉松,只需要按照上面类似加入新的类就可以,相当于在最外层包装上新的一层,不用修改父类或者大量改动已有的代码。
本文代码例子来源:《head-first设计模式》