外观模式是使用频率最高的结构型设计模式之一,无论是在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】