1. 程式人生 > >設計模式——結構型模式(介面卡,橋接,過濾器,組合,裝飾器,外觀,享元,代理)

設計模式——結構型模式(介面卡,橋接,過濾器,組合,裝飾器,外觀,享元,代理)

目錄

  • 一、介面卡模式
  • 二、橋接模式
  • 三、過濾器模式
  • 四、組合模式
  • 五、裝飾器模式
  • 六、外觀模式
  • 七、享元模式
  • 八、代理模式

結構型模式描述如何將類或者物件結合在一起形成更大的結構

可以分為兩類:

  1. 類結構型模式。它關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係
  2. 物件結構型模式。它關心類與物件的組合,通過關聯關係使得在一個類中定義另一個類的例項物件,然後通過該物件呼叫其方法

根據“組合複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是物件結構型模式

一、介面卡模式

1、楔子

昨天用剛換的新手機聽歌時,發現底部只有 type-c 介面,而博主的耳機是3.5毫米插孔的。好在廠商配備了一個 type-c 轉3.5毫米耳機孔,這個轉換器就是接下來要講的介面卡模式

2、解析

介面卡模式將一個類的介面轉換成客戶希望的另外一個介面,使介面不相容的類可以一起工作

模式組成:

  1. 目標角色:定義客戶使用的介面
  2. 被適配角色:該角色有一個已存在並使用了的介面,這個介面是需要我們適配的
  3. 介面卡角色:介面卡模式的核心。將被適配角色已有的介面轉換為目標角色希望的介面

在介面卡模式中可以定義一個包裝類,包裝不相容介面的物件,這個包裝類指的就是介面卡,它所包裝的物件就是適配者,即被適配的類

也就是說,當客戶類呼叫介面卡的方法時,在介面卡類的內部將呼叫適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。因此介面卡可以使由於介面不相容而不能互動的類可以一起工作

介面卡模式可分為兩種:類介面卡模式和物件介面卡模式,區別僅在於介面卡角色對被適配角色的適配是通過繼承完成的還是通過組合完成的。由於 Java不支援多重繼承,且繼承會破壞封裝,大都提倡使用組合代替繼承

3、舉例

一個畫圖程式有繪製點、直線、方塊等圖形的功能,為了讓客戶程式使用時不用關心它們的不同,使用一個抽象類規範這些圖形的介面

如果現在要繪製圓,發現在系統的其他地方已經有了繪製圓的實現,但系統中的方法和抽象類中規定的方法名稱不一樣。不管是修改系統中繪製圓的方法,還是抽象類的方法名都極其繁瑣,因為出現的地方和次數太多了,這時介面卡模式就能大顯身手了

新增繪製圓的需求之前,類圖如下

新增圓的繪製後,類圖如下

由圖可知,Shape、Circle、TextCircle之間的關係和介面卡模式中 Target、Apater、Apatee之間的關係相對應

下面是 Circle的實現程式碼

class Circle extends Shape {
    //這裡引用了 TextCircle
    private TextCircle tc;
    
    public Circle() {
        tc = new TextCircle(); //初始化
    }
    void public display() {
        tc.display(); //在規定的方法中呼叫 TextCircle原來的方法
    }
}

在介面卡角色中不僅可以完成介面轉換的過程,還能對功能進行改進和擴充套件

該模式與代理模式的區別是,後者應用的情況不改變介面命名,而是對已有介面功能的一種控制;前者則強調介面轉換

二、橋接模式

1、楔子

系統設計中充滿了各種變數,面對不同的變動,我們只能不斷的修改設計和程式碼,然後開始新一輪的測試

那麼,我們能採取什麼方法來解決變化帶給系統的影響呢?可以分析變化的種類,將不變的框架使用抽象類定義出來,然後再將變化的內容使用具體的子類分別實現。這樣客戶看到的只是一個抽象類,較好的避免了為抽象類現有介面新增新的實現帶來的影響,從而縮小了變化帶來的影響。但是該方法有一個缺陷:子類數量的爆炸,且某些時候不是很靈活

