設計模式-外觀模式(Facade)-Java
目錄
文章目錄
- 1、前言
- 2、外觀模式概述
- 2.1、外觀模式定義
-
- 2.2、外觀模式結構
- 2.3、外觀模式結構圖中角色
- 2.4、外觀模式實作
- 3、應用執行個體
-
- 3.1、執行個體說明
- 3.2、執行個體類圖及實作
- 3.3、結果分析
- 4、抽象外觀類
- 5、總結
- 5.1、優缺點
- 5.2、适用場景
-
- ***後記*** :
内容
1、前言
外觀模式是一種使用頻率非常高的結構型設計模式,它通過引入一個外觀角色來簡化用戶端與子系統之間的互動,為複雜的子系統調用提供一個統一的入口,降低子系統與用戶端的耦合度,且用戶端調用非常友善。
不知道大家有沒有比較過自己泡茶和去茶館喝茶的差別,如果是自己泡茶需要自行準備茶葉、茶具和開水,如圖1-1所示,而去茶館喝茶,最簡單的方式就是跟茶館服務員說想要一杯什麼樣的茶,是鐵觀音、碧螺春還是西湖龍井?正因為茶館有服務員,顧客無須直接和茶葉、茶具、開水等互動,整個泡茶過程由服務員來完成,顧客隻需與服務員互動即可,整個過程非常簡單省事,如圖1-2所示。
圖1-1 自己泡茶
圖1-2 去茶館喝茶
在軟體開發中,有時候為了完成一項較為複雜的功能,一個客戶類需要和多個業務類互動,而這些需要互動的業務類經常會作為一個整體出現,由于涉及到的類比較多,導緻使用時代碼較為複雜,此時,特别需要一個類似服務員一樣的角色,由它來負責和多個業務類進行互動,而客戶類隻需與該類互動。外觀模式通過引入一個新的外觀類(Facade)來實作該功能,外觀類充當了軟體系統中的“服務員”,它為多個業務類的調用提供了一個統一的入口,簡化了類與類之間的互動。在外觀模式中,那些需要互動的業務類被稱為子系統(Subsystem)。如果沒有外觀類,那麼每個客戶類需要和多個子系統之間進行複雜的互動,系統的耦合度将很大;而引入外觀類之後,客戶類隻需要直接與外觀類互動,客戶類與子系統之間原有的複雜引用關系由外觀類來實作,進而降低了系統的耦合度。
2、外觀模式概述
外觀模式中,一個子系統的外部與其内部的通信通過一個統一的外觀類進行,外觀類将客戶類與子系統的内部複雜性分隔開,使得客戶類隻需要與外觀角色打交道,而不需要與子系統内部的很多對象打交道。
2.1、外觀模式定義
- 外觀模式定義:為子系統中的一組接口提供一個統一的入口。外觀模式定義了一個高層接口,整個接口使得這一子系統更加容易使用。
外觀模式又稱為門面模式,它是一種對象結構型模式。外觀模式是迪米特法則的一種具體實作,通過引入一個新的外觀角色可以降低原有系統的複雜度,同時降低客戶類與子系統的耦合度。
2.2、外觀模式結構
外觀模式沒有一個一般化的類圖描述,圖2.2-1所示的類圖也可以作為描述外觀模式的結構圖:
圖2.2-1 外觀模式結構圖
2.3、外觀模式結構圖中角色
由圖2.2-1可知,外觀模式包含如下兩個角色:
- Facade(外觀角色):在用戶端可以調用它的方法,在外觀角色中可以指定相關(一個或者多個)子系統的功能和職責;在正常情況下,它将所有從用戶端發來的請求委派到相應的子系統去,傳遞給相應的子系統對象處理。
- SubSystem(子系統角形):在軟體系統中可以又一個或者多個子系統角色,每一個子系統可以不是一個單獨的類,而是一個類的集合,它實作子系統的功能;每一個子系統都可以被用戶端直接調用,或者被外觀角色調用,它處理由外觀類傳過來的請求;子系統并不知道外觀的存在,對于子系統而言,外觀角色僅僅是另外一個用戶端而已。
2.4、外觀模式實作
外觀模式的主要目的在于降低系統的複雜程度,在面向對象軟體系統中,類與類之間的關系越多,不能表示系統設計得越好,反而表示系統中類之間的耦合度太大,這樣的系統在維護和修改時都缺乏靈活性,因為一個類的改動會導緻多個類發生變化,而外觀模式的引入在很大程度上降低了類與類之間的耦合關系。引入外觀模式之後,增加新的子系統或者移除子系統都非常友善,客戶類無須進行修改(或者極少的修改),隻需要在外觀類中增加或移除對子系統的引用即可。從這一點來說,外觀模式在一定程度上并不符合開閉原則,增加新的子系統需要對原有系統進行一定的修改,雖然這個修改工作量不大。
外觀模式中所指的子系統是一個廣義的概念,它可以是一個類、一個功能子產品、系統的一個組成部分或者一個完整的系統。子系統類通常是一些業務類,實作了一些具體的、獨立的業務功能,其典型代碼如下:
class SubSystemA{
public void MethodA() {
//業務實作代碼
}
}
class SubSystemB
{
public void MethodB()
{
//業務實作代碼
}
}
class SubSystemC
{
public void MethodC()
{
//業務實作代碼
}
}
在引入外觀類之後,與子系統業務類之間的互動由外觀類來完成,在外觀類中通常存在如下代碼:
class Facade
{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void Method()
{
obj1.MethodA();
obj2.MethodB();
obj3.MethodC();
}
}
由于在外觀類中維持了對子系統對象的引用,用戶端可以通過外觀類來間接調用子系統對象的業務方法,而無須與子系統對象直接互動。引入外觀類後,用戶端代碼變得非常簡單,典型代碼如下:
class Program
{
static void Main(string[] args)
{
Facade facade = new Facade();
facade.Method();
}
}
3、應用執行個體
3.1、執行個體說明
某軟體公司欲開發一個可應用于多個軟體的檔案加密子產品,該子產品可以對檔案中的資料進行加密并将加密之後的資料存儲在一個新檔案中,具體的流程包括三個部分,分别是讀取源檔案、加密、儲存加密之後的檔案,其中,讀取檔案和儲存檔案使用流來實作,加密操作通過求模運算實作。這三個操作相對獨立,為了實作代碼的獨立重用,讓設計更符合單一職責原則,這三個操作的業務代碼封裝在三個不同的類中。
3.2、執行個體類圖及實作
通過分析,本執行個體結構圖如圖3.2-1所示:
圖3.2-1 檔案加密子產品示意圖
在圖4中,EncryptFacade充當外觀類,FileReader、CipherMachine和FileWriter充當子系統類。執行個體代碼如下:
- MyFileReader類代碼3.2-1:檔案讀取類-子系統類
package facade; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class MyFileReader { public String read(String fileSrc) { try { BufferedReader br = new BufferedReader(new FileReader(fileSrc)); StringBuilder sb = new StringBuilder(); char[] ch = new char[1024]; int len = 0; while((len = br.read(ch)) != -1) { sb.append(ch, 0, len); } br.close(); System.out.println("讀取檔案,擷取明文:" + sb.toString()); return sb.toString(); }catch (IOException e) { e.printStackTrace(); return null; } } }
- CipherMachine類代碼3.2-2:資料加密類-子系統類
package facade; public class CipherMachine { public String encrypt(String plainText) { char[] chs = plainText.toCharArray(); StringBuilder sb = new StringBuilder(); for(char ch: chs) { sb.append(ch % 7); } System.out.println("資料加密,将明文轉換為密文:" + sb.toString()); return sb.toString(); } }
- MyFileWriter類代碼3.2-3:檔案寫入類-子系統類
package facade; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class MyFileWriter { public void write(String encryptText, String fileDes) { try { BufferedWriter bw = new BufferedWriter(new FileWriter(fileDes)); bw.write(encryptText); bw.newLine(); bw.close(); System.out.println("儲存密文,寫入檔案。"); }catch (IOException e) { e.printStackTrace(); } } }
- EncryptFacade類代碼3.2-4:加密外觀類-外觀類
package facade; public class EncryptFacade { //維持對其他對象的引用 private MyFileReader reader; private CipherMachine cipher; private MyFileWriter writer; public EncryptFacade() { reader = new MyFileReader(); cipher = new CipherMachine(); writer = new MyFileWriter(); } //調用其他對象的業務方法 public void FileEncrypt(String fileNameSrc, String fileNameDes) { String plainStr = reader.read(fileNameSrc); String encryptStr = cipher.encrypt(plainStr); writer.write(encryptStr, fileNameDes); } }
- Client類代碼3.2-5:測試類
package facade; import java.io.File; public class Client { public static void main(String[] args) { EncryptFacade ef = new EncryptFacade(); String parent = "F:\\train\\common\\designPattern\\facade"; ef.FileEncrypt(parent + File.separator + "plain.txt", parent + File.separator + "encrypt.txt"); } }
- plain.txt:明文檔案内容
Hello Java!
- encrypt.txt:密文檔案内容
23336446665
- 測試結果:
讀取檔案,擷取明文:Hello Java! 資料加密,将明文轉換為密文:23336446665 儲存密文,寫入檔案。
3.3、結果分析
在加密類CipherMachine中,采用求模運算對明文進行加密,将明文中的每一個字元除以一個整數(本例中為7,可以由使用者來進行設定)後取餘數作為密文。
4、抽象外觀類
在标準的外觀模式結構圖中,如果需要增加、删除或更換與外觀類互動的子系統類,必須修改外觀類或用戶端的源代碼,這将違背開閉原則,是以可以通過引入抽象外觀類來對系統進行改進,在一定程度上可以解決該問題。在引入抽象外觀類之後,用戶端可以針對抽象外觀類進行程式設計,對于新的業務需求,不需要修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置檔案來達到不修改任何源代碼并更換外觀類的目的。
下面通過一個具體執行個體來學習如何使用抽象外觀類:
如果在應用執行個體“檔案加密子產品”中需要更換一個加密類,不再使用原有的基于求模運算的加密類CipherMachine,而改為基于移位運算的新加密類NewCipherMachine,其代碼如下:
package facade;
public class CipherMachine1 {
public String encrypt(String plainText) {
char[] chs = plainText.toCharArray();
StringBuilder sb = new StringBuilder();
int key = 10;
for(char ch: chs) {
int temp =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;
}
sb.append((char)temp);
}
System.out.println("資料加密,将明文轉換為密文:" + sb.toString());
return sb.toString();
}
}
如果不增加新的外觀類,隻能通過修改原有外觀類EncryptFacade的源代碼來實作加密類的更換,将原有的對CipherMachine類型對象的引用改為對NewCipherMachine類型對象的引用,這違背了開閉原則,是以需要通過增加新的外觀類來實作對子系統對象引用的改變。
如果增加一個新的外觀類NewEncryptFacade來與FileReader類、FileWriter類以及新增加的NewCipherMachine類進行互動,雖然原有系統類庫無須做任何修改,但是因為用戶端代碼中原來針對EncryptFacade類進行程式設計,現在需要改為NewEncryptFacade類,是以需要修改用戶端源代碼。
如何在不修改用戶端代碼的前提下使用新的外觀類呢?解決方法之一是:引入一個抽象外觀類,用戶端針對抽象外觀類程式設計,而在運作時再确定具體外觀類,引入抽象外觀類之後的檔案加密子產品結構圖如圖4-1所示:
圖4-1 引入抽象外觀類之後的檔案加密子產品結構圖
在圖4-1中,客戶類Client針對抽象外觀類AbstractEncryptFacade進行程式設計,AbstractEncryptFacade代碼如下:
package facade;
public abstract class AbstractEncryptFacade {
public abstract void FileEncrypt(String fileNameSrc, String fileNameDes);
}
新增具體加密外觀類EncryptFacade1代碼如下:
package facade;
public class EncryptFacade1 extends AbstractEncryptFacade{
//維持對其他對象的引用
private MyFileReader reader;
private CipherMachine1 cipher;
private MyFileWriter writer;
public EncryptFacade1() {
reader = new MyFileReader();
cipher = new CipherMachine1();
writer = new MyFileWriter();
}
//調用其他對象的業務方法
@Override
public void FileEncrypt(String fileNameSrc, String fileNameDes) {
String plainStr = reader.read(fileNameSrc);
String encryptStr = cipher.encrypt(plainStr);
writer.write(encryptStr, fileNameDes);
}
}
配置檔案和工具類代碼如下:
//配置檔案encrypt.properties内容
className=facade.EncryptFacade1
// Utils.java工具類代碼
package facade;
import java.util.Properties;
public class Utils {
public static AbstractEncryptFacade getEncryptFacade() {
try {
Properties prop = new Properties();
prop.load(Utils.class.getClassLoader().getResourceAsStream("encrypt.properties"));
String className = prop.getProperty("className");
return (AbstractEncryptFacade) (Class.forName(className).newInstance());
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
用戶端代碼:
package facade;
import java.io.File;
public class Client {
public static void main(String[] args) {
AbstractEncryptFacade ef = Utils.getEncryptFacade();
String parent = "F:\\train\\common\\designPattern\\facade";
ef.FileEncrypt(parent + File.separator + "plain.txt", parent + File.separator + "encrypt.txt");
}
}
測試結果:
讀取檔案,擷取明文:Hello Java!
資料加密,将明文轉換為密文:Rovvy Tkfk!
儲存密文,寫入檔案。
原有外觀類EncryptFacade也需作為抽象外觀類AbstractEncryptFacade類的子類,更換具體外觀類時隻需修改配置檔案,無須修改源代碼,符合開閉原則。
5、總結
外觀模式是一種使用頻率非常高的設計模式,它通過引入一個外觀角色來簡化用戶端與子系統之間的互動,為複雜的子系統調用提供一個統一的入口,使子系統與用戶端的耦合度降低,且用戶端調用非常友善。外觀模式并不給系統增加任何新功能,它僅僅是簡化調用接口。在幾乎所有的軟體中都能夠找到外觀模式的應用,如絕大多數B/S系統都有一個首頁或者導航頁面,大部分C/S系統都提供了菜單或者工具欄,在這裡,首頁和導航頁面就是B/S系統的外觀角色,而菜單和工具欄就是C/S系統的外觀角色,通過它們使用者可以快速通路子系統,降低了系統的複雜程度。所有涉及到與多個業務對象互動的場景都可以考慮使用外觀模式進行重構。
5.1、優缺點
- 主要優點
- (1)它對用戶端屏蔽了子系統元件,減少了用戶端所需處理的對象數目,并使得子系統使用起來更加容易。通過引入外觀模式,用戶端代碼将變得很簡單,與之關聯的對象也很少。
- (2)它實作了子系統與用戶端之間的松耦合關心,這使得子系統的變化不會影響到調用它的用戶端,值需要調整外觀類即可。
- (3)一個子系統的修改對其他子系統沒有任何影響,而且子系統内部編号不會影響到外觀對象。
- 主要缺點
- (1)) 不能很好地限制用戶端直接使用子系統類,如果對用戶端通路子系統類做太多的限制則減少了可變性和靈活 性。
- (2 如果設計不當,增加新的子系統可能需要修改外觀類的源代碼,違背了開閉原則。
5.2、适用場景
在以下情況下可以考慮使用外觀模式:
+ (1) 當要為通路一系列複雜的子系統提供一個簡單入口時可以使用外觀模式。
+ (2) 用戶端程式與多個子系統之間存在很大的依賴性。引入外觀類可以将子系統與用戶端解耦,進而提高子系統的獨立性和可移植性。
+ (3) 在階層化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接産生聯系,而通過外觀類建立聯系,降低層之間的耦合度。
後記 :
參考文獻:Java設計模式(劉偉).pdf。持續更新,歡迎交流,本人QQ:806797785
前端項目源代碼位址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼位址:https://gitee.com/gaogzhen/JAVA