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

外观模式(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. [scrapy]使用Anaconda来搭建scrapy的运行环境。官方推荐方法。

    1.官方文档推荐. 2.一般情况下多数人使用框架的时候使用的是,安装pywin32,和openssl来搭建scrapy的运行环境.但是由于,在这样搭建环境中会遇到各种各样的问题,诸如:下载的版本有问题 ...

  2. C# TextBlock 上标

    我需要做一个函数,显示 ,但是看起来用 TextBlock 做的不好看. 我用 WPF 写的上标看起来不好看,但是最后有了一个简单方法让他好看. 本文告诉大家如何做一个好看的上标. 一开始做的方法: ...

  3. php实现伪静态的方法

    mod_rewrite是Apache的一个非常强大的功能,它可以实现伪静态页面.下面我详细说说它的使用方法 1.检测Apache是否支持mod_rewrite 通过php提供的phpinfo()函数查 ...

  4. C#使用互斥量(Mutex)实现多进程并发操作时进程间的同步操作(进程同步)

    本文主要是实现操作系统级别的进程同步的代码及测试结果,代码经过测试,可直接使用,也可供参考. 承接上一篇博客的业务场景[C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题]. 随着服务进 ...

  5. 读Zepto源码之Form模块

    Form 模块处理的是表单提交.表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  6. 记录一个Unity播放器插件的开发

    背景 公司最近在做VR直播平台,VR开发我们用到了Unity,而在Unity中播放视频就需要一款视频插件,我们调研了几个视频插件,记录两个,如下: Unity视频插件调研 网上搜了搜,最流行的有以下两 ...

  7. ASP.NET MVC @Html.Label的问题

    在使用@Html.Lable()来显示Model的某一个字符串属性时,如果该字符串中包含".",那么就会在最终呈现时被截掉开头至"."位置的字符.什么原因尚不清 ...

  8. LeetCode 238. Product of Array Except Self (去除自己的数组之积)

    Given an array of n integers where n > 1, nums, return an array output such that output[i] is equ ...

  9. LeetCode 33. Search in Rotated Sorted Array(在旋转有序序列中搜索)

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  10. js实现获取短信验证码倒计时

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...