當一顆幾成熟上的一些子樹存在了類似的行為,意味著子樹中存在幾乎重複的功能程式碼,不妨將行為提取出來,並採用介面的方式提供出來,然後以組合的方式將服務提供給原來的子類。以此達到了前端和被使用的後端獨立變化,還實現了後端的重用

這就是橋接模式的誕生

2、解析

橋接模式將抽象部分與它的實現部分分離,使它們都可以獨立變化。這裡的抽象部分和實現部分指組合關係,即實現部分被抽象部分呼叫以完成抽象部分的功能

模式組成:

  1. 抽象角色:定義抽象類的介面,維護一個指向實現角色的引用
  2. 精確抽象角色:實現並擴充由抽象角色定義的介面
  3. 實現角色:給出實現類的介面,這裡的介面與抽象角色中的介面可以不一致
  4. 具體實現角色:給出實現角色定義介面的具體實現

理解橋接模式,重點需要理解如何將抽象化與實現化脫耦,使得二者可以獨立地變化

  • 抽象化:忽略一些資訊,把不同的實體當作同樣的實體對待。在面向物件中,將物件的共同性質抽取出來形成類的過程即為抽象化的過程
  • 實現化:針對抽象化給出的具體實現。抽象化與實現化是一對互逆的概念,實現化產生的物件比抽象化更具體,是對抽象化事物的進一步具體化
  • 脫耦:將抽象化和實現化之間的耦合解開,或者說將它們之間的強關聯改換成弱關聯,將兩個角色之間的繼承關係改為關聯關係。橋接模式中的脫耦是指在一個軟體系統的抽象化和實現化之間使用關聯關係(組合或者聚合關係)而不是繼承關係,從而使兩者可以相對獨立地變化

橋接模式適用場景:

  • 如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯絡,通過橋接模式可以使它們在抽象層建立一個關聯關係
  • 抽象化角色和實現化角色可以以繼承的方式獨立擴充套件而互不影響,在程式執行時可以動態將一個抽象化子類的物件和一個實現化子類的物件進行組合,即系統需要對抽象化角色和實現化角色進行動態耦合
  • 一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件
  • 雖然在系統中使用繼承是沒有問題的,但是由於抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者
  • 對於那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用

3、舉例

使用過 Java AWT的人都知道,在不同系統下開發的軟體介面帶有不同系統的風格,而我們在使用 AWT的 API時根本沒有對不同系統進行區分。AWT正是使用橋接模式做到的這一點

由於博主對 AWT瞭解不深,無法具體講解,這裡用 《Thinking in Patterns with Java》中的教學程式碼分析

//抽象部分(前端)的抽象角色
class Abstraction {
    //維護一個指向實現(Implementor)角色的引用
    private Implementation implementation;
    
    public Abstraction(Implementation imp) {
        implementation = imp;
    }
    //接下來定義前端應有的介面
    public void service1() {
        //使用後端(實現部分)已有的介面
        //組合實現功能
        implementation.facility1();
        implementation.facility2();
    }
    public void service2() {
        implementation.facility2();
        implementation.facility3();
    }
    public void service3() {
        implementation.facility1();
        implementation.facility2();
        implementation.facility4();
    }
    
    protected Implementation getImplementation() {
        return implementation;
    }
}

//抽象部分(前端)的精確抽象角色
class ClientService1 extends Abstraction {
    public ClientService1(Implementation imp) {
        super(imp);
    }
    //使用抽象角色提供的方法組合起來完成某項功能,這就是為什麼叫精確抽象角色(修正抽象角色)
    public void serviceA() {
        service1();
        service2();
    }
    public void serviceB() {
        service3();
    }
}
//另一個精確抽象角色,和上面一樣
class ClientService2 extends Abstraction {
    ...
    
