装饰(Decorator)模式又叫包装器(Wrapper)模式。
1. 用意
动态地给一个对象添加一些额外的职责。
2. 参与者
• 抽象构件(Component):定义一个对象接口,可以给这些对象动态地添加职责。
• 具体构件(Concrete Component):定义一个对象,可以给这个对象添加一些职责。
• 抽象装饰者(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
• 具体装饰者(Concrete Decorator):向组件添加职责。
3.结构
在阎宏的设计模式中,提到“装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方法。”
先来理解装饰模式的用意-----------动态地给一个对象添加一些额外的职责。
所谓动态是说可以在系统运行时(RunTime)动态给对象增加其它职责而不需要修改代码或重新编译;
所谓静态是说必须通过调整代码(DesignTime)才能给对象增加职责,而且系统还需要重新编译;
从具体技术层面来说,对象的组合和继承正好对应于前面的动态和静态
我们应该多使用对象组合来保持系统的运行时扩展性,尽量少使继承,因为继承让程序变得僵硬!
这就是优先使用对象组合,而非类继承(Favor composition over inheritance.)。
再来理解一下“装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方法。”这句话。
Decorator是装饰者模式里非常特殊的一个类,它既继承于Component(IS A关系),又维护一个指向Component实例的引用(HAS A关系),换个角度来说,Decorator跟Component之间,既有动态组合关系又有静态继承关系。
这里为什么要这么来设计?
组合的好处是可以在运行时给对象增加职责,Decorator 有一个(HAS A) Component的目的是让具体装饰者(Concrete Decorator)可以在运行时动态给具体构件(Concrete Component)增加职责,这一点相对来说还比较好理解;
那么Decorator继承于Component的目的是什么?
在这里,继承的目的只有一个,那就是可以统一装饰者和被装饰者的接口,换个角度来说,不管是具体构件(Concrete Component)还是具体装饰者(Concrete Decorator),它们都是 Component,用户代码可以把它们统一看作Component来处理,这样带来的更深一层的好处就是,装饰者对象对被装饰者对象的功能职责扩展对用户代码来说是完全透明的,因为用户代码引用的都是Component,所以就不会因为被装饰者对象在被装饰后,引用它的用户代码发生错误,实际上不会有任何影响,因为装饰前后,用户代码引用的都是Component类型的对象。
装饰者模式通过继承实现统一了装饰者和被装饰者的接口,通过组合获得了在运行时动态扩展被装饰者对象的能力。
还是通过例子来说明:
首先,不使用装饰模式,想想怎么实现下面的需求。
1.还是以买汽车为例---顾客要去汽车代理商买汽车,代理商提供了一些服务,刚开始是一些售后维修服务;
2.后来,还提供了零部件供应的服务;然后,又提供了一些咨询服务(代理商解答顾客买车前的一些疑问);最后,又为客户提供了车险的服务。
3.如果代理商需要根据情况,提供不同的服务组合。
(这里就不写代码来实现了)
大多数情况下,都会使用继承,这样,主要会有下面的问题:
1. 系统的扩展性很差;
2. 当需要提供不同的服务组合时,需要创建大量的类,造成类爆炸;
那么,看看装饰模式是怎么实现上面的需求的。Java代码如下:
抽象构件(Component): 汽车销售商
/**
* 抽象构件(Component)
*
* 汽车销售商
*/
public interface CarSeller {
/*
* 销售汽车
*/
String sellCars();
}
具体构件(Concrete Component):奥迪汽车经销商
/**
* 具体构件(Concrete Component)
*
* 奥迪汽车经销商
*/
public class AudiCarAgency implements CarSeller {
/*
* 销售奥迪汽车
*/
public String sellCars() {
System.out.println("销售奥迪汽车");
return "奥迪汽车";
}
}
抽象装饰者(Decorator):提供服务的奥迪销售商
/**
* 抽象装饰者(Decorator)
*
* 提供服务的奥迪销售商
*/
public class AudiCarAgencyWithServices implements CarSeller {
private CarSeller carSeller = null;
/*
* 构造函数
*/
public AudiCarAgencyWithServices(CarSeller carSeller) {
this.carSeller = carSeller;
}
/*
* 装饰
* @see myDecoratorPattern.sellCars.CarSeller#sellCars()
*/
public String sellCars() {
String carName = null;
carName = carSeller.sellCars();
return carName;
}
}
具体装饰者(Concrete Decorator):提供维修服务的奥迪销售商
/**
* 具体装饰者(Concrete Decorator)
*
* 提供维修服务的奥迪销售商
*
*/
public class AudiCarAgencyWithMaintenance extends AudiCarAgencyWithServices {
/*
* 构造函数
*/
public AudiCarAgencyWithMaintenance(CarSeller carSeller) {
super(carSeller);
}
/*
* 添加了其他的服务
*/
public String sellCars(){
String carName = super.sellCars();
System.out.println("提供维修服务");
return carName;
}
}
具体装饰者(Concrete Decorator):提供零配件供应的奥迪销售商
/**
* 具体装饰者(Concrete Decorator)
*
* 提供零配件供应的奥迪销售商
*
*/
public class AudiCarAgencyWithSparepart extends AudiCarAgencyWithServices {
/*
* 构造函数
*/
public AudiCarAgencyWithSparepart(CarSeller carSeller) {
super(carSeller);
}
/*
* 添加了其他的服务
*/
public String sellCars(){
String carName = super.sellCars();
System.out.println("提供零配件供应");
return carName;
}
}
具体装饰者(Concrete Decorator):提供售前咨询服务的奥迪销售商
/**
* 具体装饰者(Concrete Decorator)
*
* 提供售前咨询服务的奥迪销售商
*
*/
public class AudiCarAgencyWithConsultation extends AudiCarAgencyWithServices {
/*
* 构造函数
*/
public AudiCarAgencyWithConsultation(CarSeller carSeller){
super(carSeller);
}
/*
* 添加了其他的服务
*/
public String sellCars() {
System.out.println("提供了售前咨询服务");
return super.sellCars();
}
}
具体装饰者(Concrete Decorator):提供车险服务的奥迪销售商
/**
* 具体装饰者(Concrete Decorator)
*
* 提供车险服务的奥迪销售商
*
*/
public class AudiCarAgencyWithInsurance extends AudiCarAgencyWithServices {
/*
* 构造函数
*/
public AudiCarAgencyWithInsurance(CarSeller carSeller) {
super(carSeller);
}
/*
* 添加了其他的服务
*/
public String sellCars(){
String carName = super.sellCars();
System.out.println("提供车险的购买");
return carName;
}
}
结构如下:
客户端的调用:
public class Customer {
public static void main(String[] args){
String car = null;
System.out.println("--------------只卖车,没有服务------------------");
// 汽车经销商
CarSeller carSeller = new AudiCarAgency();
car = carSeller.sellCars();
System.out.println("买到了" + car);
System.out.println("--------------第一种组合-维修服务------------------");
// 提供了维修服务
CarSeller carSellerWithMaintenance = new AudiCarAgencyWithMaintenance(carSeller);
car = carSellerWithMaintenance.sellCars();
System.out.println("买到了" + car);
System.out.println("--------------第二种组合-添加了零部件供应服务---------");
// 提供了零部件供应
CarSeller carSellerWithSparepart = new AudiCarAgencyWithSparepart(carSellerWithMaintenance);
car = carSellerWithSparepart.sellCars();
System.out.println("买到了" + car);
System.out.println("--------------第三种组合-添加了咨询服务--------------");
// 提供了咨询服务
CarSeller carSellerWithConsultation = new AudiCarAgencyWithConsultation(carSellerWithSparepart);
car = carSellerWithConsultation.sellCars();
System.out.println("买到了" + car);
System.out.println("--------------第四种组合-添加了车险服务--------------");
// 提供了车险
CarSeller carSellerWithInsurance = new AudiCarAgencyWithInsurance(carSellerWithConsultation);
car = carSellerWithInsurance.sellCars();
System.out.println("买到了" + car);
System.out.println("#########################################");
// 还可以有其他的组合方式
System.out.println("还可以尝试其他形式自由的组合。。。。。。");
}
}
运行结果:
--------------只卖车,没有服务------------------
销售奥迪汽车
买到了奥迪汽车
--------------第一种组合-维修服务------------------
销售奥迪汽车
提供维修服务
买到了奥迪汽车
--------------第二种组合-添加了零部件供应服务---------
销售奥迪汽车
提供维修服务
提供零配件供应
买到了奥迪汽车
--------------第三种组合-添加了咨询服务--------------
提供了售前咨询服务
销售奥迪汽车
提供维修服务
提供零配件供应
买到了奥迪汽车
--------------第四种组合-添加了车险服务--------------
提供了售前咨询服务
销售奥迪汽车
提供维修服务
提供零配件供应
提供车险的购买
买到了奥迪汽车
#########################################
还可以尝试其他形式自由的组合。。。。。。
可以看到使用装饰模式,有如下优点:
1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以使用很少的类,创造出很多不同的行为组合。
以上内容部分参照自阎宏的《Java与模式》以及来杯咖啡-装饰者模式(Decorator)实例应用详解
如果仔细观察装饰模式的调用(例如:第二种组合-添加了零部件供应服务)时,感觉装饰模式和代理模式的作用好像一样(都给一个对象添加了额外的功能);甚至,如果比较这两个模式的结构时,好像也没什么差别。那么,它们到底有什么区别呢?
首先,它们的用意是不同的:
代理模式是 控制 对一个对象的访问。
装饰模式是 增加 一个类的职责(功能)。
单从它们的结构来看,
使用代理模式时,在访问对象之前或之后也可以添加一些额外的操作,这一点,装饰模式和代理模式差不多,甚至可以说,这两个模式是一样的,都为一个类(一个对象)添加了其他功能。
但如果仔细体会“控制”和“增加”这两个词汇,可能会发现,
代理模式中,所添加的额外的操作,往往为了确定被访问对象者是否能被访问。
而在装饰模式中,增加的这些功能,不会影响原有对象的操作,被装饰的对象一定会被访问的。