装饰模式
定义
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
在软件开发中,往往会有这样一种需求,我们需要在不改变原系统代码的时候,给一个类增加一个新的功能或特性,而Java中单继承的特性往往会限制我们对原代码的拓展。采用装饰模式可以使用非继承的方式且不改变原系统的情况下拓展新的功能/特性。
UML图
装饰者模式是一种比较容易理解的设计模式。它的UML图如下:
装饰者模式有以下几个角色:
- Component(抽象构建):它是具体构建与抽象装饰类的共同父类,定义一个统一的规范,使客户端以一致的方法处理装饰前后的对象。
- ConcreteComponent(具体构建):抽象构建的具体实现,需要被装饰的对象。
- Decorator(抽象装饰类):它也是抽象构建的字类,它用作给具体构建增加新的功能/特性,但是具体增加方法由它的字类实现。它维持一个抽象构建的引用,通过该引用调用未装饰前具体构建的方法。
- ConcreteDecorator(具体装饰类):抽象装饰类的子类,实现向具体构建新增功能/特性。
我觉得装饰模式的核心就是 Decorator(抽象装饰类) ,它通过一个持有一个抽象构建的引用来实现对具体构建的调用,且让子类可以新增方法特性。
代码
//抽象构建
public abstract class Component {
public abstract void operation();
}
//具体构建
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
//抽象装饰类 核心
public class Decorator extends Component {
Component component; //持有一个抽象构建的引用
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
//具体的装饰类
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
methodA();
}
public void methodA(){
System.out.println("ConcreteDecoratorA methodA");
}
}
......
客户端调用
public class Client {
public static void main(String[] args) {
Component concreteComponent, concreteDecoratorA, concreteDecoratorB;
concreteComponent = new ConcreteComponent();
concreteComponent.operation();
System.out.println("----------------------------");
concreteDecoratorA = new ConcreteDecoratorA(concreteComponent);
concreteDecoratorA.operation();
System.out.println("----------------------------");
concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);
concreteDecoratorB.operation();
System.out.println("----------------------------");
}
}
客户端调用结果:
ConcreteComponent operation
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
----------------------------
ConcreteComponent operation
ConcreteDecoratorA methodA
ConcreteDecoratorB methodB
----------------------------
从上面可以看到,我在客户端分别创建了三个Component的子类,第一个是具体构建类,第二个是具体装饰类A,持有具体构建类的引用,第三个是具体装饰类B,持有具体装饰类A的引用,调用同一个方法后,可以从输出结果中看到,每一个装饰类都实现了被装饰类的方法同时可以装饰自己的方法。
实例
老规矩,还是用一个实例来演练一番。
某软件公司欲开发了一个数据加密模块,可以对字符串进行加密。最简单的加密算法通过对字母进行移位来实现,同时还提供了稍复杂的逆向输出加密,还提供了更为高级的求模加密。用户先使用最简单的加密算法对字符串进行加密,如果觉得还不够可以对加密之后的结果使用其他加密算法进行二次加密,当然也可以进行第三次加密。试使用装饰模式设计该多重加密系统。
分析需求,字母位移加密是原始的加密方法,其他算法的二次三次加密都是对原加密算法对装饰。
UML图
根据需求绘制UML图如下
基本上都是套用定义的UML图,根据UML图可以很清楚的看清整个软件架构。
代码
// 抽象构建
public abstract class EncryptComponent {
abstract String encrypt(String str);
}
//原始加密算法
public class OriginalEncrypt extends EncryptComponent {
@Override
String encrypt(String str) {
System.out.println("对字符串 \'"+str+"\' 使用原始加密 =====> 原始加密结果");
String encryptStr = "原始加密结果";
return encryptStr;
}
}
//抽象装饰类
public class EncryptDecorator extends EncryptComponent {
EncryptComponent encryptComponent;
public EncryptDecorator(EncryptComponent encryptComponent) {
this.encryptComponent = encryptComponent;
}
@Override
String encrypt(String str) {
return encryptComponent.encrypt(str);
}
}
//另一种加密算法A
public class OtherAEncrypt extends EncryptDecorator {
public OtherAEncrypt(EncryptComponent encryptComponent) {
super(encryptComponent);
}
@Override
String encrypt(String str) {
return otherAEncrypt(super.encrypt(str));
}
public String otherAEncrypt(String str){
System.out.println("对字符串 \'"+str+"\' 使用OtherA加密 =====> OtherA加密结果");
return "OtherA 加密结果";
}
}
//另一种加密算法B 代码类似 可以到我的git上去clone
...
客户端测试
public class Client {
public static void main(String[] args) {
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
String result;
originalEncrypt = new OriginalEncrypt();
result = originalEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");
otherAEncrypt = new OtherAEncrypt(originalEncrypt);
result = otherAEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(originalEncrypt);
result = otherBEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");
otherBEncrypt = new OtherBEncrypt(otherAEncrypt);
result = otherBEncrypt.encrypt("初始数据");
System.out.println("-----------------------------------------------");
}
}
结果
对字符串 '初始数据' 使用原始加密 =====> 原始加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密 =====> 原始加密结果
对字符串 '原始加密结果' 使用OtherA加密 =====> OtherA加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密 =====> 原始加密结果
对字符串 '原始加密结果' 使用OtherB加密 =====> OtherB加密结果
-----------------------------------------------
对字符串 '初始数据' 使用原始加密 =====> 原始加密结果
对字符串 '原始加密结果' 使用OtherA加密 =====> OtherA加密结果
对字符串 'OtherA 加密结果' 使用OtherB加密 =====> OtherB加密结果
-----------------------------------------------
相信大部分同学都可以猜到结果及逻辑处理过程。
简化
其实当系统中具体构建只有一个的时候,我们可以省略抽象构建,让具体构建同时担任这两个角色,如下:
如图,可以简化系统的复杂度,去处冗余的代码。具体代码就不贴了,相信对于大家应该不难理解。
半透明装饰模式
通过上面的代码,相信大部分同学都会感觉有点熟悉,这种一个类包裹另一个类的套路像不像JAVA 数据IO 操作。
FileInputStream fileInputStream = new FileInputStream("DecoratorPattern/test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
的确,JAVA的IO操作就是装饰模式的一个具体实现,在JAVA中装饰模式是使用十分频繁的一种设计模式。
但是,大家有没有发现这里的装饰模式与我们前面的例子有什么不同地方呢?
在上面的例子中,我们在客户端声明实例时
EncryptComponent originalEncrypt, otherAEncrypt, otherBEncrypt;
所有的具体构建或者具体装饰都是以抽象构建来定义,因为通过UML图我们知道它们都是抽象构建的子类。这种对于客户端是而言是完全针对抽象编程,也就是透明装饰模式
但是所有的具体构建或者具体装饰都以抽象构建来定义会导致一个问题,它们都只能调用抽象构建中定义的方法,而在实际开发中,我们往往需要单独使用具体装饰类中的方法,这个时候使用抽象构建来定义具体装饰类就不合适了。
当我们需要单独使用具体装饰类中的方法时,我们就需要单独以具体装饰类来定义声明,这种就是半透明的装饰模式。
小结
装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。对于拓展一个对象的功能,装饰模式比继承更加灵活。通过动态的方式来拓展一个对象的功能,且可以进行多次不同的装饰。
在实际开发中,透明装饰模式的设计难度较大,而且使用不够灵活。而半透明装饰模式可以给系统带来更大的灵活性,且设计相对简单。