设计模式的征途—11.外观(Facade)模式
在软件开发中,有时候为了完成一项较为复杂的功能,一个类需要和多个其他业务类交互,而这些需要交互的业务类经常会作为一个完整的整体出现,由于涉及的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由他来负责和多个业务类进行交互,而使用这些业务类的类只需要和该类进行交互即可。外观模式通过引入一个新的外观类来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。
外观模式(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)在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系 => 通过外观类建立联系,降低层与层之间的耦合度!
参考资料
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》
设计模式的征途—11.外观(Facade)模式的更多相关文章
- 设计模式C++描述----14.外观(Facade)模式
一. 举例说明 还以我以前做的文件系统(FileSys)为例: 文件系统是一个独立的系统,它提供一套核心的文件操作. 除了文件系统,还有四个子系统,分别是杀毒子系统(KillVirus),压缩子系统( ...
- 设计模式--外观(Facade)模式
Insus.NET在去年有写过一篇<软件研发公司,外观设计模式(Facade)>http://www.cnblogs.com/insus/archive/2013/02/27/293606 ...
- 设计模式之第11章-建造者模式(Java实现)
设计模式之第11章-建造者模式(Java实现) “那个餐厅我也是醉了...”“怎么了?”“上菜顺序啊,竟然先上甜品,然后是冷饮,再然后才是菜什么的,无语死了.”“这个顺序也有人这么点的啊.不过很少就是 ...
- 外观(Facade)模式
外观模式:为子系统中的一组接口提供一个一致的界面.此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需 ...
- 十一、外观(Facade)模式--结构模式(Structural Pattern)
外部与一个子系统的通信必须通过一个统一的门面(Facade)对象进行,这就是门面模式.门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行. 门面模式提供一个高层次 ...
- 设计模式(十五)Facade模式
Facade模式可以为相互关联在一起的错综复杂的类整理出高层接口,可以让系统对外只有一个简单的接口,而且还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正常的顺序调用各个类. 还是先看一下示例程 ...
- 《图解设计模式》读书笔记7-1 facade模式
目录 1. Facade模式简介 2. 示例程序 2.1 类图 2.2 程序 3.角色和类图 4.思路拓展 1. Facade模式简介 开发程序的过程中,随着时间的推移,类会越来越多,调用关系会越来越 ...
- Head First 设计模式 —— 08. 外观 (Facade) 模式
思考题 想想看,你在 JavaAPI 中遇到过哪些外观,你还希望 Java 能够新增哪些外观? P262 println.log 日志接口.JDBC 接口 突然让想感觉想不出来,各种 API 都用得挺 ...
- 设计模式C++描述----11.组合(Composite)模式
一. 举例 这个例子是书上的,假设有一个公司的组结结构如下: 它的结构很像一棵树,其中人力资源部和财务部是没有子结点的,具体公司才有子结点. 而且最关健的是,它的每一层结构很相似. 代码实现如下: / ...
随机推荐
- php使用curl下载指定大小的文件
php中使用基于libcurl的curl函数,可以对目标url发起http请求并获取返回的响应内容.通常的请求方式类似如下的代码: public function callFunction($url, ...
- 结束《Java编程思想》(Thinking in Java)自学的读后感(2017.10.15)
首先划重点:这是一本Java的入门书. 自学前需要的条件:已经具备基本的计算机基础. 1)已经对一种编程语言比较熟悉: 2)有过四年计算机专业学习,或者三年以上的软件开发经验, 自学的方法: 1)完全 ...
- 【转载】WAI-ARIA无障碍网页应用属性完全展示
文章转载自 张鑫旭-鑫空间-鑫生活 http://www.zhangxinxu.com/wordpress/ 原文链接:http://www.zhangxinxu.com/wordpress/?p=2 ...
- VB6文件操作自定义函数合集之一
'--与文件及文件夹操作相关的函数 '--必须引用FSO的ACTIVE OBJECT Dim strList As String '--列表串,返回文件列表 '================ '-- ...
- PE格式第七讲,重定位表
PE格式第七讲,重定位表 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首先, ...
- pm2用法详解+ecosystem.config
对于后台进程的管理,常用的工具是crontab,可用于两种场景:定时任务和常驻脚本.关于常驻脚本,今天介绍一款更好用的工具:pm2,基于nodejs开发的进程管理器,适用于后台常驻脚本管理,同时对no ...
- intelliJ IDEA安装、激活与汉化
1.去intelliJ IDEA 官网下载idea,选择Ultimate版本(非免费版,community免费但功能较少) 2.开始安装 3.选择好路径 4.选择在桌面创建的快捷方式(注意32bit和 ...
- 优先队列(存储结构数组)--Java实现
/*优先队列--是对队列的一种改进 *要存储的数据存在优先级--数值小的优先级高--在队头 *优先队列的实现 *1.数组:适合数据量小的情况(没有用rear+front实现) *优先队列头在items ...
- jerasure 2.0译文
原文地址: 本文译者水平有限,如发现问题请批评指正 Jerasure 2.0:为方便存储相关应用开发的一个基于C开发的纠删码库 版本2.0 James S.Plank Kevin M.Greenan ...
- java调用oracle数据库发布WebService
package com.hyan.service; import java.io.FileInputStream;import java.sql.Connection;import java.sql. ...