    //直接通過實現部分的方法來實現一定的功能
    public void serviceE() {
        getImplementation().facility3();
    }
}

//實現部分(後端)的實現角色
interface Implementation {
    //該介面只是定義了一定的介面
    void facility1();
    void facility2();
    void facility3();
    void facility4();
}

//具體實現角色就是要將實現角色提供的介面實現並完成一定功能,這裡略
class Implementation1 implements Implementation {
    ...
}

該程式體現出一點特色:不僅實現部分和抽象部分提供的介面可以完全不一樣,它們內部的介面也可以完全不一樣。但是實現部分要提供類似的功能

三、過濾器模式

1、解析

過濾器模式允許開發人員使用不同的標準來過濾一組物件,通過邏輯運算以解耦的方式把它們連線起來

在管道和過濾器軟體體系結構中,每個模組都有一組輸入和一組輸出。每個模組從它的輸入端接收輸入資料流,在其內部經過處理後,按照標準的順序將結果資料流送到輸出端以達到傳遞一組完整的計算結果例項的目的

通常情況下可以通過對輸入資料流進行區域性變換,並採用漸增式計算方法,在未處理完所有輸入資料以前就可以產生部分計算結果,並將其送到輸出埠(類似於流水線結構),因此稱這種模組為“過濾器“。在這種結構中,各模組之間的聯結器充當了資料流的導管,將一個過濾器的輸出傳到下一個過濾器的輸入端,所以這種聯結器稱為“管道”

模式組成:

  1. 過濾器
    • 輸入過濾器:處在問題所在的外部世界與軟體系統的邊界處,是系統資料流的源點。負責接收外界資訊並轉化為系統所需的資料流
    • 處理過濾器:系統內變換資料流的部件。它有一個入口和一個出口,資料經入口流入,經過處理過濾器內部處理之後從出口流出
    • 輸出過濾器:資料流的終點
  2. 管道

    • 主要功能是連線各個過濾器,充當過濾器之間資料流的通道。具有資料緩衝以及提高過濾器之間的並行性操作的作用

優點:

  • 體現了各功能模組的“黑盤”特性及高內聚、低耦合的特點
  • 可以將整個系統的輸入,輸出行為看成是多個過濾器行為的簡單合成
  • 支援軟體功能模組的重用
  • 便於系統維護:新的過濾器可以新增到現有系統中來,舊的可以由改進的過濾器替換
  • 支援某些特定的分析,如吞吐量計算、死鎖檢測等
  • 支援並行操作,每個過濾器可以作為一個單獨的任務完成

缺點:

  • 通常導致系統處理過程的成批操作
  • 需要設計者協調兩個相對獨立但又存在關係的資料流
  • 可能需要每個過濾器自己完成資料解析和合成工作(如加密和解密),從而導致系統性能下降,並增加了過濾器具體實現的複雜性

2、舉例

//建立一個類,在該類上應用標準
public class Person {
   private String name;
   private String gender;
   private String maritalStatus;
 
   public Person(String name,String gender,String maritalStatus){
      this.name = name;
      this.gender = gender;
      this.maritalStatus = maritalStatus;    
   }
   public String getName() {
      return name;
   }
   public String getGender() {
      return gender;
   }
   public String getMaritalStatus() {
      return maritalStatus;
   }  
}

//為標準(Criteria)建立一個介面
import java.util.List;
 
public interface Criteria {
   public List<Person> meetCriteria(List<Person> persons);
}

//建立實現了 Criteria介面的實體類
import java.util.ArrayList;
import java.util.List;
 
class CriteriaMale implements Criteria {
   public List<Person> meetCriteria(List<Person> persons) {
      List<Person> malePersons = new ArrayList<Person>(); 
      for (Person person : persons) {
         if(person.getGender().equalsIgnoreCase("MALE")){
            malePersons.add(person);
         }
      }
      return malePersons;
   }
}

