在软件开发中,有时候为了完成一项较为复杂的功能,一个类需要和多个其他业务类交互,而这些需要交互的业务类经常会作为一个完整的整体出现,由于涉及的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由他来负责和多个业务类进行交互,而使用这些业务类的类只需要和该类进行交互即可。外观模式通过引入一个新的外观类来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。

外观模式(Facade) 学习难度:★☆☆☆☆ 使用频率:★★★★★

一、文件加密模块设计

1.1 需求背景

M公司想要开发一个用于多个软件的文件加密模块,该模块可以对文件中的数据进行加密并将加密后的数据存储在一个新文件中,具体的流程包括3个部分,分别是读取源文件、加密、保存加密之后的文件。其中,读取文件和保存文件使用流来实现,加密操作通过求模运算实现。这3个操作相对独立,为了实现代码地独立重用,让设计更加符合单一职责原则,这3个操作的业务代码封装在3个不同的类中。

1.2 初始设计

  M公司开发人员独立实现了这3个具体业务类:FileReader用于读取文件,CipherMachine用于对数据加密,FileWriter用于保存文件。由于该文件加密模块的通用性,它在M公司开发的多款软件中都得以使用,包括财务管理软件、公文审批系统、邮件管理系统等,如下图所示:

  从上图中不难发现,在每一次使用这3个类时都需要编写代码与它们逐个进行交互,客户端代码如下:

    public static void Main()
{
FileReader reader = new FileReader(); // 文件读取类
CipherMachine cipher = new CipherMachine(); // 数据加密类
FileWriter writer = new FileWriter(); // 文件保存类 reader = new FileReader();
cipher = new CipherMachine();
writer = new FileWriter(); string plainStr = reader.Read("Facade/src.txt"); // 读取源文件
string encryptStr = cipher.Encrypt(plainStr); // 加密
writer.Write(encryptStr, "Facade/des.txt"); // 将加密结果写入新文件
}

  经过分析后发现,该方案虽然能够实现预期功能,但存在以下2个问题:

  (1)FileReader、CipherMachie与FileWriter类经常作为一个整体同时出现,但是如果按照上述方案进行设计和实现,在每一次使用这3个类时,客户端代码都需要与它们逐个进行交互,导致客户端代码较为复杂,且在每次使用它们时很多代码都会重复出现。

  (2)如果需要更换一个加密类,例如将CipherMachine改为NewCipherMachine,则所有使用该文件加密模块的代码都需要进行修改,系统维护难度增大,灵活性和可扩展性较差。

二、外观模式概述

2.1 外观模式简介

  根据单一职责原则,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标就是使客户类与子系统之间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观(Facade)角色,它为子系统的访问提供了一个简单而单一的入口。

外观(Facade)模式:外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。  

2.2 外观模式结构与角色

  外观模式没有一个一般化的类图描述,通常使用示意图来表示外观模式,如下图所示:

  当然,下图所示的类图也可以作为外观模式的结构型描述形式之一。

  外观模式主要包含两个角色:

  (1)Facade(外观角色):在客户端可以调用这个角色的方法,在外观角色中可以知道相关的子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统中去,传递给相应的子系统对象处理。

  (2)SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;子系统并不知道外观(又称为门面)的存在,对于子系统而言,外观角色仅仅是另一个客户端而已。

三、重构文件加密模块

3.1 重构后的设计结构

  为了降低系统耦合度,封装与多个子系统进行交互的代码,M公司开发人员使用外观模式来重构文件加密模块的设计,重构后的结构如下图所示:

3.2 重构后的代码实现

  (1)子系统类:FileReader、CipherMachie和FileWriter

    /// <summary>
/// 文件读取类:子系统A
/// </summary>
public class FileReader
{
public string Read(string fileNameSrc)
{
Console.WriteLine("读取文件,获取明文:");
string result = string.Empty;
using (System.IO.FileStream fsRead = new System.IO.FileStream(fileNameSrc, System.IO.FileMode.Open))
{
int fsLen = (int)fsRead.Length;
byte[] heByte = new byte[fsLen];
int r = fsRead.Read(heByte, , heByte.Length);
result = System.Text.Encoding.UTF8.GetString(heByte);
} return result;
}
} /// <summary>
/// 数据加密类:子系统B
/// </summary>
public class CipherMachine
{
public string Encrypt(string plainText)
{
Console.WriteLine("数据加密,将明文转换为密文:");
StringBuilder result = new StringBuilder(); for (int i = ; i < plainText.Length; i++)
{
string ch = Convert.ToString(plainText[i] % );
result.Append(ch);
} string encryptedResult = result.ToString();
Console.WriteLine(encryptedResult);
return encryptedResult;
}
} /// <summary>
/// 文件保存类:子系统C
/// </summary>
public class FileWriter
{
public void Write(string encryptedStr, string fileNameDes)
{
Console.WriteLine("保存密文,写入文件:");
byte[] myByte = System.Text.Encoding.UTF8.GetBytes(encryptedStr);
using (System.IO.FileStream fsWrite = new System.IO.FileStream(fileNameDes, System.IO.FileMode.Append))
{
fsWrite.Write(myByte, , myByte.Length);
}; Console.WriteLine("写入文件成功:100%");
}
}

  (2)外观类:EncrytFacade

    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 encryptedStr = cipher.Encrypt(plainStr);
