#外觀模式(Facade pattern)
##簡介
外觀模式(Facade pattern),是軟件工程中常用的一種軟件設計模式,它為子系統中的一組接口提供一個統一的高層接口,使得子系統更容易使用。
##模式實現
某軟件公司欲開發一個可應用于多個軟件的文件加密模塊,該模塊可以對文件中的數據進行加密并將加密之后的數據存儲在一個新文件中,具體的流程包括三個部分,分別是讀取源文件、加密、保存加密之后的文件,其中,讀取文件和保存文件使用流來實現,加密操作通過求模運算實現。這三個操作相對獨立,為了實現代碼的獨立重用,讓設計更符合單一職責原則,這三個操作的業務代碼封裝在三個不同的類中。
現使用外觀模式設計該文件加密模塊。
(1) FileReader:文件讀取類,充當子系統類。
```
//FileReader.cs
using System;
using System.Text;
using System.IO;
namespace FacadeSample
{
class FileReader
{
public string Read(string fileNameSrc)
{
Console.Write("讀取文件,獲取明文:");
FileStream fs = null;
StringBuilder sb = new StringBuilder();
try
{
fs = new FileStream(fileNameSrc, FileMode.Open);
int data;
while((data = fs.ReadByte())!= -1)
{
sb = sb.Append((char)data);
}
fs.Close();
Console.WriteLine(sb.ToString());
}
catch(FileNotFoundException e)
{
Console.WriteLine("文件不存在!");
}
catch(IOException e)
{
Console.WriteLine("文件操作錯誤!");
}
return sb.ToString();
}
}
}
```
(2) CipherMachine:數據加密類,充當子系統類。
```
//CipherMachine.cs
using System;
using System.Text;
namespace FacadeSample
{
class CipherMachine
{
public string Encrypt(string plainText)
{
Console.Write("數據加密,將明文轉換為密文:");
string es = "";
char[] chars = plainText.ToCharArray();
foreach(char ch in chars)
{
string c = (ch % 7).ToString();
es += c;
}
Console.WriteLine(es);
return es;
}
}
}
```
(3) FileWriter:文件保存類,充當子系統類。
```
//FileWriter.cs
using System;
using System.IO;
using System.Text;
namespace FacadeSample
{
class FileWriter
{
public void Write(string encryptStr,string fileNameDes)
{
Console.WriteLine("保存密文,寫入文件。");
FileStream fs = null;
try
{
fs = new FileStream(fileNameDes, FileMode.Create);
byte[] str = Encoding.Default.GetBytes(encryptStr);
fs.Write(str,0,str.Length);
fs.Flush();
fs.Close();
}
catch(FileNotFoundException e)
{
Console.WriteLine("文件不存在!");
}
catch(IOException e)
{
Console.WriteLine(e.Message);
Console.WriteLine("文件操作錯誤!");
}
}
}
}
```
(4) EncryptFacade:加密外觀類,充當外觀類。
```
// EncryptFacade.cs
namespace FacadeSample
{
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.Write(encryptStr, fileNameDes);
}
}
}
```
(5) Program:客戶端測試類
```
//Program.cs
using System;
namespace FacadeSample
{
class Program
{
static void Main(string[] args)
{
EncryptFacade ef = new EncryptFacade();
ef.FileEncrypt("src.txt", "des.txt");
Console.Read();
}
}
}
```
###結果及分析
```
讀取文件,獲取明文:Hello world!
數據加密,將明文轉換為密文:233364062325
保存密文,寫入文件。
```
##抽象外觀類
在標準的外觀模式結構圖中,如果需要增加、刪除或更換與外觀類交互的子系統類,必須修改外觀類或客戶端的源代碼,這將違背開閉原則,因此可以通過引入抽象外觀類來對系統進行改進,在一定程度上可以解決該問題。在引入抽象外觀類之后,客戶端可以針對抽象外觀類進行編程,對于新的業務需求,不需要修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置文件來達到不修改任何源代碼并更換外觀類的目的。
下面通過一個具體實例來學習如何使用抽象外觀類:
如果在應用實例“文件加密模塊”中需要更換一個加密類,不再使用原有的基于求模運算的加密類CipherMachine,而改為基于移位運算的新加密類NewCipherMachine,其代碼如下:
```
using System;
namespace FacadeSample
{
class NewCipherMachine
{
public string Encrypt(string plainText)
{
Console.Write("數據加密,將明文轉換為密文:");
string es = "";
int key = 10;//設置密鑰,移位數為10
char[] chars = plainText.ToCharArray();
foreach(char ch in chars)
{
int temp = Convert.ToInt32(ch);
//小寫字母移位
if (ch >= 'a' && ch <= 'z') {
temp += key % 26;
if (temp > 122) temp -= 26;
if (temp < 97) temp += 26;
}
//大寫字母移位
if (ch >= 'A' && ch <= 'Z') {
temp += key % 26;
if (temp > 90) temp -= 26;
if (temp < 65) temp += 26;
}
es += ((char)temp).ToString();
}
Console.WriteLine(es);
return es;
}
}
}
```
如果不增加新的外觀類,只能通過修改原有外觀類EncryptFacade的源代碼來實現加密類的更換,將原有的對CipherMachine類型對象的引用改為對NewCipherMachine類型對象的引用,這違背了開閉原則,因此需要通過增加新的外觀類來實現對子系統對象引用的改變。
如果增加一個新的外觀類NewEncryptFacade來與FileReader類、FileWriter類以及新增加的NewCipherMachine類進行交互,雖然原有系統類庫無須做任何修改,但是因為客戶端代碼中原來針對EncryptFacade類進行編程,現在需要改為NewEncryptFacade類,因此需要修改客戶端源代碼。
如何在不修改客戶端代碼的前提下使用新的外觀類呢?解決方法之一是:引入一個抽象外觀類,客戶端針對抽象外觀類編程,而在運行時再確定具體外觀類,引入抽象外觀類之后的文件加密模塊結構圖如圖5所示:
客戶類Client針對抽象外觀類AbstractEncryptFacade進行編程,AbstractEncryptFacade代碼如下:
```
namespace FacadeSample
{
abstract class AbstractEncryptFacade
{
public abstract void FileEncrypt(string fileNameSrc, string fileNameDes);
}
}
```
新增具體加密外觀類NewEncryptFacade代碼如下:
```
namespace FacadeSample
{
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 encryptStr = cipher.Encrypt(plainStr);
writer.Write(encryptStr, fileNameDes);
}
}
}
```
```
配置文件App.config中存儲了具體外觀類的類名,代碼如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="facade" value="FacadeSample.NewEncryptFacade"/>
</appSettings>
</configuration>
```
客戶端測試代碼修改如下:
```
using System;
using System.Configuration;
using System.Reflection;
namespace FacadeSample
{
class Program
{
static void Main(string[] args)
{
AbstractEncryptFacade ef; //針對抽象外觀類編程
//讀取配置文件
string facadeString = ConfigurationManager.AppSettings["facade"];
//反射生成對象
ef = (AbstractEncryptFacade)Assembly.Load("FacadeSample"). CreateInstance (facadeString);
ef.FileEncrypt("src.txt", "des.txt");
Console.Read();
}
}
}
```
##模式適用場景
在以下情況下可以考慮使用外觀模式:
1. 當要為訪問一系列復雜的子系統提供一個簡單入口時可以使用外觀模式。
2. 客戶端程序與多個子系統之間存在很大的依賴性。引入外觀類可以將子系統與客戶端解耦,從而提高子系統的獨立性和可移植性。
3. 在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯系,而通過外觀類建立聯系,降低層之間的耦合度。