class CriteriaFemale implements Criteria {
   public List<Person> meetCriteria(List<Person> persons) {
      List<Person> femalePersons = new ArrayList<Person>(); 
      for (Person person : persons) {
         if(person.getGender().equalsIgnoreCase("FEMALE")){
            femalePersons.add(person);
         }
      }
      return femalePersons;
   }
}

class CriteriaSingle implements Criteria {
   public List<Person> meetCriteria(List<Person> persons) {
      List<Person> singlePersons = new ArrayList<Person>(); 
      for (Person person : persons) {
         if(person.getMaritalStatus().equalsIgnoreCase("SINGLE")){
            singlePersons.add(person);
         }
      }
      return singlePersons;
   }
}

class AndCriteria implements Criteria {
   private Criteria criteria;
   private Criteria otherCriteria;
 
   public AndCriteria(Criteria criteria, Criteria otherCriteria) {
      this.criteria = criteria;
      this.otherCriteria = otherCriteria; 
   }
   public List<Person> meetCriteria(List<Person> persons) {
      List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);     
      return otherCriteria.meetCriteria(firstCriteriaPersons);
   }
}

class OrCriteria implements Criteria {
   private Criteria criteria;
   private Criteria otherCriteria;
 
   public OrCriteria(Criteria criteria, Criteria otherCriteria) {
      this.criteria = criteria;
      this.otherCriteria = otherCriteria; 
   }
   public List<Person> meetCriteria(List<Person> persons) {
      List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
      List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);
 
      for (Person person : otherCriteriaItems) {
         if(!firstCriteriaItems.contains(person)){
           firstCriteriaItems.add(person);
         }
      }  
      return firstCriteriaItems;
   }
}

//使用不同的標準(Criteria)和它們的結合來過濾 Person物件的列表
import java.util.ArrayList; 
import java.util.List;
 
public class CriteriaPatternDemo {
   public static void main(String[] args) {
      List<Person> persons = new ArrayList<Person>();
 
      persons.add(new Person("Robert","Male", "Single"));
      persons.add(new Person("John","Male", "Married"));
      persons.add(new Person("Laura","Female", "Married"));
      persons.add(new Person("Diana","Female", "Single"));
      persons.add(new Person("Mike","Male", "Single"));
      persons.add(new Person("Bobby","Male", "Single"));
 
      Criteria male = new CriteriaMale();
      Criteria female = new CriteriaFemale();
      Criteria single = new CriteriaSingle();
      Criteria singleMale = new AndCriteria(single, male);
      Criteria singleOrFemale = new OrCriteria(single, female);
 
      System.out.println("Males: ");
      printPersons(male.meetCriteria(persons));
 
      System.out.println("\nFemales: ");
      printPersons(female.meetCriteria(persons));
 
      System.out.println("\nSingle Males: ");
      printPersons(singleMale.meetCriteria(persons));
 
      System.out.println("\nSingle Or Females: ");
      printPersons(singleOrFemale.meetCriteria(persons));
   }
 
   public static void printPersons(List<Person> persons){
      for (Person person : persons) {
         System.out.println("Person : [ Name : " + person.getName() 
            +", Gender : " + person.getGender() 
            +", Marital Status : " + person.getMaritalStatus()
            +" ]");
      }
   }      
}

四、組合模式

1、楔子

在資料結構課程中,樹是非常重要的章節。其定義如下

樹是 n(n>=0)個結點的有限集 T,T為空時稱為空樹,否則它滿足如下兩個條件:

  1. 有且僅有一個特定的稱為根的結點
  2. 其餘的結點可分為 m(m>=0)個互不相交的子集 T1, T2, ..., Tm,其中每個子集本身又是一棵樹,並稱其為根的子樹

該遞迴定義刻畫了樹的固有屬性:一顆非空樹由若干棵子樹構成,子樹又能分成若干更小的子樹,這些子樹既可以是葉子也可以是分支

寫下來學習的組合模式就和樹型結構、遞迴關係有一定關係