writer.Write(encryptedStr, fileNameDes);
}
}

  (3)客户端调用:

    public class Program
{
public static void Main(string[] args)
{
EncryptFacade facade = new EncryptFacade();
facade.FileEncrypt("Facade/src.txt", "Facade/des.txt"); Console.ReadKey();
}
}

  这里,src.txt的内容就是一句:Hello World!

  最终运行结果如下图所示:

  

四、二次重构文件加密模块

4.1 新的加密类

  在标准的外观模式实现中,如果需要增加、删除或更换与外观类交互的子系统类,势必会修改外观类或客户端的源代码,这就将违背开闭原则。因此,我们可以引入抽象外观类来对系统进行重构,可以在一定程度上解决该问题。

  假设在M公司开发的文件加密模块中需要更换一个加密类,不再使用原有的基于求模运算的加密类CipherMachine,而改为基于移位运算的新加密类NewCipherMachine,其中NewCipherMachine类的代码如下:

    /// <summary>
/// 新数据加密类:子系统B
/// </summary>
public class NewCipherMachine
{
public string Encrypt(string plainText)
{
Console.WriteLine("数据加密,将明文转换为密文:");
StringBuilder result = new StringBuilder();
int key = ; // 设置密钥,移位数为10 for (int i = ; i < plainText.Length; i++)
{
char c = plainText[i];
// 小写字母位移
if (c >= 'a' && c <= 'z')
{
c += Convert.ToChar(key % );
if (c > 'z')
{
c -= Convert.ToChar();
} if (c < 'a')
{
c += Convert.ToChar();
}
} // 大写字母位移
if (c >= 'A' && c <= 'Z')
{
c += Convert.ToChar(key % );
if (c > 'Z')
{
c -= Convert.ToChar();
} if (c < 'A')
{
c += Convert.ToChar();
}
}
result.Append(c);
} string encryptedResult = result.ToString();
Console.WriteLine(encryptedResult);
return encryptedResult;
}
}

4.2 重构后的设计

  如何在不修改源代码的基础之上使用新的外观类呢?解决办法是:引入一个新的抽象外观类,客户端只针对抽象编程,而在运行时再确定具体外观类。引入抽象外观类之后的设计结构图如下图所示:

  在新的设计中,客户端只针对抽象外观类AbstractEncryptFacade进行编程。

4.3 代码实现

  (1)抽象外观类:AbstractEncryptFacade

    /// <summary>
/// 抽象外观类
/// </summary>
public abstract class AbstractEncryptFacade
{
public abstract void FileEncrypt(string fileNameSrc, string fileNameDes);
}

  (2)新的外观类:NewEncryptFacade

    public class NewEncryptFacade : AbstractEncryptFacade
{
private FileReader reader;
private NewCipherMachine cipher;
private FileWriter writer; public NewEncryptFacade()
{
reader = new FileReader();
cipher = new NewCipherMachine();
writer = new FileWriter();
} public override void FileEncrypt(string fileNameSrc, string fileNameDes)
{
string plainStr = reader.Read(fileNameSrc);
string encryptedStr = cipher.Encrypt(plainStr);
writer.Write(encryptedStr, fileNameDes);
}
}

  (3)配置文件将具体外观类进行配置:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- EncryptFacade Setting -->
<add key="EncryptFacadeName" value="Manulife.ChengDu.DesignPattern.Facade.NewEncryptFacade, Manulife.ChengDu.DesignPattern.Facade" />
</appSettings>
</configuration>

  (4)客户端调用

    public class Program
{
public static void Main(string[] args)
{
AbstractEncryptFacade newFacade = AppConfigHelper.GetFacadeInstance() as AbstractEncryptFacade;
if (newFacade != null)
{
newFacade.FileEncrypt("Facade/src.txt", "Facade/des.txt");
} Console.ReadKey();
}
}

  其中,AppConfigHelper用于读取配置文件的配置并借助反射动态生成具体外观类实例,其代码如下:

    public class AppConfigHelper
{
public static string GetFacadeName()
{
string factoryName = null;
try
{
factoryName = System.Configuration.ConfigurationManager.AppSettings["EncryptFacadeName"];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return factoryName;
} public static object GetFacadeInstance()
{
string assemblyName = AppConfigHelper.GetFacadeName();
Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type);
return instance;
}
}

  最后,运行结果如下图所示:

  

  此时,如果需要再次修改具体外观类,只需要新增一个外观类,并修改配置文件即可,原有代码无须再次修改,符合开闭原则。

五、外观模式小结

5.1 主要优点

  (1)对客户端屏蔽了子系统组件,减少了客户端需要处理的对象数量并且使得子系统使用起来更加容易。

  (2)实现了子系统与客户端之间松耦合。

  (3)提供了一个访问子系统的统一入口,并不影响客户端直接使用子系统。

