1、 概述
装饰器模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),属于结构型模式。
它是指在不改变原有对象的基础之上,允许向一个现有的对象添加新的功能,同时又不改变其结构,作为现有的类的一个包装。
-
这种模式创建一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
-
提供了比继承更有单性的替代方案,进行扩展原有对象的功能。
-
装饰器模式的核心是功能扩展。使用装饰器模式可以透明且动态地扩展类的功能。
-
装饰器模式主要用于诱明且动态地扩展类的功能。
实现原理
- 让装饰器实现被包装类相同的接口,使得装饰器与被扩展类类型一致
- 并在构造函数中传入该接口对象
- 然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加新功能。
由于装饰器与被包装类属于同一类型,且构造函数的参数为其实现接口类,因此装饰器模式具备嵌套扩展功能,因此就能使用装饰器模式一层层的对最底层被包装类进行功能扩展。
2、 结构
装饰(Decorator)模式中的角色:
- 抽象组件(Component)角色 :抽象组件可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被装饰对象的原始行为。
- 具体组件(Concrete Component)角色 :具体组件实现/继承抽象组件的一个具体对象,即被装饰对象。
- 抽象装饰(Decorator)角色 : 继通用的装饰具体组件的装饰器,其内部必然有一个属性指向抽象组件;其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一个抽象组件,这是强制的通用行为。
如果系统中装饰逻辑单一,并不需要实现许多装饰角色,那么可以直接省略该类,而直接实现一个具体装饰器即可。
- 具体装饰(ConcreteDecorator)角色 :抽象装饰器的具体实现类,理论上,每个具体组件都扩展了抽象组件对象的一种功能。
3、 案例
我们先来看一个手机功能扩展的例子。
现在的老年机大多都只有基本的打电话和发短信基本功能,比如在卖的诺基亚老年机。那如果诺基突然想扩展功能,使老年机成为可以卫星通话的高端智能机。
代码如下:
1、创建抽象组件
创建Phone抽象类:
public abstract class Phone {
public abstract String call();
public abstract double price();
}
2、具体组件
创建最基本具有打电话功能的诺基亚手机:
public class NokiaPhone extends Phone {
@Override
public String call() {
return "普通手机的普通方式打电话";
}
// 普通通话每分钟价格
@Override
public double price() {
return 0.1;
}
}
3、抽象装饰角色
创建一个抽象装饰器来扩展该只具备基本功能的诺基亚手机,让这个电话有卫星通话功能。
public abstract class PhoneDecorator extends Phone {
private Phone phone;
public PhoneDecorator(Phone phone) {
this.phone = phone;
}
@Override
public String call() {
return phone.call();
}
// 普通通话每分钟价格
@Override
public double price() {
return phone.price();
}
// 卫星通话功能
public abstract void satelliteCall();
// 卫星通话每分钟价格
public abstract void satelliteCall();
}
4、具体装饰角色–卫星通话手机
public class SatellitePhoneDecorator extends PhoneDecorator{
public SatellitePhoneDecorator(Phone phone) {
super(phone);
}
// 普通通话
@Override
public String call() {
return "卫星手机普通方式电话";
}
// 普通通话每分钟的价格
@Override
public double price() {
return super.price();
}
// 卫星通话
public void satelliteCall() {
System.out.println("卫星手机的卫星方式打电话");
}
// 卫星通话每分钟的价格,比普通方式贵了100元
public double satelliteCallPrice() {
return super.price() + 100;
}
}
5、客户端调用
public static void main(String[] args) {
// 创建需要被装饰的原始对象(即要被装饰的对象)
Phone phone = new NokiaPhone();
System.out.println("使用: "+ phone.call()+",每分钟单价:" + phone.price());
// 给对象透明的增加额外功能并调用
PhoneDecorator phoneDecorator = new SatellitePhoneDecorator(phone);
System.out.println("使用: "+ phoneDecorator.call()+",每分钟单价:" + phoneDecorator.price());
// 装饰器也可以装饰具体的装饰对象,相当于给对象在增加的功能基础上在添加功能,这里体现在单价100.1基础上再+100
PhoneDecorator satellitePhoneDecorator = new SatellitePhoneDecorator(phoneDecorator);
System.out.println("使用: "+ satellitePhoneDecorator.satelliteCall()+",每分钟单价:" + satellitePhoneDecorator.satelliteCallPrice());
}
结果如下:
使用: 使用普通手机的普通方式打电话,每分钟单价:0.1
使用: 使用卫星手机普通方式电话,每分钟单价:0.1
使用: 使用卫星手机的卫星方式打电话,每分钟单价:100.1
好处:
-
装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加功能,装饰者则是动态的附加功能。
-
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
4、使用场景
-
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将会产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
-
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
-
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
5、JDK源码解析
IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter。
- 首先创建一个FileWriter,没有缓冲区功能
- 然后作为构造函数传入带有缓冲功能的BufferedWriter
- 这样就对FileWriter进行了功能增强
public class Demo {
public static void main(String[] args) throws Exception{
//创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
//创建BufferedWriter对象,并将fw作为构造函数传入
BufferedWriter bw = new BufferedWriter(fw);
//写数据
bw.write("hello Buffered");
bw.close();
}
}
使用起来感觉确实像是装饰者模式,接下来看它们的结构:
小结:
BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。
6、 代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
- 不同点:
- 目的不同
装饰者是为了增强目标对象
静态代理是为了保护和隐藏目标对象 - 获取目标对象构建的地方不同
装饰者是由外界传递进来,可以通过构造方法传递
静态代理是在代理类内部创建,以此来隐藏目标对象
- 目的不同