2、解析

組合模式將物件以樹形結構組織起來,以達成“部分—整體”的層次結構,使得客戶端對單個物件和組合物件的使用具有一致性

模式組成:

  1. 抽象構件角色:為組合中的物件宣告介面,也可以為共有介面實現預設行為
  2. 樹葉構件角色:在組合中表示葉節點物件——沒有子節點,實現抽象構件角色宣告的介面
  3. 樹枝構件角色:在組合中表示分支節點物件——有子節點,實現抽象構件角色宣告的介面;儲存子部件

組合模式的關鍵是定義了一個抽象構件類,它既可以代表葉子,又可以代表容器,而客戶端針對該抽象構件類進行程式設計,無須知道它到底表示的是葉子還是容器,可以對其進行統一處理

同時容器物件與抽象構件類之間還建立一個聚合關聯關係,在容器物件中既可以包含葉子,也可以包含容器,以此實現遞迴組合,形成一個樹形結構

優點:

  • 使客戶端呼叫簡單,客戶端可以一致的使用組合結構或其中單個物件,使用者不必關心自己處理的是單個物件還是整個組合結構,這就簡化了客戶端程式碼
  • 更容易在組合體內加入物件部件。客戶端不必因為加入了新的物件部件而更改程式碼

缺點:不容易限制組合中的構件

3、舉例

JUnit是一個單元測試框架,按照此框架的規範編寫測試程式碼可以使單元測試自動化。為達到自動化的目的,JUnit定義了兩個概念:TestCase和 TestSuite。前者是編寫的測試類,後者是一個不同 TestCase的集合,當然這個集合裡可以包含 TestSuite元素,這樣執行一個 TestSuite會將其包含的 TestCase全部執行

但是在真實執行測試程式時不需要關心這個類是 TestCase還是 TestSuite,只關心測試執行結果如何。這就是 JUnit使用組合模式的原因

JUnit為了採用組合模式將 TestCase和 TestSuite統一起來,建立了一個 Test介面來扮演抽象構件角色,這樣原來的 TestCase扮演組合模式中的樹葉構件角色,而 TestSuite扮演組合模式中的樹枝構件角色

//Test介面——抽象構件角色
interface Test {
    /**
     * Counts the number of test cases that will be run by this test. 
     */
    public abstract int countTestCase();
    /** 
     * Runs a test and collects its result in a TestResult instance. 
     */
    public abstract void run(TestResult result);
}

//TestSuite類的部分有關原始碼——Composite角色,實現了介面 Test
class TestSuite implements Test {
    //使用較老的 Vector儲存新增的 test
    private Vector fTests = new Vector(10);
    private String fName;
    
    /** 
     * Adds a test to the suite. 
     */
    public void addTest(Test test) {
        //這裡的引數是 Test型別的,意味著 TestCase和 TestSuite以及以後實現 Test介面的任何類都可以被新增進來
        fTests.addElement(test);
    }
    /**
     * Counts the number of test cases that will be run by this test. 
     */
    public int countTestCase() {
        int count= 0;
        for (Enumeration e= tests(); e.hasMoreElements(); ) {
            Test test= (Test)e.nextElement();
            count= count + test.countTestCases();
        }
        return count;
    }
    /** 
     * Runs the tests and collects their result in a TestResult. 
     */
    public void run(TestResult result) {
        for (Enumeration e= tests(); e.hasMoreElements(); ) {
            if (result.shouldStop() )
                break;
            Test test= (Test)e.nextElement();
            //關鍵在這個方法
            runTest(test, result);
        }
    }
    //該方法中是遞迴的呼叫,至於 Test是什麼型別只有執行時得知
    public void runTest(Test test, TestResult result) {
        test.run(result);
    }
}

