设计模式的征途—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)模式
一. 举例 这个例子是书上的,假设有一个公司的组结结构如下: 它的结构很像一棵树,其中人力资源部和财务部是没有子结点的,具体公司才有子结点. 而且最关健的是,它的每一层结构很相似. 代码实现如下: / ...
随机推荐
- 基于FPGA的肤色识别算法实现
大家好,给大家介绍一下,这是基于FPGA的肤色识别算法实现. 我们今天这篇文章有两个内容一是实现基于FPGA的彩色图片转灰度实现,然后在这个基础上实现基于FPGA的肤色检测算法实现. 将彩色图像转化为 ...
- ZOJ2110 HDU1010 搜索 Tempter of the Bone
传送门:Tempter of the Bone 大意是给一个矩阵,叫你是否可以在给定的可走路径上不重复地走,在最后一秒走到终点. 我用了两个剪枝,且称其为简直001和剪枝002,事实证明001不要都可 ...
- win10 UWP 申请微软开发者
申请微软开发者可以到https://dev.windows.com/zh-cn/programs/join 如果是学生,先去http://www.dreamspark.com/ 如果是英文,点stud ...
- OpenSCAD 建模:相框
下载地址:https://github.com/ZhangGaoxing/openscad-models/tree/master/PhotoFrame 代码: module bottom(){ dif ...
- win10 & Ubuntu16 双系统安装
忽然心血来潮吧,本机在已经安装了win10的背景下,想要再加一个linux系统学习学习,几经波折,终于成功. 博主笔记本里有两块固态,一个250G的装了win10,装的时间不久,镜像是在msdn上下载 ...
- PHP知识大全
--------------------------------------------------------- PHP知识大全 ---------------------------------- ...
- 在项目中集成jetty server
为什么使用jetty 使用 tomcat 开发效率并不是太高,并且在eclipse有时两秒做更新,有时候又得手动去部署显得非常麻烦.折算我们可以使用 jetty server 由于 eclipse开发 ...
- php开发微信公众号获取信息LBS
1.一般的公众号都可以在微信公众平台里面设置自定义菜单和自动回复消息,如果需要获取用户位置,则必须开启 服务器配置,当次功能开启后,微信公众平台的自定义菜单和自动回复则失效. 需要通过接口开发来实现微 ...
- LeetCode 119. Pascal's Triangle II (杨辉三角之二)
Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return [1,3, ...
- 一个让你想到即可做到的web弹窗/层----Layer
Layer layer是一款近年来备受青睐的web弹层组件,她具备全方位的解决方案,致力于服务各水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验. 在与同类组件的比较中,layer总是 ...