软件设计模式学习(十三)装饰模式
装饰者模式是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使对象之间的关联关系取代类之间的继承关系。
模式动机
装饰者可以在不改变一个对象本身的基础上给对象增加额外的新行为,如一张照片,可以不改变照片本身,给她增加一个相框,使得它具有防潮功能,而且用户可以根据需要增加不同类型的相框。在软件开发中,类似给照片增加相框的情况随处可见,如给一个图形界面构件增加边框、滚动等新特性。一般有两种方式实现给一个类或对象增加行为:
-
继承机制
通过继承一个类现有类可以使子类在拥有自身方法的同时还拥有父类方法。但这种方式是静态的,用户不能控制增加行为的方式和时机。
-
关联机制
将一个类的对象嵌入另一个新对象中,由另一个对象来决定是否调用嵌入对象的行为并扩展自己的行为,我们称这个新对象(即另一个对象)为装饰类(Dectorator)。
模式定义
动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰者模式比生成子类对象实现更灵活。其别名也可以称为包装器(Wrapper)。
模式结构
-
Component(抽象构件)
抽象构件定义了对象的接口,可以给这些对象动态增加职责(方法)。抽象构件是具体构件和和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法。
-
ConcreteComponent(具体构件)
具体构件定义了具体构件对象,实现在抽象构件中声明的方法。
-
Decorator(抽象装饰类)
抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用调用装饰之前构件对象的方法,并通过子类扩展该方法。
-
ConcreteDecorator(具体装饰类)
具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新行为,它可以调用在抽象装饰类中定义的方法,并增加新的方法以扩充对象的行为。
实例之多重加密系统
某系统提供一个数据加密功能,可以对字符串进行加密。该系统分别提供了简单的移位加密算法、稍复杂的逆向输出加密和更高级的求模加密。用户先使用最简单的移位加密算法对字符串进行加密,如果觉得不够可以对加密后结果进行二次乃至三次加密。
-
抽象构件类 Cipher(抽象加密类)
public interface Cipher { //方法为待加密字符串,返回值为加密后密文 public String encrypt(String plantTetx); }
-
具体构件类 SimpleCipher(简单加密类)
public class SimpleCipher implements Cipher { /* * 以凯撒加密的方式实现加密方法 */ @Override public String encrypt(String plantTetx) { String str = ""; for (int i = 0; i < plantTetx.length(); i++) { char c = plantTetx.charAt(i); if (c >= 'a' && c <= 'z') { c += 6; if (c > 'z') c -= 26; if (c < 'a') c += 26; } if (c >= 'A' && c <= 'Z') { c += 6; if(c > 'Z') c -= 26; if(c < 'A') c += 26; } str += c; } return str; } }
-
抽象装饰类 CipherDecorator(加密装饰类)
public class CipherDecorator implements Cipher { private Cipher cipher; public CipherDecorator(Cipher cipher) { this.cipher = cipher; } @Override public String encrypt(String plantTetx) { // 调用 cipher 对象的 encrypt() 方法 return cipher.encrypt(plantTetx); } }
-
具体装饰类 ComplexCipher(复杂加密类)
public class ComplexCipher extends CipherDecorator { public ComplexCipher(Cipher cipher) { super(cipher); } // 调用了父类的 encrypt() 方法 // 并通过新增的 reserve() 方法对加密后字符串做进一步处理 public String encrypt(String plainText) { String result = super.encrypt(plainText); result = reverse(result); return result; } public String reverse(String text) { String str = ""; for (int i = text.length(); i > 0; i--) { str += text.substring(i - 1, i); } return str; } }
-
具体装饰类 AdvancedCipher(高级加密类)
public class AdvancedCipher extends CipherDecorator { public AdvancedCipher(Cipher cipher) { super(cipher); } // 调用了父类的 encrypt() 方法 // 并通过新增的 mod() 方法对加密后字符串做进一步处理 @Override public String encrypt(String plantTetx) { String result = super.encrypt(plantTetx); result = mod(result); return result; } public String mod(String text) { String str = ""; for (int i = 0; i < text.length(); i++) { String c = String.valueOf(text.charAt(i) % 6); str += c; } return str; } }
-
测试代码 Client
public class Client { public static void main(String[] args) { String password = "sunnyLiu"; //明文 String cpassword; //密文 Cipher sc = new SimpleCipher(); cpassword = sc.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); Cipher cc = new ComplexCipher(sc); cpassword = cc.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); //可以对装饰之后的 cc 对象继续进行装饰 //从而进一步对字符串进行处理,获得更复杂的加密结果 Cipher ac = new AdvancedCipher(cc); cpassword = ac.encrypt(password); System.out.println(cpassword); System.out.println("---------------------"); } }
-
运行结果
模式优缺点
装饰模式优点如下:
- 在扩展对象功能方面,装饰者模式比继承模式更具灵活性
- 可以通过动态的方式扩展对象功能,通过配置文件在运行时选择不同的装饰器,从而实现不同的行为
- 可以使用多个具体装饰类装饰同一对象,得到功能更强大的对象
- 用户根据需要添加新的具体构件类和具体装饰类,原有代码无需改变,符合开闭原则
装饰模式缺点如下:
- 使用装饰者模式进行系统设计将产生很多小对象与装饰类,增加了系统的复杂度
- 程序更加易于出错,排查错误也更加困难
模式适用场景
以下情况可以考虑使用装饰模式
- 在不影响其他对象的情况下,以透明、动态的方式给单个对象添加职责
- 当不能采用继承对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统存在大量独立的扩展,为支持每一种组合将产生大量子类;第二类是因为类不能继承(final 类)
装饰模式的简化
大多数情况下,装饰模式的实现比标准的结构图要简单,可以对装饰模式进行简化。简化过程中要注意如下几个问题:
-
一个装饰类的接口必须与被装饰类接口保持相同。对于客户端来说,无论是装饰之前的对象还是装饰之后的对象都可以同等对待
-
不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类进行扩展
-
如果只有一个具体构件类而没有抽象构件类,那么抽象构件类可以作为具体构件类的子类
如果只有一个具体装饰类,那也就没必要再设计一个单独的抽象装饰类,可以把抽象装饰类和具体装饰类的职责合并在一个类中
透明装饰模式和半透明装饰模式
在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应全部声明为抽象构件类型。如上述加密系统实例就是透明装饰模式
Cipher sc = new SimpleCipher();
Cipher cc = new ComplexCipher(sc);
Cipher ac = new AdvancedCipher(cc);
装饰模式的用意是在不改变接口的前提下增强原有类的功能。在增强功能时用户往往需创建新的方法,如希望直接使用复杂加密算法中的 reverse() 方法,这时就要采用半透明装饰模式
SimpleCipher sc = new SimpleCipher();
ComplexCipher cc = new ComplexCipher(sc);
AdvancedCipher ac = new AdvancedCipher(cc);
Java IO 对装饰模式的应用
这里对 IO 流不再做过多介绍,以 InputStream 和 OutputStream 为例,它们只提供了最简单的流处理方法,只能读入和写出字符,没有缓冲处理、无法处理文件。
InputStream 是所有字节输入流的父类,其中声明的读取以及操作数据方法会被所有字节输入流继承。子类中有一个 FilterInputStream 类,它又包含了一些子类,如用于给一个输入流添加缓冲功能的 BufferedInputStream,用于读取原始类型的数据的 DataInputStream 等。
InputStream 的层次结构对应装饰模式的结构,其中 InputStream 是一个抽象类,它对应装饰模式中的抽象构件类。而 FilterInputStream、ByteArrayInputStream 等都直接继承 InputStream 类,它们实现了在 InputStream 中定义的 read() 方法。FilterInputStream 类也是 InputStream 的子类,对应抽象装饰类的角色。
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
// 构造函数需要传递一个 InputStream 对象的引用
// in 对象可以是任何继承自 InputStream 类型的引用
protected FilterInputStream(InputStream in) {
this.in = in;
}
...
}
BufferInputStream 是 FilterInputStream 的子类,相当于装饰模式中的具体装饰类,对传入的输入流做不同的装饰。BufferInputStream 类提供了一个缓存机制,使用一个数组作为数据读入的缓冲区,并覆盖了父类的 read() 方法,在调用输入流读取数据前都会检查缓存是否已满,实现了对输入流对象动态添加新功能的目的,在此处的新功能即为缓冲控制。
FileInputStream inFS = new FileInputStream("temp/fileSrc.txt");
BufferedInputStream inBS = new BufferedInputStream(inFS);
//定义一个字节数组,用于存放缓冲数据
byte[] data = new byte[1024];
inBS.read(data);
在 Java IO 中,不仅 InputStream 用到了装饰模式,OutputStream、Reader、Writer 等都用到了此模式。