//TestCase類的部分有關原始碼——Leaf角色,編寫的測試類就是繼承於它
abstract class TestCase extends Assert implements Test {
    /**
     * Counts the number of test cases executed by run(TestResult result).
     */
    public int countTestCases() {
        return 1;
    }
    /**
     * Runs the test case and collects the results in TestResult.
     */
    public void run(TestResult result) {
        result.run(this);
    }
}

五、裝飾器模式

1、楔子

“裝飾”一詞,肯定讓你想到了又黑又火的家庭裝修。兩者在道理上有很多相似之處。家庭裝修就是在實而不華的牆面上塗抹一層華而不實的顏料,看起來多姿多彩。但是牆仍舊是牆,本質並沒有發生變化

2、解析

裝飾器模式動態地給一個物件新增一些額外的功能。新增的方式是對客戶透明的,因此客戶端並不會覺得物件在裝飾前和裝飾後有什麼不同。這樣裝飾器模式可以在不需要創造更多子類的情況下,將物件的功能加以擴充套件

模式組成:

  1. 抽象構件角色:定義一個抽象介面,規範準備接受附加功能的物件
  2. 具體構件角色:被裝飾者,定義一個將要被裝飾增加功能的類
  3. 裝飾角色:持有一個構件角色的例項,並定義了抽象構件定義的介面
  4. 具體裝飾角色:給構件增加功能

一般有兩種方式可以實現給一個類或物件增加行為:

  • 繼承機制(通過繼承一個現有類可以使得子類在擁有自身方法的同時還擁有父類的方法)
  • 關聯機制(將一個類的物件嵌入另一個物件中,由另一個物件決定是否呼叫嵌入物件的行為以便擴充套件自己的行為)

與繼承關係相比,關聯關係的優勢在於不會破壞類的封裝性,而且繼承是一種耦合度較大的靜態關係,無法在程式執行時動態擴充套件。使用裝飾器模式來實現擴充套件比繼承更加靈活,可以在不需要創造更多子類的情況下,將物件的功能加以擴充套件

組合模式側重通過遞迴組合構造類,使不同的物件、多重的物件可以“一視同仁”;裝飾器模式僅僅是借遞迴組合來達到定義中的目的

3、舉例

JUnit中 TestCase是一個很重要的類,允許對其進行進行功能擴充套件

在 junit.extensions包中,TestDecorator、RepeatedTest就是對 TestCase的裝飾器模式擴充套件

//抽象構件角色
interface Test {
    /**
     * Counts the number of test cases that will be run by this test.
     */
    public abstract int countTestCases();
    /**
     * Runs a test and collects its result in a TestResult instance.
     */
    public abstract void run(TestResult result);
}

//具體構件物件,這裡是抽象類
abstract class TestCase extends Assert implements Test {
    public int countTestCases() {
        return 1;
    }
    public TestResult run() {
        TestResult result = createResult();
        run(result);
        return result;
    }
    public void run(TestResult result) {
        result.run(this);
    }
}

//裝飾角色
class TestDecorator extends Assert implements Test {
    //按照上面的要求保留了一個對構件物件的例項
    protected Test fTest;
    
    public TestDecorator(Test test) {
        fTest = test;
    }
    /**
     * The basic run behaviour.
     */
    public void basicRun(TestResult result) {
        fTest.run(result);
    }
    public int countTestCases() {
        return fTest.countTestCases();
    }
    public void run(TestResult result) {
        basicRun(result);
    }
    public String toString() {
        return fTest.toString();
    }
    public Test getTest() {
        return fTest;
    }
}

//具體裝飾角色。該類的作用是設定測試類的執行次數
class RepeatedTest extends TestDecorator {
    private int fTimesRepeat;
    
    public RepeatedTest(Test test, int repeat) {
        super(test);
        if (repeat < 0)
            throw new IllegalArgumentException("Repetition count must be > 0");
        fTimesRepeat = repeat;
    }
    //如何進行裝飾
    public int countTestCases() {
        return super.countTestCases()*fTimesRepeat;
    }
    public void run(TestResult result) {
        for (int i= 0; i < fTimesRepeat; i+ + ) {
            if (result.shouldStop())
                break;
            super.run(result);
        }
    }
    public String toString() {
        return super.toString() + "(repeated)";
    }
}
/*
使用時,採用如下方式:
TestDecorator test = new RepeatedTest(new TestXXX(), 3);
*/

