設計模式-外觀模式(Facade)-Java

設計模式-外觀模式(Facade)-Java


目錄




內容

1、前言

  外觀模式是一種使用頻率非常高的結構型設計模式,它通過引入一個外觀角色來簡化客戶端與子系統之間的交互,爲複雜的子系統調用提供一個統一的入口,降低子系統與客戶端的耦合度,且客戶端調用非常方便。

  不知道大家有沒有比較過自己泡茶和去茶館喝茶的區別,如果是自己泡茶需要自行準備茶葉、茶具和開水,如圖1-1所示,而去茶館喝茶,最簡單的方式就是跟茶館服務員說想要一杯什麼樣的茶,是鐵觀音、碧螺春還是西湖龍井?正因爲茶館有服務員,顧客無須直接和茶葉、茶具、開水等交互,整個泡茶過程由服務員來完成,顧客只需與服務員交互即可,整個過程非常簡單省事,如圖1-2所示。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HYY8b9gQ-1591831642708)(./images/drinkTea.png)]
圖1-1 自己泡茶
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GZKGP0S1-1591831642712)(./images/drinkTea_chaguan.png)]
圖1-2 去茶館喝茶

  在軟件開發中,有時候爲了完成一項較爲複雜的功能,一個客戶類需要和多個業務類交互,而這些需要交互的業務類經常會作爲一個整體出現,由於涉及到的類比較多,導致使用時代碼較爲複雜,此時,特別需要一個類似服務員一樣的角色,由它來負責和多個業務類進行交互,而客戶類只需與該類交互。外觀模式通過引入一個新的外觀類(Facade)來實現該功能,外觀類充當了軟件系統中的“服務員”,它爲多個業務類的調用提供了一個統一的入口,簡化了類與類之間的交互。在外觀模式中,那些需要交互的業務類被稱爲子系統(Subsystem)。如果沒有外觀類,那麼每個客戶類需要和多個子系統之間進行復雜的交互,系統的耦合度將很大;而引入外觀類之後,客戶類只需要直接與外觀類交互,客戶類與子系統之間原有的複雜引用關係由外觀類來實現,從而降低了系統的耦合度。

2、外觀模式概述

  外觀模式中,一個子系統的外部與其內部的通信通過一個統一的外觀類進行,外觀類將客戶類與子系統的內部複雜性分隔開,使得客戶類只需要與外觀角色打交道,而不需要與子系統內部的很多對象打交道。

2.1、外觀模式定義

  • 外觀模式定義:爲子系統中的一組接口提供一個統一的入口。外觀模式定義了一個高層接口,整個接口使得這一子系統更加容易使用。

  外觀模式又稱爲門面模式,它是一種對象結構型模式。外觀模式是迪米特法則的一種具體實現,通過引入一個新的外觀角色可以降低原有系統的複雜度,同時降低客戶類與子系統的耦合度。

2.2、外觀模式結構

  外觀模式沒有一個一般化的類圖描述,圖2.2-1所示的類圖也可以作爲描述外觀模式的結構圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lPpufHE8-1591831642713)(./images/model_facade.png)]
圖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所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vBQehkw4-1591831642714)(./images/encrypt.png)]
圖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所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ognOtjUx-1591831642715)(./images/encrypt_abstractFacade.png)]
圖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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章