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

  • 2019 年 10 月 20 日
  • 筆記

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

可以分為兩類:

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

根據「組合復用原則」,在系統中盡量使用關聯關係來替代繼承關係,因此大部分結構型模式都是對象結構型模式

一、適配器模式

1、楔子

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

2、解析

適配器模式將一個類的接口轉換成客戶希望的另外一個接口,使接口不兼容的類可以一起工作

模式組成:

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

在適配器模式中可以定義一個包裝類,包裝不兼容接口的對象,這個包裝類指的就是適配器,它所包裝的對象就是適配者,即被適配的類

也就是說,當客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。因此適配器可以使由於接口不兼容而不能交互的類可以一起工作

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

3、舉例

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

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

添加繪製圓的需求之前,類圖如下

KelX3n.png

添加圓的繪製後,類圖如下

Ke1SBT.png

由圖可知,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!");      }  }