六、外觀模式

1、解析

外觀模式:外部與一個子系統的通訊必須通過一個統一的外觀物件進行,為子系統中的一組介面提供一個一致的介面

模式組成:

  1. 外觀角色:外觀模式的核心。被使用者角色呼叫,因此熟悉子系統的功能。其內部根據客戶角色已有的需求預定了幾種功能組合
  2. 子系統角色:實現子系統的功能。對它而言,外觀角色和客戶角色是未知的,它沒有任何外觀角色的資訊和連結
  3. 客戶角色:呼叫外觀角色完成要得到的功能

根據“單一職責原則”,在軟體中將一個系統劃分為若干個子系統有利於降低整個系統的複雜性。常見的設計目標是使子系統間的通訊和相互依賴關係達到最小,而達到該目標的途徑之一就是引入一個外觀物件,它為子系統的訪問提供了一個簡單而單一的入口

外觀模式也是“迪米特法則”的體現,通過引入一個新的外觀類可以降低原有系統的複雜度,同時降低客戶類與子系統類的耦合度

外觀模式要求一個子系統的外部與其內部的通訊通過一個統一的外觀物件進行,外觀類將客戶端與子系統的內部複雜性分隔開,使得客戶端只需要與外觀物件打交道,而不需要與子系統內部的很多物件打交道

外觀模式適用場景:

  • 為一個複雜子系統提供一個簡單介面
  • 客戶程式與抽象類的實現部分之間存在很大的依賴性
  • 需要構建一個層次結構的子系統時,使用外觀模式定義子系統中每層的入口點

優點:

  • 對客戶遮蔽子系統元件,減少了客戶處理的物件的數目,使子系統使用更加方便
  • 實現了子系統與客戶之間的鬆耦合關係,而子系統內部的功能元件往往是緊耦合的

2、舉例

典型應用是進行資料庫連線

一般每次對資料庫進行訪問時都要執行如下操作:先得到 connect例項,然後開啟 connect獲得連結,得到一個 statement,執行 sql語句進行查詢,得到查詢結果集

將步驟提取出來並封裝到一個類中,這樣每次執行資料庫訪問時只需要將必要的引數傳遞到類中即可

七、享元模式

1、楔子

面向物件技術可以很好地解決一些靈活性或可擴充套件性問題,但在很多情況下需要在系統中增加類和物件的個數。當物件數量太多時,將導致執行代價過高,帶來效能下降等問題

享元模式正是為解決這一類問題而誕生的,它通過共享技術實現相同或相似物件的重用

2、解析

享元模式:運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用

在享元模式中可以共享的相同內容稱為內部狀態,需要外部環境來設定的不能共享的內容稱為外部狀態。可以通過設定不同的外部狀態使得相同的物件具有不同的特徵,而相同的內部狀態是可以共享的

在享元模式中通常會出現工廠模式,需要建立一個享元工廠來負責維護一個享元池,用於儲存具有相同內部狀態的享元物件

在實際使用中,能夠共享的內部狀態是有限的,因此享元物件一般都設計為較小的物件,它所包含的內部狀態較少,這種物件也稱為細粒度物件

享元模式主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式

優點:大大減少物件的建立,降低系統的記憶體,使效率提高

缺點:

  • 提高了系統的複雜度
  • 需要分離出外部狀態和內部狀態,而且外部狀態具有固有化的性質,不應該隨著內部狀態的變化而變化,否則會造成系統的混亂

享元模式試用場景:

  • 系統有大量相似物件
  • 需要緩衝池的場景

3、舉例

import java.util.HashMap;
import java.util.Map;

