1. 概述
在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,使客户类与子系统之间通信和相互依赖关系达到最小,实现的方式之一就是引入一个外观角色,它为子系统的访问提供了一个简单而单一的入口.若没有外观模式,每个客户端可能需要和多个子系统进行复杂的交互,如图(a)所示,而增加一个外观角色后,客户端只需要直接与外观角色交互,降低系统的耦合度,如图(b)所示
注: 在外观模式中所指的子系统是一个广义的概念,它可以是一个类,一个功能模块,系统的一个组成部分或者一个完整的系统.
2. 外观模式
1) 定义: 外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中一组接口提供一个一致的入口,外观模式定义了一个高层接口,该接口使得这一子系统更加容易使用
2) 结构图
3) Facade(外观角色): 客户端调用这个角色的方法,在外观角色中可以知道相关的子系统的功能和责任,一般情况下,它将所有客户端的请求委派到相应的子系统中去,传递给相应的子系统对象处理
4) SubSystem(子系统角色): 每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统中的功能,每一个子系统可以被客户端直接调用,或者被外观角色调用,处理由外观类传来的请求,子系统并不知道外观的存在,对子系统而言外观角色仅仅是另外一个客户端而已
引入外观模式后,增加或删除新的子系统都非常方便,客户端类都几乎不用修改,只需要在外观类中中增加或移除对子系统的引用即可,因此外观模式不符合开闭原则
5) 核心代码
|
class Facade{ private SubSystem obj1 = new SubSystemA(); private SubSystem obj2 = new SubSystemB(); private SubSystem obj3 = new SubSystemC(); public void method() { obj1.mnethod(); obj2.method(); obj3.method(); } } |
3. 案例
2)实现代码
| package com.zach.facade; import java.io.FileInputStream; import java.io.IOException; //文件读取类:子系统类 public class FileReader { public String read(String fileNameSrc) { System.out.println("读取文件,获取明文: "); StringBuffer sb = new StringBuffer(); FileInputStream fis = null; try { fis = new FileInputStream(fileNameSrc); int len= 0; while((len=fis.read())!=-1) { sb = sb.append((char)len); } } catch (Exception e) { // TODO: handle exception }finally { if(fis !=null) { try { fis.close(); System.out.println(sb.toString()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return sb.toString(); } } package com.zach.facade; //数据加密类:子系统类 public class CipherMachine { public String encrypt(String plainText) { System.out.println("数据加密,将明文转换为密文: "); 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; } } //文件保存类:子系统类 public class FileWriter { public void writer(String encryptStr,String fileNameDes){ System.out.println("保存密文,写入文件"); try { FileOutputStream outFS = new FileOutputStream(fileNameDes); outFS.write(encryptStr.getBytes()); outFS.close(); } catch (Exception e) { // TODO: handle exception } } } //加密外观类:外观类 public 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.writer(encryptStr, fileNameDes); } } //客户端 public class Client { public static void main(String[] args) { EncryptFacade ef = new EncryptFacade(); ef.fileEncrypt("facade/src.txt", "facade/des.txt"); } } |
4. 抽象外观类
在原有的外观模式结构图中,如果需要增加,删除,或者更换外观类交互的子系统类,则必须修改外观类或客户端类的源代码,这违背了开闭原则,解决办法是引入一个抽象外观类,客户端针对抽象外观类编程,而在运行时再确定具体外观类;如果Sunny软件公司开发的文件加密模块中需要更换一个加密类,不再使用原有的基于求模运算的加密类CipherMachine,而改为基于位运算的新加密类NewCipherMachine
2. 新加密类的代码:
| //基于位移运算的数据加密类:子系统类 public class NewCipherMachine { public String encrypt(String plainText) { System.out.println("数据加密,将明文转换成密文: "); String es = ""; int key = 10; //设置**,移位数为10 for (int i = 0; i < plainText.length(); i++) { char c = plainText.charAt(i); //小写字母移位 if(c>='a' && c<'z') { c += key % 26; if(c>'z') c -= 26; if(c<'a') c += 26; } //大写字母位移 if(c>='A' && c<='Z'){ c += key % 26; if(c>'Z') c -=26; if(c<'A') c +=26; } es += c; } System.out.println(es); return es; } } |
3. 抽象外观类
|
abstract class AbstractEncryptFacade { public abstract void fileEncrypt(String fileNameSrc,String fileNameDes); } //新加密外观类:具体外观类 class NewEncryptFacade extends AbstractEncryFacade { private FileReader reader; private NewCipherMachine cipher; private FileWriter writer; public NewEncryFacade() { reader = new FileReader(); cipher = new NewCipherMachine(); writer = new FileWriter(); } public void fileEncrypt(String fileNameSrc,String fileNameDesc) { String plainStr = reader.read(fileNameSrc); String encryStr = cipher.encrypt(plainStr); writer.writer(encryptStr,fileNameDes); } } |
4. 引入抽象外观类之后的文件加密模块结构图
5. 外观角色设计注意事项
1) 大多数情况下,系统中只需要一个外观类实例,故可以通过单例模式来设计外观类,从而确保系统中只有唯一一个访问子系统的入口,并降低对系统资源的消耗;结构图如下:
2) 在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向客户端提供相应的业务能力
3) 试图通过外观类为子系统增加新行为的做法是错误的,新行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现
6. 外观模式总结
外观模式使用频率很高,绝大多数B/S系统中首页或者导航页面就是外观角色,C/S系统中的菜单或工具栏就是系统的外观角色,还有JavaEE中的Session也运用了外观模式
1. 主要优点
1) 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数据,简化了代码
2) 实现了子系统与客户端之间的松耦合关系,子系统的变化不会影响客户端
3) 只提供了一个访问子系统的统一入口,并不影响客户端直接使用子系统类
2. 主要缺点
1) 若客户单访问子系统类则减少了可变性和灵活性
2) 设计不当,增加新的子系统可能需要修改外观类的源码
3. 适用场景
1) 当要为访问一系列复杂的子系统提供一个简单入口
2) 客户端程序与多个子系统之间存在很大的依赖性
3) 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系.而是通过外观类建立联系,降低层之间的耦合度