5.2 应用场景

  (1)想要为访问一系列复杂的子系统提供一个统一的简单入口 => 使用外观模式吧!

  (2)客户端与多个子系统之间存在很大的依赖性,引入外观类可以将子系统和客户端解耦

  (3)在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系 => 通过外观类建立联系,降低层与层之间的耦合度!

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—11.外观(Facade)模式的更多相关文章

  1. 设计模式C++描述----14.外观(Facade)模式

    一. 举例说明 还以我以前做的文件系统(FileSys)为例: 文件系统是一个独立的系统,它提供一套核心的文件操作. 除了文件系统,还有四个子系统,分别是杀毒子系统(KillVirus),压缩子系统( ...

  2. 设计模式--外观(Facade)模式

    Insus.NET在去年有写过一篇<软件研发公司,外观设计模式(Facade)>http://www.cnblogs.com/insus/archive/2013/02/27/293606 ...

  3. 设计模式之第11章-建造者模式(Java实现)

    设计模式之第11章-建造者模式(Java实现) “那个餐厅我也是醉了...”“怎么了?”“上菜顺序啊,竟然先上甜品,然后是冷饮,再然后才是菜什么的,无语死了.”“这个顺序也有人这么点的啊.不过很少就是 ...

  4. 外观(Facade)模式

    外观模式:为子系统中的一组接口提供一个一致的界面.此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需 ...

  5. 十一、外观(Facade)模式--结构模式(Structural Pattern)

    外部与一个子系统的通信必须通过一个统一的门面(Facade)对象进行,这就是门面模式.门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行. 门面模式提供一个高层次 ...

  6. 设计模式(十五)Facade模式

    Facade模式可以为相互关联在一起的错综复杂的类整理出高层接口,可以让系统对外只有一个简单的接口,而且还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正常的顺序调用各个类. 还是先看一下示例程 ...

  7. 《图解设计模式》读书笔记7-1 facade模式

    目录 1. Facade模式简介 2. 示例程序 2.1 类图 2.2 程序 3.角色和类图 4.思路拓展 1. Facade模式简介 开发程序的过程中,随着时间的推移,类会越来越多,调用关系会越来越 ...

  8. Head First 设计模式 —— 08. 外观 (Facade) 模式

    思考题 想想看,你在 JavaAPI 中遇到过哪些外观,你还希望 Java 能够新增哪些外观? P262 println.log 日志接口.JDBC 接口 突然让想感觉想不出来,各种 API 都用得挺 ...

  9. 设计模式C++描述----11.组合(Composite)模式

    一. 举例 这个例子是书上的,假设有一个公司的组结结构如下: 它的结构很像一棵树,其中人力资源部和财务部是没有子结点的,具体公司才有子结点. 而且最关健的是,它的每一层结构很相似. 代码实现如下: / ...

随机推荐

  1. Java web AJAX入门

    一:AJAX简介 AJAX :Asynchronous JavaScript And XML 指异步 JavaScript 及 XML 一种日渐流行的Web编程方式 Better Faster Use ...

  2. JS中的作用域以及全局变量的问题

    一. JS中的作用域 1.全局变量:函数外声明的变量,称为全部变量 局部变量:函数内部使用var声明的变量,称为局部变量在JS中,只有函数作用域,没有块级作用域!!!也就是说,if/for等有{}的结 ...

  3. typescript 的 polyfill 学习1-Class 继承篇

    Class 继承 js 是多范式的编程语言,同样也是支持面向对象编程的,类 是面向对象中是很重要的概念. 区别于传统的java,c#基于模板的类,js是基于原型的. 类继承一般是通过原型链的方式来实现 ...

  4. win10 uwp 使用 Geometry resources 在 xaml

    经常会遇到在 xaml 使用矢量图,对于 svg 的矢量图,一般都可以拿出来写在 Path 的 Data ,所以可以写为资源,但是写出来的是字符串,如何绑定 Geometry 到字符串资源? 假如在资 ...

  5. JPA + SpringData 操作数据库原来可以这么简单 ---- 深入了解 JPA - 2

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7704914.html ------------------------------------ ...

  6. TsBatis 预览

    前言 在发布了 mybatis-dynamic-query之后感觉基本功能稳定了,而且现在在工作项目开发效率大大提高,而且非常易于维护. 最近准备带几个小朋友以前用typescript 打通前后端,当 ...

  7. java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载

    java压缩包上传,解压,预览(利用editor.md和Jstree实现)和下载 实现功能:zip文件上传,后台自动解压,Jstree树目录(遍历文件),editor.md预览 采用Spring+Sp ...

  8. linux命令行下svn常用命令

    linux命令行下svn常用命令 1. 将文件checkout到本地目录 1 #path是服务器上的目录 2 svn checkout path 3 4 #示例 5 svn checkout svn: ...

  9. 【20171027中】alert(1) to win 第13,14,15,16题

    第13题 题目: function escape(s) { var tag = document.createElement('iframe'); // For this one, you get t ...

  10. enum(枚举类型)

    可以使用枚举类型声明代表整数常量的符号名称. 通过enum,创建一个新类型,并指定它可以拥有的值.(就像平常用一个整形变量,我们指定它等于0的时候代表什么,1呢,2呢...而通过枚举,就增加了程序的可 ...