public class Test {
    public static void main(String args[]){
        String yundong ="足球";
        for(int i=0;i<5;i++){
            TiYuGuan tiYuGuan = JianZhuFactory.getTyg(yundong);
            tiYuGuan.setName("合肥體育館");
            tiYuGuan.setShape("橢圓形");
            tiYuGuan.setYongtu("比賽");
            tiYuGuan.use();
        }
    }
}

//定義一個建築介面
interface Jianzhu{
    void use();
}

//建立一個體育館
class TiYuGuan implements Jianzhu{
    private String name;
    private String shape;
    private String yongtu;
    private String yundong;

    public TiYuGuan(String yundong) {
        this.yundong = yundong;
    }
    public String getYongtu() {
        return yongtu;
    }
    public void setYongtu(String yongtu) {
        this.yongtu = yongtu;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getShape() {
        return shape;
    }
    public void setShape(String shape) {
        this.shape = shape;
    }
    public String getYundong() {
        return yundong;
    }
    public void setYundong(String yundong) {
        this.yundong = yundong;
    }
    
    public void use() {
        System.out.println("該體育館被使用於"+yongtu+",專案為:"+ yundong+",場地形狀為:"+shape+",場地名稱為:"+name+",物件:"+this);
    }
}

//需要用到工廠類 建築工廠 可以產出體育館等建築
class JianZhuFactory{
    private static final Map<String,TiYuGuan> tygs =new HashMap<String,TiYuGuan>();
    public static TiYuGuan getTyg(String yundong){
        TiYuGuan tyg = tygs.get(yundong);
        if(tyg==null){
            tygs.put(yundong,new TiYuGuan(yundong));
        }
        return tygs.get(yundong);
    }
    public static int getSize(){
        return tygs.size();
    }
}

八、代理模式

1、楔子

在某些情況下,一個客戶不想或者不能直接引用一個物件,此時可以通過一個稱之為“代理”的第三者來實現間接引用。代理物件可以在客戶端和目標物件之間起到中介的作用,並且可以通過代理物件去掉客戶不能看到的內容和服務或者新增客戶需要的額外服務

2、解析

代理模式為其他物件提供一種代理以控制對這個物件的訪問

模式組成:

  1. 抽象主題角色:宣告真實主題和代理主題的共同介面
  2. 代理主題角色:內部包含對真實主題的引用,並且提供和真實主題角色相同的介面
  3. 真實主題角色:定義真實的物件

代理模式分為8種,這裡介紹常見的幾個:

  1. 遠端代理:為一個位於不同地址空間的物件提供一個局域代表物件
  2. 虛擬代理:使用一個資源消耗很大或者比較複雜的物件產生很大延遲時才建立
  3. 保護代理:控制對一個物件的訪問許可權
  4. 智慧引用代理:提供比對目標物件額外的服務。乳記錄訪問的流量,提供友情提示等

3、舉例

以論壇為例。註冊使用者擁有發帖,修改個人資訊,修改帖子等功能;遊客只能看別人發的帖子,沒有其他許可權。為了簡化程式碼,這裡只實現發帖許可權的控制

首先實現一個抽象主題角色 MyForum,其中定義了真實主題和代理主題的共同介面——發帖功能

public interface MyForum {
    public void AddFile();
}

真實主題角色和代理主題角色都要實現該介面,前者基本是將這個介面的方法內容填充起來,這裡不再贅述,主要實現代理主題角色

public class MyForumProxy implements MyForum {
    private RealMyForum forum = new RealMyForum() ;
    private int permission ; //許可權值
    
    public MyForumProxy(int permission) {
        this.permission = permission ;
    }
    //實現的介面
    public void AddFile() {
        //滿足許可權設定時才執行操作
        //Constants是一個常量類
        if(Constants.ASSOCIATOR = = permission) {
            forum.AddFile();
        }
        else
            System.out.println("You are not a associator of MyForum, please registe!");
    }
}