外观模式是使用频率最高的结构型设计模式之一,无论是在Web应用软件或是桌面应用软件,还是在移动应用软件中,外观模式都得到了广泛的应用。
外观模式要求外部与一个子系统的通信可以通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的入口,它定义了一个高层接口,这个接口使得相关子系统更加容易使用。如果没有外观角色,每个客户端可能需要和多个子系统之间进行复杂的交互,系统的耦合度将很大,如图1(A)所示;而增加一个外观角色之后,客户端只需要直接与外观角色交互,客户端与子系统之间原有的复杂关系由外观角色来实现,从而降低了系统的耦合度,如图1(B)所示。在图1(B)中,Facade表示一个外观角色,在客户端可以调用这个角色的方法,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
GoF外观模式类图可简化为图2所示结构:
图2 GoF外观模式简化类图
【严格地说,外观模式没有一个统一的类图,图2也只能认为是外观模式的一种结构示意图。】
在外观模式结构图中包含如下几个角色:
● Facade(外观角色):在客户端可以调用这个角色的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
● Subsystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
下面通过一个简单实例来进一步说明外观模式及其用途:
某系统需要提供一个文件加密模块,可以对文件中的数据进行加密并将加密之后的数据存储在一个新文件中,具体的流程包括三个部分,分别是读取源文件、加密、保存加密之后的文件,其中,读取文件和保存文件使用流来实现,加密操作通过求模运算实现。这三个操作相对独立,为了实现代码的独立重用,让设计更符合单一职责原则,这三个操作的业务代码封装在三个不同的类中。现使用外观模式对该文件加密模块进行设计,得到初始设计方案如图3所示:
图3 文件加密模块初始设计方案结构图
在图3中,EncryptFacade充当外观类,FileReader、CipherMachine和FileWriter充当子系统类。完整代码如下所示:
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- //文件读取类:子系统类
- class FileReader {
- public String read(String fileNameSrc) {
- System.out.print("读取文件,获取明文:");
- StringBuffer sb = new StringBuffer();
- try{
- FileInputStream inFS = new FileInputStream(fileNameSrc);
- int data;
- while((data = inFS.read())! = -1) {
- sb = sb.append((char)data);
- }
- inFS.close();
- System.out.println(sb.toString());
- }
- catch(FileNotFoundException e) {
- System.out.println("文件不存在!");
- }
- catch(IOException e) {
- System.out.println("文件操作错误!");
- }
- return sb.toString();
- }
- }
- //数据加密类:子系统类
- class CipherMachine {
- public String encrypt(String plainText) {
- System.out.print("数据加密,将明文转换为密文:");
- String es = "";
- for(int i = 0; i < plainText.length(); i++) {
- String c = String.valueOf(plainText.charAt(i) % 7);
- es += c;
- }
- System.out.println(es);
- return es;
- }
- }
- //文件保存类:子系统类
- class FileWriter {
- public void write(String encryptStr,String fileNameDes) {
- System.out.println("保存密文,写入文件。");
- try{
- FileOutputStream outFS = new FileOutputStream(fileNameDes);
- outFS.write(encryptStr.getBytes());
- outFS.close();
- }
- catch(FileNotFoundException e) {
- System.out.println("文件不存在!");
- }
- catch(IOException e) {
- System.out.println("文件操作错误!");
- }
- }
- }
- //加密外观类:外观类
- class EncryptFacade {
- //维持对其他对象的引用
- private FileReader reader;
- private CipherMachine cipher;
- private FileWriter writer;
- public EncryptFacade() {
- reader = new FileReader();
- cipher = new CipherMachine();
- writer = new FileWriter();
- }
- //调用其他对象的业务方法
- public void fileEncrypt(String fileNameSrc, String fileNameDes) {
- String plainStr = reader.read(fileNameSrc);
- String encryptStr = cipher.encrypt(plainStr);
- writer.write(encryptStr,fileNameDes);
- }
- }
编写如下客户端测试代码:
- class Client {
- public static void main(String args[]) {
- EncryptFacade ef = new EncryptFacade();
- ef.fileEncrypt("facade/src.txt","facade/des.txt");
- }
- }
编译并运行程序,输出结果如下:
读取文件,获取明文:Hello world! 数据加密,将明文转换为密文:233364062325 保存密文,写入文件。 |
在本实例中,对facade文件夹下的文件src.txt中的数据进行加密,该文件内容为“Hello world!”,加密之后将密文保存到facade文件夹下的另一个文件des.txt中,程序运行后保存在文件中的密文为“233364062325”。在加密类CipherMachine中,采用求模运算对明文进行加密,将明文中的每一个字符除以一个整数(本例中为7,可以由用户设置)后取余数作为密文。
作为最常用的设计模式之一,外观模式具有以下优点:
(1) 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目并使得子系统使用起来更加容易;
(2) 实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可;
(3) 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
【作者:刘伟 http://blog.csdn.net/lovelion】
在通用的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则,因此我们可以通过引入抽象外观类来对系统进行改进,在一定程度上解决该问题。在引入抽象外观类之后,客户端可以针对抽象外观类进行编程,对于新的业务需求,不需要修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改任何源代码并更换外观类的目的。引入抽象外观类的外观模式结构如图4所示:
图4 引入抽象外观类的外观模式结构图
如果需要将图3中的加密类CipherMachine改为另一个加密类,例如NewCipherMachine,势必会导致外观类EncryptFacade源代码发生修改,违反开闭原则。通过引入抽象外观类,重构后的系统设计方案如图5所示:
图5 引入抽象外观类之后的文件加密模块结构图
在图5中,客户类Client针对抽象外观类AbstractEncryptFacade进行编程,可以将具体外观类类名存储在XML等格式的配置文件中,如下代码所示:
- <?xml version="1.0"?>
- <config>
- <className>EncryptFacade</className>
- </config>
引入抽象外观类之后,客户类针对抽象外观类编程,更换具体外观类时只需修改配置文件,无须修改源代码,符合开闭原则。
【作者:刘伟 http://blog.csdn.net/lovelion】
由于外观类维持了对多个子系统类的引用,外观对象在系统运行时将占用较多的系统资源,因此需要对外观对象的数量进行限制,避免系统资源的浪费。可以结合单例模式对外观类进行改进,将外观类设计为一个单例类。通过对外观模式单例化,可以确保系统中只有唯一一个访问子系统的入口,降低系统资源的消耗。单例化之后的外观模式结构如图6所示:
图6 单例外观类结构图
在图6中,外观类Facade被设计为单例类,在其中定义了一个静态的Facade类型的成员变量instance,其构造函数为私有的(private),通过一个静态的公有工厂方法getInstance()返回自己的唯一实例。
实例改进:
为了节省系统资源,可以将图3中的EncryptFacade设计为单例类,改进之后的结构图如图7所示:
图7 单例化改进之后的文件加密模块结构图
【在图7中,我采用UML衍型(Stereotype)对模式角色进行了标注,大家也可以这么标注结构图中的模式信息,挺方便的,】
在图7中,EncryptFacade类的实现代码如下:
- public class EncryptFacade {
- private static EncryptFacade instance = new EncryptFacade();
- private FileReader reader;
- private CipherMachine cipher;
- private FileWriter writer;
- private EncryptFacade() {
- reader = new FileReader();
- cipher = new CipherMachine();
- writer = new FileWriter();
- }
- public void fileEncrypt(String fileNameSrc, String fileNameDes) {
- String plainStr = reader.read(fileNameSrc);
- String encryptStr = cipher.encrypt(plainStr);
- writer.write(encryptStr,fileNameDes);
- }
- public static EncryptFacade getInstance() {
- return instance;
- }
- }
在上述代码中使用饿汉式单例实现EncryptFacade类,确保系统中有且仅有一个EncryptFacade类的实例,从而避免生成多个EncryptFacade对象,节约系统资源,提高程序性能。
在以下情况下可以考虑使用外观模式:
(1) 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
(2) 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
(3) 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。【下次再结合Struts等框架的设计,写一篇文章来专门讨论这种情况,】
【作者:刘伟 http://blog.csdn.net/lovelion】