一. 定义
上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题,有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么加码,都还是一个煎饼,在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修,相片加相框,打包一个快递主体:比如:陶瓷、衣服; 包装:比如:报纸填充,塑料泡沫,,都是装饰器模式。
装饰者模式(Decorator Pattern): 不改变现有对象结构给其动态添加一些额外功能,用增加额外功能的具体装饰类包装真实对象.
二. 特点
1. 优点
-
它把每一个要额外添加的功能单独封装在一个个类中,并让这个类包装它所要装饰的对象。
因此,当一个类需要执行特殊行为时,客户端就可以在运行时有选择地添加所需要的附加功能。
-
装饰器是继承的有力补充,比继承灵活,装饰器模式完全符合开闭原则(OCP原则)
2. 缺点
装饰者模式会增加许多子类,过度使用会增加程序的复杂性;
三. 应用场景
1.当要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时,如该类被隐藏或是终极类或使用继承会有大量的类;
2.当要通过对现有的一组基本功能进行排列组合而产生非常多的功能是,采用继承关系很难实现,而采用装饰器模式好实现时;
3.当对象的功能要求可以动态地添加,也可以在动态的撤销时;
四. 模式的结构
通常情况下,扩展一个类的功能会使用继承方式来实现,但继承具有静态特征,耦合度高,并且随着扩展功能的增多,这时使用装饰器模式合适
装饰者模式结构图
装饰器模式角色分析:
Component(抽象被装饰者):定义一个抽象接口来规范准备接收附加功能的对象(抽象构件);
ConcreteComponent(具体被装饰者):继承抽象被装饰者,通过装饰角色为其添加一些职责 (具体构件);
Decorator(抽象装饰器):继承抽象被装饰者,并包含具体构件的实例,可通过其子类扩展具体构件的功能;
ConcreteDecorator(具体装饰器):实现抽象装饰的相关方法,并给具体装饰者对象添加附加的责任;
五. 模式的实现
需求:星巴克咖啡订单项目(咖啡馆)
-
咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、LongBlack(美式咖啡)、Decaf(无因咖啡);
-
调料: Milk(牛奶)、Soy(豆浆)、Chocolate(巧克力);
-
要求在扩展新的咖啡种类时,具有良好的扩展性,改动方便,维护方便;
-
使用面向对象程序设计来计算不同种类咖啡的费用,客户可点单品咖啡,也可点单品咖啡+调料组合;
-
订单需求: 2份巧克力 + 一份牛奶 的LongBlack;
1. 原始方案分析:
1)Drink 是一个抽象类,表示饮料;
2)des: 是对咖啡的描述,比如咖啡的名字;
3)cost():计算费用,Drink类中做成一个抽象方法;
4)Decaf: 单品咖啡,继承Drink,并实现cost()
5)Espresso & Milk: 单品咖啡+调料,组合很多
问题: 这样设计,会有很多的类,当我们增加一个单品咖啡,或者一个新的调料时,类的数量就会倍增,导致类爆炸(不可取)
2.装饰者方案分析
需求: Chocolate(Chocolate(Milk(LongBlack)))//2份巧克力 +一份牛奶的美式咖啡
-
先计算一份牛奶加美式咖啡,装饰者(牛奶 Milk) 包含 被装饰者(美式咖啡 LongBlack);
-
然后添加一份巧克力计算;装饰者(巧克力 Chocolate) 包含 被装饰者(牛奶 Milk+美式咖啡 LongBlack);
-
再添加一份巧克力计算,装饰者(巧克力 Chocolate) 包含 被装饰者 (牛奶 Milk+ 巧克力 Chocolate+美式咖啡 LongBlack)
-
这样不管什么时候单品咖啡+调料组合,通过递归方式可以方便的组合和维护;
1> 实例结构图
2> 相关代码实现
-
需要被装饰的对象所在的类和装饰类都有一个共同的父类Drink,该类中拥有需要添加额外功能的函数:operator() ;
-
Coffe是需要被装饰的类;Decorator是装饰类;
-
Decorator类中持有被装饰对象的引用,客户端通过构造参数(组合),或 setComponent(Component)(聚合)设置;
-
Decorator类的operator()函数中,执行了被用户set进去的component对象中的operator()函数,也就是执行了被装饰类原本的功能;
-
Decorator的子类们的operator()函数,拥有被装饰类原本的功能(方法中调用了被装饰者对应的方法) 和 新需要增加的功能;
//抽象被装饰者
public abstract class Drink {
protected String des;//描述
private float price = 0.0f;
public String getDes() {
return des == null ? "" : des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//计算费用的抽象方法,由子类实现 operator()
public abstract float cost();
}
//具体被装饰者公共部分抽取出的缓冲层
public class Coffee extends Drink{
@Override
public float cost() {
return super.getPrice();
}
}
//无因咖啡(具体被装饰者)
public class Decaf extends Coffee {
public Decaf() {
setDes("无因咖啡");
setPrice(4.0f);
}
}
//意大利咖啡(具体被装饰者)
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
//美式咖啡(具体被装饰者)
public class LongBlack extends Coffee {
public LongBlack() {
setDes("美式咖啡");
setPrice(5.0f);
}
}
//装饰者
public abstract class Decorator extends Drink {
//需要被装饰的对象
private Drink drink;
//提供给客户端将需要被装饰的对象通过构造参设置进来,组合关系
public Decorator(Drink drink) {
this.drink = drink;
}
//提供给客户端将需要被装饰的对象设置进来,聚合关系
public void setDrink(Drink drink){
this.drink = drink;
}
@Override
public float cost() {
//getPrice()自己的价格 + 咖啡价格 新功能
return getPrice() + drink.cost();
}
@Override
public String getDes() {
//输出装饰者信息 自己(调料装饰者信息)+(被装饰者饮品信息) 新功能
return des + getPrice() + " && " + drink.getDes();
}
}
//具体装饰者,这里就是调味品-巧克力
public class Chocolate extends Decorator{
public Chocolate(Drink drink) {
super(drink);
setDes("巧克力");
setPrice(3.0f);//调味品的价格
}
}
//具体装饰者,这里就是调味品-牛奶
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(2.0f);//调味品的价格
}
}
//具体装饰者,这里就是调味品-豆浆
public class Soy extends Decorator{
public Soy(Drink drink) {
super(drink);
setDes("豆浆");
setPrice(1.5f);//调味品的价格
}
}
public class CoffeeBar {
public static void main(String[] args) throws Exception{
//装饰者模式下的订单: 2份巧克力+一份牛奶的LongBlack
//1.点一份 LongBlack
LongBlack longBlack = new LongBlack();
System.out.println("longBlack 描述 = "+longBlack.des);
System.out.println("longBlack 费用 = "+longBlack.cost());
Milk milk = new Milk(longBlack);
System.out.println("milk 描述 = "+milk.getDes());
System.out.println("milk 费用 = "+milk.cost());
Chocolate chocolate = new Chocolate(milk);
System.out.println("chocolate 描述 = "+chocolate.des);
System.out.println("chocolate 费用 = "+chocolate.cost());
Chocolate chocolate1 = new Chocolate(chocolate);
System.out.println("第二份chocolate 描述 = "+chocolate1.des);
System.out.println("第二份chocolate 费用 = "+chocolate1.cost());
}
}
程序运行结果
longBlack 描述 = 美式咖啡
longBlack 费用 = 5.0
milk 描述 = 牛奶2.0 && 美式咖啡
milk 费用 = 7.0
chocolate 描述 = 巧克力
chocolate 费用 = 10.0
第二份chocolate 描述 = 巧克力
第二份chocolate 费用 = 13.0
六. 装饰者模式和建造者模式的异同
1. 相同点
二者都使用了面向切面编程的思想,装饰模式中的装饰类和建造者模式中的建造者类中的函数都为被装饰对象附加函数,或为对象中属性赋值。
2.不同点
建造者给对象舔砖加瓦的顺序是固定的,在Director类中写好的,而装饰模式中需要添加哪些操作是由客户端决定的;
七. 装饰者模式在JDK中的应用
Java的IO结构,FileInputStream就是一个装饰者模式
InputStream(抽象被装饰者)
↗ ↖
FileInputStream FilterInputStream(装饰者类) :为装饰者(类似Decorator),它继承了抽象类InputStream(类似Drink类)
↑ - InputStream in(被装饰的对象)
DataInputStream (子类也继承了被装饰的对象in) :为具体修饰者(类似Milk,Soy),它继承FilterInputStream类
FilterInputStream 类
抽象类 InputStream