淺談設(shè)計模式

設(shè)計模式分類

總體來說設(shè)計模式分為三大類:

創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結(jié)構(gòu)型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。

其實還有兩類:并發(fā)型模式和線程池模式。用一個圖片來整體描述一下:


03.png

設(shè)計模式六大原則

總原則:開閉原則(Open Close Principle)

開閉原則就是說對擴(kuò)展開放,對修改關(guān)閉。在程序需要進(jìn)行拓展的時候,不能去修改原有的代碼,而是要擴(kuò)展原有代碼,實現(xiàn)一個熱插拔的效果。所以一句話概括就是:為了使程序的擴(kuò)展性好,易于維護(hù)和升級。想要達(dá)到這樣的效果,我們需要使用接口和抽象類等,后面的具體設(shè)計中我們會提到這點。

1、單一職責(zé)原則

不要存在多于一個導(dǎo)致類變更的原因,也就是說每個類應(yīng)該實現(xiàn)單一的職責(zé),如若不然,就應(yīng)該把類拆分。

2、里氏替換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向?qū)ο笤O(shè)計的基本原則之一。 里氏代換原則中說,任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP是繼承復(fù)用的基石,只有當(dāng)衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復(fù)用,而衍生類也能夠在基類的基礎(chǔ)上增加新的行為。里氏代換原則是對“開-閉”原則的補(bǔ)充。實現(xiàn)“開-閉”原則的關(guān)鍵步驟就是抽象化。而基類與子類的繼承關(guān)系就是抽象化的具體實現(xiàn),所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。—— From Baidu 百科

歷史替換原則中,子類對父類的方法盡量不要重寫和重載。因為父類代表了定義好的結(jié)構(gòu),通過這個規(guī)范的接口與外界交互,子類不應(yīng)該隨便破壞它。

3、依賴倒轉(zhuǎn)原則(Dependence Inversion Principle)

這個是開閉原則的基礎(chǔ),具體內(nèi)容:面向接口編程,依賴于抽象而不依賴于具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。

4、接口隔離原則(Interface Segregation Principle)

這個原則的意思是:每個接口中不存在子類用不到卻必須實現(xiàn)的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。

5、迪米特法則(最少知道原則)(Demeter Principle)

就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多么復(fù)雜,都應(yīng)該將邏輯封裝在方法的內(nèi)部,通過public方法提供給外部。這樣當(dāng)被依賴的類變化時,才能最小的影響該類。

最少知道原則的另一個表達(dá)方式是:只與直接的朋友通信。類之間只要有耦合關(guān)系,就叫朋友關(guān)系。耦合分為依賴、關(guān)聯(lián)、聚合、組合等。我們稱出現(xiàn)為成員變量、方法參數(shù)、方法返回值中的類為直接朋友。局部變量、臨時變量則不是直接的朋友。我們要求陌生的類不要作為局部變量出現(xiàn)在類中。

6、合成復(fù)用原則(Composite Reuse Principle)

原則是盡量首先使用合成/聚合的方式,而不是使用繼承。

Java的設(shè)計模式

順序根據(jù)Head First Design Patterns

一、策略模式

定義

策略模式定義了一系列的算法,并將每一個算法封裝起來,使每個算法可以相互替代,使算法本身和使用算法的客戶端分割開來,相互獨立。

特點

策略模式體現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計中非常重要的兩個原則:
1. 封裝變化的概念。
2. 編程中使用接口,而不是使用的是具體的實現(xiàn)類(面向接口編程)。

舉例

我們就以Java中的TreeSet為例,TreeSet僅僅知道它只是接收一個Comparator這種接口類型,但是具體是哪種實現(xiàn)類,TreeSet并不關(guān)心,實現(xiàn)類在真正的傳入TreeSet之前,TreeSet本身是不知道的,所以我們可以自己去實現(xiàn)Comparator接口,然后在實現(xiàn)類里面去封裝好我們自己的規(guī)則(這里的規(guī)則你可以當(dāng)做是算法),比如說我們要實現(xiàn)對一個集合的元素排序,但是到底是要升序排序還是降序排序,這個完全由我們來去控制,我們可以把這種變化的內(nèi)容封裝到自己的實現(xiàn)類中,真正運行的時候才知道具體的實現(xiàn)。

組成

  1. 抽象策略角色這個是一個抽象的角色,通常情況下使用接口或者抽象類去實現(xiàn)。對比來說,就是我們的Comparator接口。
  2. 具體策略角色包裝了具體的算法和行為。對比來說,就是實現(xiàn)了Comparator接口的實現(xiàn)一組實現(xiàn)類。
  3. 環(huán)境角色內(nèi)部會持有一個抽象角色的引用,給客戶端調(diào)用。對比來說,就是我們的TreeSet類。說明:TreeSet內(nèi)部一定會有一個策略類的一個成員變量,這樣做的目的在于可以當(dāng)我們在去創(chuàng)建TreeSet對象的時候,可以接收我們向TreeSet類中傳遞的具體的策略類。

編寫步驟

  1. 定義抽象策略角色(為策略對象定義一個公共的接口)
  2. 編寫具體策略角色(實際上就是實現(xiàn)上面定義的公共接口)
  3. 定義環(huán)境角色,內(nèi)部持有一個策略類的引用

案例

實現(xiàn)一個加減乘除的功能。

  1. 定義抽象策略角色

    /*    定義抽象策略角色
     *    類似于Comparator接口
     */
    public interface Strategy{
          /*
           *  實現(xiàn)了兩個數(shù)可以計算
           */
           public int calc(int num1,int num2);
    }
    
  2. 定義具體策略角色(本例子僅僅演示,只是定義加、減兩種具體策略)

    /*    
     *    定義加法策略
     */
    public class AddStrategy implements Strategy{
          /*
           *  實現(xiàn)clac方法,完成兩個數(shù)的和
           */
           public int calc(int num1,int num2){
                 return num1+num2;
           }
    }
    
    /*    
     *    定義減法策略
     */
    public class SubtractStrategy implements Strategy{
          /*
           *  實現(xiàn)clac方法,完成兩個數(shù)相減
           */
           public int calc(int num1,int num2){
                 return num1-num2;
           }
    }
    
  3. 環(huán)境角色

 /*
  *   環(huán)境角色
  *       類似于TreeSet
   */
 public class Environment{
    // 持有對策略類的引用
    private Strategy strategy;

    // 類似于TreeSet
    public Environment(Strategy strategy){
        this.strategy = strategy;
    }

    public int calulate(int a , int b){
        return strategy.calc(a,b);
    }
 }
  1. 測試類

    /*
     *  測試類
     */
     public class Test{
        public static void main(String[] args){
            Environment environment = new Environment(new AddStrategy());
            int result = environment.calulate(20,30);
            Ststem.out.println(result);
        }
     }
    

總結(jié)

策略模式的優(yōu)點:

  1. 策略模式的功能就是通過抽象、封裝來定義一系列的算法,使得這些算法可以相互替換,所以為這些算法定義一個公共的接口,以約束這些算法的功能實現(xiàn)。如果這些算法具有公共的功能,可以將接口變?yōu)槌橄箢悾瑢⒐补δ芊诺匠橄蟾割惱锩妗?/p>

  2. 策略模式的一系列算法是可以相互替換的、是平等的,寫在一起就是if-else組織結(jié)構(gòu),如果算法實現(xiàn)里又有條件語句,就構(gòu)成了多重條件語句,可以用策略模式,避免這樣的多重條件語句。

  3. 擴(kuò)展性更好:在策略模式中擴(kuò)展策略實現(xiàn)非常的容易,只要新增一個策略實現(xiàn)類,然后在使用策略實現(xiàn)的地方,使用這個新的策略實現(xiàn)就好了。

策略模式的缺點:

  1. 客戶端必須了解所有的策略,清楚它們的不同:
    如果由客戶端來決定使用何種算法,那客戶端必須知道所有的策略,清楚各個策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實現(xiàn)。

  2. 增加了對象的數(shù)量:
    由于策略模式將每個具體的算法都單獨封裝為一個策略類,如果可選的策略有很多的話,那對象的數(shù)量也會很多。

  3. 只適合偏平的算法結(jié)構(gòu):
    由于策略模式的各個策略實現(xiàn)是平等的關(guān)系(可相互替換),實際上就構(gòu)成了一個扁平的算法結(jié)構(gòu)。即一個策略接口下面有多個平等的策略實現(xiàn)(多個策略實現(xiàn)是兄弟關(guān)系),并且運行時只能有一個算法被使用。這就限制了算法的使用層級,且不能被嵌套。

參考自 策略模式詳解、深入解析策略模式

二、觀察者(Observer)模式

定義

     在對象之間定義了一對多的依賴,這樣一來,當(dāng)一個對象改變狀態(tài),依賴它的對象會收到通知并自動更新。

大白話

     其實就是發(fā)布訂閱模式,發(fā)布者發(fā)布信息,訂閱者獲取信息,訂閱了就能收到信息,沒訂閱就收不到信息。

結(jié)構(gòu)圖

01.png

組成

  • 抽象被觀察者角色:也就是一個抽象主題,它把所有對觀察者對象的引用保存在一個集合中,每個主題都可以有任意數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者角色。一般用一個抽象類和接口來實現(xiàn)。
  • 抽象觀察者角色:為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
  • 具體被觀察者角色:也就是一個具體的主題,在集體主題的內(nèi)部狀態(tài)改變時,所有登記過的觀察者發(fā)出通知。
  • 具體觀察者角色:實現(xiàn)抽象觀察者角色所需要的更新接口,一邊使本身的狀態(tài)與制圖的狀態(tài)相協(xié)調(diào)。

使用場景例子

有一個微信公眾號服務(wù),不定時發(fā)布一些消息,關(guān)注公眾號就可以收到推送消息,取消關(guān)注就收不到推送消息。

具體實現(xiàn)

  1. 定義一個抽象被觀察者接口
 /***
  * 抽象被觀察者接口
  * 聲明了添加、刪除、通知觀察者方法
  * @author charming
  *
  */
 public interface Observerable {

     public void registerObserver(Observer o);
     public void removeObserver(Observer o);
     public void notifyObserver();

 }
  1. 定義一個抽象觀察者接口
 /***
    * 抽象觀察者
    * 定義了一個update()方法,當(dāng)被觀察者調(diào)用notifyObservers()方法時,觀察者的update()方法會被回調(diào)。
    * @author charming
    *
    */
   public interface Observer {
       public void update(String message);
   }
  1. 定義被觀察者,實現(xiàn)了Observerable接口,對Observerable接口的三個方法進(jìn)行了具體實現(xiàn),同時有一個List集合,用以保存注冊的觀察者,等需要通知觀察者時,遍歷該集合即可。
 import java.util.ArrayList;
 import java.util.List;

 /**
    * 被觀察者,也就是微信公眾號服務(wù)
    * 實現(xiàn)了Observerable接口,對Observerable接口的三個方法進(jìn)行了具體實現(xiàn)
    * @author charming
    *
    */
 public class WechatServer implements Observerable {

     //注意到這個List集合的泛型參數(shù)為Observer接口,設(shè)計原則:面向接口編程而不是面向?qū)崿F(xiàn)編程
     private List<Observer> list;
     private String message;

     public WechatServer() {
         list = new ArrayList<Observer>();
     }

     @Override
     public void registerObserver(Observer o) {
   
         list.add(o);
     }

     @Override
     public void removeObserver(Observer o) {
         if(!list.isEmpty())
             list.remove(o);
     }

     //遍歷
     @Override
     public void notifyObserver() {
         for(int i = 0; i < list.size(); i++) {
             Observer oserver = list.get(i);
             oserver.update(message);
         }
     }

     public void setInfomation(String s) {
           this.message = s;
           System.out.println("微信服務(wù)更新消息: " + s);
           //消息更新,通知所有觀察者
           notifyObserver();
     }

}
  1. 定義具體觀察者,微信公眾號的具體觀察者為用戶User
 /**
    * 觀察者
    * 實現(xiàn)了update方法
    * @author charming
    *
    */
   public class User implements Observer {

       private String name;
       private String message;

       public User(String name) {
          this.name = name;
       }

       @Override
        public void update(String message) {
           this.message = message;
           read();
       }

       public void read() {
           System.out.println(name + " 收到推送消息: " + message);
       }
  }
  1. 編寫一個測試類
  • 首先注冊了三個用戶,ZhangSan、LiSi、WangWu。公眾號發(fā)布了一條消息"PHP是世界上最好用的語言!",三個用戶都收到了消息。

  • 用戶ZhangSan看到消息后頗為震驚,果斷取消訂閱,這時公眾號又推送了一條消息,此時用戶ZhangSan已經(jīng)收不到消息,其他用戶
    還是正常能收到推送消息。

public class Test {    
  public static void main(String[] args) {
    WechatServer server = new WechatServer();
    
    Observer userZhang = new User("ZhangSan");
    Observer userLi = new User("LiSi");
    Observer userWang = new User("WangWu");
    
    server.registerObserver(userZhang);
    server.registerObserver(userLi);
    server.registerObserver(userWang);
    server.setInfomation("PHP是世界上最好用的語言!");
    
    System.out.println("----------------------------------------------");
    server.removeObserver(userZhang);
    server.setInfomation("JAVA是世界上最好用的語言!");   
  }
}

測試結(jié)果


02.png

小結(jié)

  • 這個模式是松偶合的。改變主題或觀察者中的一方,另一方不會受到影像。
  • JDK中也有自帶的觀察者模式。但是被觀察者是一個類而不是接口,限制了它的復(fù)用能力。
  • 在JavaBean和Swing中也可以看到觀察者模式的影子。

三、裝飾者模式

   很煩,裝飾者模式?jīng)]保存到,由于篇幅過長我就不再貼了,放到后面與代理模式比較吧。

四、 工廠模式

概念

為創(chuàng)建對象提供過渡接口,以便將創(chuàng)建對象的具體過程屏蔽隔離起來,達(dá)到提高靈活性的目的。

類別

  • 簡單工廠模式Simple Factory:不利于產(chǎn)生系列產(chǎn)品;
  • 工廠方法模式Factory Method:又稱為多形性工廠;
  • 抽象工廠模式Abstract Factory:又稱為工具箱,產(chǎn)生產(chǎn)品族,但不利于產(chǎn)生新的產(chǎn)品;

這三種模式從上到下逐步抽象,并且更具一般性。GOF在《設(shè)計模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)。將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。

1. 簡單工廠模式

簡單工廠模式又稱靜態(tài)工廠方法模式。
從命名上就可以看出這個模式一定很簡單。它存在的目的很簡單:定義一個用于創(chuàng)建對象的接口。
在簡單工廠模式中,一個工廠類處于對產(chǎn)品類實例化調(diào)用的中心位置上,它決定哪一個產(chǎn)品類應(yīng)當(dāng)被實例化。

先來看看它的組成:

  • 工廠類角色:這是本模式的核心,含有一定的商業(yè)邏輯和判斷邏輯。在java中它往往由一個具體類實現(xiàn)。
  • 抽象產(chǎn)品角色:它一般是具體產(chǎn)品繼承的父類或者實現(xiàn)的接口。在java中由接口或者抽象類來實現(xiàn)。
  • 具體產(chǎn)品角色:工廠類所創(chuàng)建的對象就是此角色的實例。在java中由一個具體類實現(xiàn)。

簡單工廠模式又分為三種:

  1. 普通:

    就是建立一個工廠類,對實現(xiàn)了同一接口的一些類進(jìn)行實例的創(chuàng)建。首先看下關(guān)系圖:
    05.png

舉例如下:(我們舉一個發(fā)送郵件和短信的例子)首先,創(chuàng)建二者的共同接口:

 public interface Sender {  
    public void Send();  
}

其次,創(chuàng)建實現(xiàn)類:

public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}


public class SmsSender implements Sender {  
@Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}

最后,建工廠類:

public class SendFactory {  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("請輸入正確的類型!");  
            return null;  
        }  
    }  
}

我們來測試下:

public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}

輸出:this is sms sender!

  1. 多個方法

    是對普通工廠方法模式的改進(jìn),在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法,分別創(chuàng)建對象。關(guān)系圖:
    06.png

    將上面的代碼做下修改,改動下SendFactory類就行,如下:
public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  
  
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  

測試類如下:

public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}

輸出:this is mailsender!

  1. 多個靜態(tài)方法
    將上面的多個工廠方法模式里的方法置為靜態(tài)的,不需要創(chuàng)建實例,直接調(diào)用即可。
public class SendFactory {  
  
      public static Sender produceMail(){  
          return new MailSender();  
      }  
  
      public static Sender produceSms(){  
          return new SmsSender();  
      }  
}

public class FactoryTest {  

    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}

輸出:this is mailsender!

總體來說,工廠模式適合:凡是出現(xiàn)了大量的產(chǎn)品需要創(chuàng)建,并且具有共同的接口時,可以通過工廠方法模式進(jìn)行創(chuàng)建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正確創(chuàng)建對象,第三種相對于第二種,不需要實例化工廠類,所以,大多數(shù)情況下,我們會選用第三種——靜態(tài)工廠方法模式。

2. 工廠方法模式(Factory Method)

簡單工廠模式有一個問題就是,類的創(chuàng)建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進(jìn)行修改,這違背了閉包原則,所以,從設(shè)計角度考慮,有一定的問題,如何解決?就用到工廠方法模式,創(chuàng)建一個工廠接口和創(chuàng)建多個工廠實現(xiàn)類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。
07.png
public interface Sender {  
    public void Send();  
}

//兩個實現(xiàn)類:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}

public class SmsSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}

//工廠接口
public interface Provider {  
    public Sender produce();  
}

//兩個工廠類:
public class SendMailFactory implements Provider {  
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}

public class SendSmsFactory implements Provider{  
    @Override  
     public Sender produce() {  
       return new SmsSender();  
    }  
}

//測試類:
public class Test {  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
}

其實這個模式的好處就是,如果你現(xiàn)在想增加一個功能:發(fā)及時信息,則只需做一個實現(xiàn)類,實現(xiàn)Sender接口,同時做一個工廠類,實現(xiàn)Provider接口,就OK了,無需去改動現(xiàn)成的代碼。這樣做,拓展性較好!

3. 抽象工廠模式

為創(chuàng)建一組相關(guān)或者是相互依賴的對象提供一個接口,而不需要指定他們的具體實現(xiàn)類。


08.png

例如:
Q3和Q7有不同的輪胎、發(fā)動機(jī)、制動系統(tǒng)。雖然生產(chǎn)的零件不同,型號不同。但是根本上都有共同的約束,就是輪胎、發(fā)動機(jī)、制動系統(tǒng)。設(shè)計如下:
1. 需要一個抽象工廠,里面有三個接口分別為生產(chǎn)輪胎、發(fā)動機(jī)、制動系統(tǒng),抽象類
2. 需要三個抽象產(chǎn)品分別為輪胎、發(fā)動機(jī)、制動系統(tǒng),抽象接口
3. 需要實現(xiàn)上面的三個抽象接口,定義出每個接口不通的對象,比如:普通輪胎和越野輪胎
4. 需要兩個具體類繼承自上面的抽象類,實現(xiàn)具體的工廠,比如:生產(chǎn)Q3的工廠和生產(chǎn)Q7的工廠
5. 在客戶端new出對應(yīng)的具體工廠并調(diào)用對應(yīng)的生產(chǎn)方法

//1.抽象工廠
public abstract class CarFactory {
 /**
 * 生產(chǎn)輪胎
 * 
 * @return 輪胎
 * */
    public abstract ITire createTire();

/**
 * 生產(chǎn)發(fā)動機(jī)
 * 
 * @return 發(fā)動機(jī)
 * */
    public abstract IEngine createEngine();

/**
 * 生產(chǎn)制動系統(tǒng)
 * 
 * @return 制動系統(tǒng)
 * */
    public abstract IBrake createBrake();

}

//2.三個產(chǎn)品抽象接口
public interface ITire {
/**
 * 輪胎 
 */
    void tire();
}

public interface IEngine {
/**
 *發(fā)動機(jī) 
 */
    void engine();
}

public interface IBrake {
/**
 *制動系統(tǒng) 
 */
    void brake();
}

//3.根據(jù)抽象接口定義不同的對象
public class NormalBrake implements IBrake{
    @Override
    public void brake() {
        System.out.println("普通制動");
    }
}
public class SeniorBrake implements IBrake{
    @Override
    public void brake() {
        System.out.println("高級制動");
    }
}  //后面的定義省略。。。。。。。。。。。。。

//4.實現(xiàn)具體的工廠類
public class Q3Factory extends CarFactory{

    @Override
    public ITire createTire() {
        return new NormalTire();
    }

    @Override
    public IEngine createEngine() {
        return new DomesticEngine();
    }

    @Override
    public IBrake createBrake() {
        return new NormalBrake();
    }
}

//5.客戶端使用
public class Client {
    public static void main(String[] args) {
        //A車廠
        CarFactory factoryQ3 = new Q3Factory();
        factoryQ3.createTire().tire();
        factoryQ3.createEngine().engine();
        factoryQ3.createBrake().brake();
    }
}

//輸出
普通輪胎
國產(chǎn)發(fā)動機(jī)
普通制動

4. 工廠方法模式和抽象工廠模式區(qū)別

工廠方法模式:
一個抽象產(chǎn)品類,可以派生出多個具體產(chǎn)品類。
一個抽象工廠類,可以派生出多個具體工廠類。
每個具體工廠類只能創(chuàng)建一個具體產(chǎn)品類的實例。

抽象工廠模式:
多個抽象產(chǎn)品類,每個抽象產(chǎn)品類可以派生出多個具體產(chǎn)品類。
一個抽象工廠類,可以派生出多個具體工廠類。
每個具體工廠類可以創(chuàng)建多個具體產(chǎn)品類的實例,也就是創(chuàng)建的是一個產(chǎn)品線下的多個產(chǎn)品。

區(qū)別:
工廠方法模式只有一個抽象產(chǎn)品類,而抽象工廠模式有多個。
工廠方法模式的具體工廠類只能創(chuàng)建一個具體產(chǎn)品類的實例,而抽象工廠模式可以創(chuàng)建多個。
工廠方法創(chuàng)建 "一種" 產(chǎn)品,他的著重點在于"怎么創(chuàng)建",也就是說如果你開發(fā),你的大量代碼很可能圍繞著這種產(chǎn)品的構(gòu)造,初始化這些細(xì)節(jié)上面。也因為如此,類似的產(chǎn)品之間有很多可以復(fù)用的特征,所以會和模版方法相隨。
抽象工廠需要創(chuàng)建一些列產(chǎn)品,著重點在于"創(chuàng)建哪些"產(chǎn)品上,也就是說,如果你開發(fā),你的主要任務(wù)是劃分不同差異的產(chǎn)品線,并且盡量保持每條產(chǎn)品線接口一致,從而可以從同一個抽象工廠繼承。

對于java來說,你能見到的大部分抽象工廠模式都是這樣的:
---它的里面是一堆工廠方法,每個工廠方法返回某種類型的對象。

比如說工廠可以生產(chǎn)鼠標(biāo)和鍵盤。那么抽象工廠的實現(xiàn)類(它的某個具體子類)的對象都可以生產(chǎn)鼠標(biāo)和鍵盤,但可能工廠A生產(chǎn)的是羅技的鍵盤和鼠標(biāo),工廠B是微軟的。

這樣A和B就是工廠,對應(yīng)于抽象工廠;
每個工廠生產(chǎn)的鼠標(biāo)和鍵盤就是產(chǎn)品,對應(yīng)于工廠方法;

用了工廠方法模式,你替換生成鍵盤的工廠方法,就可以把鍵盤從羅技換到微軟。但是用了抽象工廠模式,你只要換家工廠,就可以同時替換鼠標(biāo)和鍵盤一套。如果你要的產(chǎn)品有幾十個,當(dāng)然用抽象工廠模式一次替換全部最方便(這個工廠會替你用相應(yīng)的工廠方法)

所以說抽象工廠就像工廠,而工廠方法則像是工廠的一種產(chǎn)品生產(chǎn)線

五、單例模式

定義

單例模式是一種常用的軟件設(shè)計模式,其定義是單例對象的類只能允許一個實例存在。

許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。比如在某個服務(wù)器程序中,該服務(wù)器的配置信息存放在一個文件中,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。

好處

1、某些類創(chuàng)建比較頻繁,對于一些大型的對象,這是一筆很大的系統(tǒng)開銷。
2、省去了new操作符,降低了系統(tǒng)內(nèi)存的使用頻率,減輕GC壓力。
3、有些類如交易所的核心交易引擎,控制著交易流程,如果該類可以創(chuàng)建多個的話,系統(tǒng)完全亂了。(比如一個軍隊出現(xiàn)了多個司令員同時指揮,肯定會亂成一團(tuán)),所以只有使用單例模式,才能保證核心交易服務(wù)器獨立控制整個流程。

基本的實現(xiàn)思路

單例模式要求類能夠有返回對象一個引用(永遠(yuǎn)是同一個)和一個獲得該實例的方法(必須是靜態(tài)方法,通常使用getInstance這個名稱)。

單例的實現(xiàn)主要是通過以下兩個步驟:

將該類的構(gòu)造方法定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造方法來實例化該類的對象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實例;
在該類內(nèi)提供一個靜態(tài)方法,當(dāng)我們調(diào)用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創(chuàng)建該類的實例并將實例的引用賦予該類保持的引用。

注意事項

單例模式在多線程的應(yīng)用場合下必須小心使用。如果當(dāng)唯一實例尚未創(chuàng)建時,有兩個線程同時調(diào)用創(chuàng)建方法,那么它們同時沒有檢測到唯一實例的存在,從而同時各自創(chuàng)建了一個實例,這樣就有兩個實例被構(gòu)造出來,從而違反了單例模式中實例唯一的原則。 解決這個問題的辦法是為指示類是否已經(jīng)實例化的變量提供一個互斥鎖(雖然這樣會降低效率)。

單例模式的八種寫法

1、餓漢式(靜態(tài)常量)[可用]

  public class Singleton {
      private final static Singleton INSTANCE = new Singleton();

      private Singleton(){}

      public static Singleton getInstance(){
           return INSTANCE;
      }
 }

優(yōu)點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
缺點:在類裝載的時候就完成實例化,沒有達(dá)到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內(nèi)存的浪費。

2、餓漢式(靜態(tài)代碼塊)[可用]

 public class Singleton {

   private static Singleton instance;

   static {
       instance = new Singleton();
   }

   private Singleton() {}

   public static Singleton getInstance() {
       return instance;
   }
 }

這種方式和上面的方式其實類似,只不過將類實例化的過程放在了靜態(tài)代碼塊中,也是在類裝載的時候,就執(zhí)行靜態(tài)代碼塊中的代碼,初始化類的實例。優(yōu)缺點和上面是一樣的。

3、懶漢式(線程不安全)[不可用]

 public class Singleton {

     private static Singleton singleton;

     private Singleton() {}

     public static Singleton getInstance() {
         if (singleton == null) {
             singleton = new Singleton();
         }
         return singleton;
     }
 }

這種寫法起到了Lazy Loading的效果,但是只能在單線程下使用。如果在多線程下,一個線程進(jìn)入了if (singleton == null)判斷語句塊,還未來得及往下執(zhí)行,另一個線程也通過了這個判斷語句,這時便會產(chǎn)生多個實例。所以在多線程環(huán)境下不可使用這種方式。

4、懶漢式(線程安全,同步方法)[不推薦用]

 public class Singleton {

     private static Singleton singleton;

     private Singleton() {}

     public static synchronized Singleton getInstance() {
         if (singleton == null) {
             singleton = new Singleton();
         }
         return singleton;
     }
 }

解決上面第三種實現(xiàn)方式的線程不安全問題,做個線程同步就可以了,于是就對getInstance()方法進(jìn)行了線程同步。

缺點:效率太低了,每個線程在想獲得類的實例時候,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步。而其實這個方法只執(zhí)行一次實例化代碼就夠了,后面的想獲得該類實例,直接return就行了。方法進(jìn)行同步效率太低要改進(jìn)。

5、懶漢式(線程安全,同步代碼塊)[不可用]

 public class Singleton {

     private static Singleton singleton;

     private Singleton() {}

     public static Singleton getInstance() {
         if (singleton == null) {
             synchronized (Singleton.class) {
                 singleton = new Singleton();
             }
         }
         return singleton;
     }
 }

由于第四種實現(xiàn)方式同步效率太低,所以摒棄同步方法,改為同步產(chǎn)生實例化的的代碼塊。但是這種同步并不能起到線程同步的作用。跟第3種實現(xiàn)方式遇到的情形一致,假如一個線程進(jìn)入了if (singleton == null)判斷語句塊,還未來得及往下執(zhí)行,另一個線程也通過了這個判斷語句,這時便會產(chǎn)生多個實例。

6、雙重檢查[推薦用]

 public class Singleton {

     private static volatile Singleton singleton;

     private Singleton() {}

     public static Singleton getInstance() {
         if (singleton == null) {
             synchronized (Singleton.class) {
                 if (singleton == null) {
                     singleton = new Singleton();
                 }
             }
         }
         return singleton;
     }
 }

Double-Check概念對于多線程開發(fā)者來說不會陌生,如代碼中所示,我們進(jìn)行了兩次if (singleton == null)檢查,這樣就可以保證線程安全了。這樣,實例化代碼只用執(zhí)行一次,后面再次訪問時,判斷if (singleton == null),直接return實例化對象。

優(yōu)點:線程安全;延遲加載;效率較高。

7、靜態(tài)內(nèi)部類[推薦用]

 public class Singleton {

     private Singleton() {}

     private static class SingletonInstance {
         private static final Singleton INSTANCE = new Singleton();
     }

     public static Singleton getInstance() {
         return SingletonInstance.INSTANCE;
     }
 }

這種方式跟餓漢式方式采用的機(jī)制類似,但又有不同。兩者都是采用了類裝載的機(jī)制來保證初始化實例時只有一個線程。不同的地方在餓漢式方式是只要Singleton類被裝載就會實例化,沒有Lazy-Loading的作用,而靜態(tài)內(nèi)部類方式在Singleton類被裝載時并不會立即實例化,而是在需要實例化時,調(diào)用getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的實例化。

類的靜態(tài)屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時,別的線程是無法進(jìn)入的。

優(yōu)點:避免了線程不安全,延遲加載,效率高。

8、枚舉[推薦用]

 public enum Singleton {
     INSTANCE;
     public void whateverMethod() {

     }
 }

借助JDK1.5中添加的枚舉來實現(xiàn)單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象??赡苁且驗槊杜e在JDK1.5中才添加,所以在實際項目開發(fā)中,很少見人這么寫過。

總結(jié)

1、單例模式理解起來簡單,但是具體實現(xiàn)起來還是有一定的難度。

2、synchronized關(guān)鍵字鎖定的是對象,在用的時候,一定要在恰當(dāng)?shù)牡胤绞褂茫ㄗ⒁庑枰褂面i的對象和過程,可能有的時候并不是整個對象及整個過程都需要鎖)。

采用類的靜態(tài)方法,實現(xiàn)單例模式的效果,也是可行的,此處二者有什么不同?

首先,靜態(tài)類不能實現(xiàn)接口。(從類的角度說是可以的,但是那樣就破壞了靜態(tài)了。因為接口中不允許有static修飾的方法,所以即使實現(xiàn)了也是非靜態(tài)的)

其次,單例可以被延遲初始化,靜態(tài)類一般在第一次加載是初始化。之所以延遲加載,是因為有些類比較龐大,所以延遲加載有助于提升性能。

再次,單例類可以被繼承,他的方法可以被覆寫。但是靜態(tài)類內(nèi)部方法都是static,無法被覆寫。

最后一點,單例類比較靈活,畢竟從實現(xiàn)上只是一個普通的Java類,只要滿足單例的基本需求,你可以在里面隨心所欲的實現(xiàn)一些其它功能,但是靜態(tài)類不行。

參考自單例模式的八種寫法比較

六、命令模式

定義

命令模式(Command Pattern):將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進(jìn)行參數(shù)化;對請求排隊或者記錄請求日志,以及支持可撤銷的操作。命令模式是一種對象行為型模式,其別名為動作(Action)模式或事務(wù)(Transaction)模式。
Command Pattern: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

舉例

假設(shè)某個公司需要設(shè)計一個多用功能的遙控器。基本的需求如下:

該遙控器有可以控制風(fēng)扇,白熾燈,熱水器等等的多對開關(guān),而且可能還有其他的電器,暫時不做其功能,但是希望可以保留接口,用的時間可以方便的擴(kuò)展。

除上面的需求之外,還需要有個按鈕,可以撤銷上一步的操作?;竟δ苋缦聢D:


09.png

在設(shè)計遙控器時,風(fēng)扇,白熾燈,熱水器的開關(guān)方法已經(jīng)定義好,其名字各不相同。不妨設(shè)置其方法為如下:


10.png

由于各種電器的開關(guān)方法都不一樣,而且還存在一個待擴(kuò)展的電器,如果沒有學(xué)習(xí)命名模式之前,我們在設(shè)置擴(kuò)展的開關(guān)時,會出現(xiàn)的問題是什么呢?假設(shè)現(xiàn)在有電視,冰箱還可能會用到遙控器,那么我們會在最后一個開關(guān)上寫if else,當(dāng)然如果哪一天有多了一個大門也加入了我們的遙控的行列,這樣我們繼續(xù)加if else ,很顯然隨著電器的高速發(fā)展,會有多個需要遙控可以控制的。

舉個例子,如果我們是需要遙控的客戶,現(xiàn)在有一款遙控如果有遙控可以進(jìn)行擴(kuò)展,一種是可以擴(kuò)展指定類型的,像上面的,只能再去擴(kuò)展電視和冰箱中的一種,偶爾有一天你看到隔壁鄰居的門,也可以使用遙控了,所以你去把你的高級遙控器,拿到擴(kuò)展店時,擴(kuò)展工程師說了,現(xiàn)在只能擴(kuò)展電視和冰箱,不支持對大門的遙控擴(kuò)展.

我們肯定是希望,可以自由的擴(kuò)展,大門可以使用遙控了,就對大門擴(kuò)展,車門使用遙控了,就對車門擴(kuò)展……其實也就是一種松耦合的實現(xiàn)。

我們可以先定義好我們的風(fēng)扇,白熾燈,熱水器。然后定義其分別的開關(guān)命令,每個命令都有自己對應(yīng)的電器引用,而且會在命令的Excute中包裝電器的開或者關(guān),最后需要把命令安裝到遙控器上面,在遙控器上每個按鈕都對應(yīng)有自己的激發(fā)方法,其代碼如下:

namespace RemoteControl
{
    class Program
    {
        static void Main(string[] args)
        {
            //家中的電器
            Fan fan=new Fan();
            Light light=new Light();
            Heater heater=new Heater();

            //電器分別對應(yīng)的命令
            FanOffCommand fanOffCommand=new FanOffCommand(fan);
            FanOnCommand fanOnCommand=new FanOnCommand(fan);
            LightOnCommand lightOnCommand=new LightOnCommand(light);
            LightOffCommand lightOffCommand=new LightOffCommand(light);
            HeaterOnCommand heaterOnCommand=new HeaterOnCommand(heater);
            HeaterOffCommand heaterOffCommand=new HeaterOffCommand(heater);
            RemoteControl remoteControl = new RemoteControl();

            //設(shè)置遙控器
            remoteControl.SetCommand(0, fanOnCommand, fanOffCommand);
            remoteControl.SetCommand(1, lightOnCommand, lightOffCommand);
            remoteControl.SetCommand(2, heaterOnCommand, heaterOffCommand);
            //分別測試遙控器的命令
            remoteControl.OnButtonWasPress(1);
            remoteControl.OffButtonWasPress(1);
            remoteControl.OnButtonWasPress(0);
            remoteControl.OffButtonWasPress(0);
            Console.ReadKey();
        }
    }

    /// <summary>
    /// 風(fēng)扇類
    /// </summary>
   public class Fan
    {
        public void FanOn()
        {
            Console.WriteLine("風(fēng)扇開了");
        }
        public void FanOff()
        {
            Console.WriteLine("風(fēng)扇關(guān)了");
        }
    }
    /// <summary>
    /// 燈類
    /// </summary>
   public class Light
   {
       public void LightOn()
       {
           Console.WriteLine("燈亮了");
       }
       public void LightOff()
       {
           Console.WriteLine("燈滅了");
       }
   }
    /// <summary>
    /// 熱水器類
    /// </summary>
   public class Heater
   {
       public void HeaterOn()
       {
           Console.WriteLine("加熱中");
       }
       public void HeaterOff()
       {
           Console.WriteLine("停止加熱");
       }
   }

    /// <summary>
    /// 命令接口
    /// </summary>
   public interface ICommand
   {
       void Excute();
   }

   public class FanOnCommand : ICommand
   {
       Fan fan;
       public FanOnCommand(Fan fan)
       {
           this.fan = fan;
       }
        public void Excute()
        {
            this.fan.FanOn();
        }
    }

   public class FanOffCommand : ICommand
   {
       Fan fan;
       public FanOffCommand(Fan fan)
       {
           this.fan = fan;
       }
        public void Excute()
        {
            this.fan.FanOff();
        }
   }

   public class LightOnCommand : ICommand
   {
       Light light;
       public LightOnCommand(Light light)
       {
           this.light = light; 
       }
       public void Excute()
       {
           light.LightOn();
       }

   }

   public class LightOffCommand : ICommand
   {
       Light light;
       public LightOffCommand(Light light)
       {
           this.light = light;
       }
       public void Excute()
       {
           this.light.LightOff();
       }
   }

   public class HeaterOnCommand : ICommand
   {
       Heater heater;
       public HeaterOnCommand(Heater heater)
       {
           this.heater = heater;
       }
       public void Excute()
       {
           this.heater.HeaterOn();
       }
   }

   public class HeaterOffCommand : ICommand
   { 
       Heater heater;
       public HeaterOffCommand(Heater heater)
       {
           this.heater = heater;
       }
       public void Excute()
       {
           this.heater.HeaterOff();
       }
   }

   public class NoCommand : ICommand
   {
       public void Excute()
       { }
   }

   public class RemoteControl
   {
      private ICommand[] onCommands;
      private ICommand[] offCommands;
      public RemoteControl()
      {
         ICommand noCommand=new NoCommand();
          onCommands = new ICommand[4];
          offCommands = new ICommand[4];
          for (int i = 0; i < 4; i++)
          {
              onCommands[i] = noCommand;
              offCommands[i] = noCommand;
          }
      }

      public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
      {
          onCommands[slot] = onCommand;
          offCommands[slot] = offCommand;
      }
      public void OnButtonWasPress(int slot)
      {
          onCommands[slot].Excute();
      }
      public void OffButtonWasPress(int slot)
      {
          offCommands[slot].Excute();
      }
     
   }
}

這樣基本上就實現(xiàn)了我們的現(xiàn)有的三種電器的遙控。需要注意的是,在開始初始化遙控器時,對每個命令初始化成了NoCommand,也就是什么都不執(zhí)行。在命令的初始化經(jīng)常使用,同時這也解決了我們的在擴(kuò)展前什么都不做的難題。看了風(fēng)扇,白熾燈,熱水器的遙控實現(xiàn),進(jìn)一步的擴(kuò)展任何的電器,相信都不是什么難事。但是還有個功能沒有實現(xiàn),就是撤銷到上一步的操作,接下來我們就來實現(xiàn)撤銷操作。

撤銷操作就想我們遙控中的返回一樣?;旧暇褪菬袅林?,突然按了一下關(guān)燈,然后再按一下返回鍵,燈就亮了。其他的電器同樣的道理。下面先看一下燈的撤銷原理,命令除了執(zhí)行外還有一個撤銷,所以我們需要先都命令的接口添加一個方法。

/// <summary> 
/// 命令接口 
/// </summary> 
public interface ICommand 
{ 
    void Excute(); 
    void Undo(); 
}

對于開燈需要做的修改如下:

public class LightOnCommand : ICommand 
   { 
       Light light; 
       public LightOnCommand(Light light) 
       { 
           this.light = light; 
       } 
       public void Excute() 
       { 
           light.LightOn(); 
       } 
       /// <summary> 
       /// 調(diào)用命令的反命令 
       /// </summary> 
       public void Undo() 
       { 
           light.LightOff(); 
       } 
   }

其他命令同理,代碼會在源碼中一并給出。也就是每個命令都有自己的反命令,在Undo方法里面也就是調(diào)用反命令的Excute方法。每當(dāng)按下一個按鈕時,就去記錄其命令的名稱,如果按撤銷的話,就執(zhí)行命名的Undo方法。下面給出主要代碼:

public void OnButtonWasPressed(int slot) 
   { 
       onCommands[slot].Excute(); 
       backCommand=onCommands[slot]; 
   } 
   public void OffButtonWasPressed(int slot) 
   { 
       offCommands[slot].Excute(); 
       backCommand = offCommands[slot]; 
   } 
   public void BackButtonWasPressed() 
   { 
       backCommand.Undo(); 
   }

以上是對遙控器對命令的撤銷,需要注意兩點1、通過記住命令執(zhí)行之前的狀態(tài),然后去恢復(fù)到原來的狀態(tài)。2、在每次執(zhí)行之后要記住執(zhí)行的那個命令。也即記住命令和記住狀態(tài)。

除了一次執(zhí)行一個命令和撤銷一個命令,當(dāng)然還可以一次執(zhí)行多個命令。下面給出主要代碼:

public class MutlipleCommand : ICommand 
   { 
       ICommand[] commands; 
       ICommand[] backCommands; 
       public MutlipleCommand(ICommand[] commands) 
       { 
           this.commands = commands; 
           backCommands = new ICommand[commands.Length]; 
       }


       public void Excute() 
       { 
           for (int i = 0; i < commands.Length; i++) 
            { 
              commands[i].Excute(); 
              backCommands[i] = commands[i]; 
            } 
           
       }

       public void Undo() 
       { 
           for (int i = 0; i < commands.Length; i++) 
           { 
               backCommands[i].Undo(); 
           } 
       } 
   }

總結(jié)

11.png

命令模式主要通過中介Command實現(xiàn)了發(fā)出命令者和命令的執(zhí)行者,也即Invoke類和Receiver的松耦合。

參考自命令模式

七、適配器模式

定義

適配器模式將某個類的接口轉(zhuǎn)換成客戶端期望的另一個接口表示,目的是消除由于接口不匹配所造成的類的兼容性問題。主要分為三類:類的適配器模式、對象的適配器模式、接口的適配器模式。

1、類的適配器模式

12.png

核心思想就是:有一個Source類,擁有一個方法,待適配,目標(biāo)接口是Targetable,通過Adapter類,將Source的功能擴(kuò)展到Targetable里,看代碼:

public class Source {  
  
    public void method1() {  
        System.out.println("this is original method!");  
    }  
}

public interface Targetable {  
  
    /* 與原類中的方法相同 */  
    public void method1();  
  
    /* 新類的方法 */  
    public void method2();  
} 

public class Adapter extends Source implements Targetable {  
  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
}

//Adapter類繼承Source類,實現(xiàn)Targetable接口,下面是測試類:
public class AdapterTest {  
  
    public static void main(String[] args) {  
        Targetable target = new Adapter();  
        target.method1();  
        target.method2();  
    }  
}


輸出:

this is original method!
this is the targetable method!

這樣Targetable接口的實現(xiàn)類就具有了Source類的功能。

2、對象的適配器模式

基本思路和類的適配器模式相同,只是將Adapter類作修改,這次不繼承Source類,而是持有Source類的實例,以達(dá)到解決兼容性的問題??磮D:


13.png

只需要修改Adapter類的源碼即可:

public class Wrapper implements Targetable {  
  
    private Source source;  
      
    public Wrapper(Source source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
  
    @Override  
    public void method1() {  
        source.method1();  
    }  
}

public class AdapterTest {  
  
    public static void main(String[] args) {  
        Source source = new Source();  
        Targetable target = new Wrapper(source);  
        target.method1();  
        target.method2();  
    }  
}
輸出與第一種一樣,只是適配的方法不同而已。

3、接口的適配器模式

第三種適配器模式是接口的適配器模式,接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當(dāng)我們寫該接口的實現(xiàn)類時,必須實現(xiàn)該接口的所有方法,這明顯有時比較浪費,因為并不是所有的方法都是我們需要的,有時只需要某一些,此處為了解決這個問題,我們引入了接口的適配器模式,借助于一個抽象類,該抽象類實現(xiàn)了該接口,實現(xiàn)了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯(lián)系,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行??匆幌骂悎D:


14.png

這個很好理解,在實際開發(fā)中,我們也常會遇到這種接口中定義了太多的方法,以致于有時我們在一些實現(xiàn)類中并不是都需要??创a:

public interface Sourceable {  
      
    public void method1();  
    public void method2();  
}

public abstract class Wrapper2 implements Sourceable{  
      
    public void method1(){}  ;
    public void method2(){}  ;
}

public class SourceSub1 extends Wrapper2 {  
    public void method1(){  
        System.out.println("the sourceable interface's first Sub1!");  
    }  
}

public class SourceSub2 extends Wrapper2 {  
    public void method2(){  
        System.out.println("the sourceable interface's second Sub2!");  
    }  
}

public class WrapperTest {  
  
    public static void main(String[] args) {  
        Sourceable source1 = new SourceSub1();  
        Sourceable source2 = new SourceSub2();  
          
        source1.method1();  
        source1.method2();  
        source2.method1();  
        source2.method2();  
    }  
}

測試輸出:

the sourceable interface's first Sub1!
the sourceable interface's second Sub2!

達(dá)到了我們的效果!

總結(jié)

類的適配器模式:當(dāng)希望將一個類轉(zhuǎn)換成滿足另一個新接口的類時,可以使用類的適配器模式,創(chuàng)建一個新類,繼承原有的類,實現(xiàn)新的接口即可。

對象的適配器模式:當(dāng)希望將一個對象轉(zhuǎn)換成滿足另一個新接口的對象時,可以創(chuàng)建一個Wrapper類,持有原類的一個實例,在Wrapper類的方法中,調(diào)用實例的方法就行。

接口的適配器模式:當(dāng)不希望實現(xiàn)一個接口中所有的方法時,可以創(chuàng)建一個抽象類Wrapper,實現(xiàn)所有方法,我們寫別的類的時候,繼承抽象類即可。

八、模板方法模式(略)

九、迭代器與組合模式(略)

十、狀態(tài)模式(略)

十一、代理模式(極其重要)

與裝飾者模式的區(qū)別

裝飾器模式關(guān)注于在一個對象上動態(tài)的添加方法,然而代理模式關(guān)注于控制對對象的訪問。換句話 說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個對象的具體信息。因此,當(dāng)使用代理模式的時候,我們常常在一個代理類中創(chuàng)建一個對象的實例。并且,當(dāng)我們使用裝飾器模 式的時候,我們通常的做法是將原始對象作為一個參數(shù)傳給裝飾者的構(gòu)造器。

舉個例子

例如:有嬰兒,嬰兒會吃飯和走動,如以下類

//嬰兒類
public class Child implements Human
{
    public void eat()
    {
        System.out.println("eat something....");
    }

    @Override
    public void run()
    {
        System.out.println("Child run very slow");
    }
}

突然有一天,家長發(fā)現(xiàn)不行,孩子不能隨便吃東西,而且吃飯前一定要洗手。但是孩子太小(被委托方),不會自己洗手。家長(Client 端)又沒法照顧孩子。那簡單,找個保姆照顧孩子! 讓保姆類和嬰兒類共同實現(xiàn)同一個接口,讓保姆類全程管理小孩,同時在家長眼里,只要看到保姆在幫孩子洗手就可以了。于是,有以下內(nèi)容。

//保姆類
public class BabySitter implements Human
{

    @Override
    public void eat()
    {

    }

    @Override
    public void run()
    {

    }

}

現(xiàn)在保姆已經(jīng)有了,孩子也有了,怎么把孩子跟保姆關(guān)聯(lián)起來。讓保姆給相應(yīng)的孩紙洗手。于是保姆類更改如下

//保姆類
public class BabySitter implements Human
{
    private Human human;

    public BabySitter(Human human)
    {
        this.human = human;
    }

    @Override
    public void eat()
    {
        // 添加washHand的方法
        this.washHandForChild();
        human.eat();
    }

    @Override
    public void run()
    {

    }

    public void washHandForChild()
    {
        System.out.println("help the child to wash his hands");
    }
}

保姆與嬰兒類關(guān)聯(lián)

好,那么家長就是給孩紙找了個保姆助手(裝飾器),讓他附加了一些嬰兒做不了事。同時家長也沒有強(qiáng)迫孩紙自己學(xué)會洗手(不更改Child類)

//客戶端
public class Client
{
    public static void main(String[] args)
    {
        Human human = new BabySitter(new Child());
        human.eat();
    }
}

家長客戶端代碼

以上就是一個簡單的裝飾模式,來看一下這一塊完整的類圖。


15.png

裝飾模式的一個很重要特點就是,在客戶端可以看到抽象對象的實例,如Human human = new BabySitter(new Child()); 因為裝飾模式通過聚合方式,把內(nèi)容整合到裝飾類里面了。

裝飾者模式能夠使用裝飾類對抽象對象進(jìn)行裝飾。假如來了個OldMan類手腳不利索。保姆類BabySitter同樣能夠勝任這個OldMan的飯前洗手操作。

代理模式類圖如下:

16.png

由該類圖可知,以上BabySitter代碼應(yīng)該如下:

//保姆類
public class BabySitter implements Human
{    
    private Child child;

    public BabySitter()
    {
        child = new Child();
    }

    @Override
    public void eat()
    {
        // 添加washHand的方法
        this.washHandForChild();
        human.eat();
    }

    @Override
    public void run()
    {

    }

    public void washHandForChild()
    {
        System.out.println("help the child to wash his hands");
    }
}

//客戶端
public class Client
{
    public static void main(String[] args)
    {
        Human human = new BabySitter();
        human.eat();
    }
}

裝飾者模式和代理模式的最后運行的結(jié)果都是一樣的,由代理模式代碼可知,客戶端不關(guān)心代理類了哪個類。但代碼控制了客戶端對委托類的訪問??蛻舳舜a表現(xiàn)為 Human human = new BabySitter( );

所以資料上都說了,裝飾模式主要是強(qiáng)調(diào)對類中代碼的拓展,而代理模式則偏向于委托類的訪問限制。兩者都一樣擁有抽象角色(接口)、真實角色(委托類)、代理類 。

由于代理類實現(xiàn)了抽象角色的接口,導(dǎo)致代理類無法通用。如有天,一個有錢人養(yǎng)了只小猩猩,他要一個保姆在猩猩吃東西前,幫猩猩洗手....保姆根本不懂猩猩的特性(跟猩猩類不是同一類型的,保姆屬于Human類,而猩猩可能屬于Animal類型。),但洗手這個方法是不變的,用水洗。能不能找一個代理說既可以照看人吃飯前洗手也可以照看猩猩吃飯前洗手?

要實現(xiàn)這種功能,必須讓代理類與特定的接口分離。在代理的時候能夠了解每個委托的特性,這就有可能了。這時候動態(tài)代理就出現(xiàn)了。

動態(tài)代理
關(guān)于動態(tài)代理模式里面有兩種實現(xiàn),一種是jdk實現(xiàn),一種是cglib來實現(xiàn)。
下面來整jdk來實現(xiàn)動態(tài)代理的Java實例。
jdk動態(tài)代理模式里面有個攔截器的概念,在jdk中,只要實現(xiàn)了InvocationHandler這個接口的類就是一個攔截器類。
還使用了些反射的相關(guān)概念。
攔截器的概念不了解沒關(guān)系,假如寫了個請求到action,經(jīng)過攔截器,然后才會到action。然后繼續(xù)有之后的操作。
攔截器就像一個過濾網(wǎng),一層層的過濾,只要滿足一定條件,才能繼續(xù)向后執(zhí)行。
攔截器的作用:控制目標(biāo)對象的目標(biāo)方法的執(zhí)行。

攔截器的具體操作步驟:
1.引入類:目標(biāo)類和一些擴(kuò)展方法相關(guān)的類。
2.賦值:調(diào)用構(gòu)造函數(shù)給相關(guān)對象賦值
3.合并邏輯處理:在invoke方法中把所有的邏輯結(jié)合在一起。最終決定目標(biāo)方法是否被調(diào)用。

總結(jié)

動態(tài)代理靜態(tài)代理的區(qū)別,代理涉及到兩個關(guān)聯(lián)詞代理類和委托類。靜態(tài)代理一個代理類針對一個委托類!動態(tài)代理一個代理類可利用反射機(jī)制代理多個委托類

我們根據(jù)加載被代理類的時機(jī)不同,將代理分為靜態(tài)代理和動態(tài)代理。如果我們在代碼編譯時就確定了被代理的類是哪一個,那么就可以直接使用靜態(tài)代理;如果不能確定,那么可以使用類的動態(tài)加載機(jī)制,在代碼運行期間加載被代理的類這就是動態(tài)代理,比如RPC框架和Spring AOP機(jī)制。

參考自代理模式和裝飾模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計模式分類 總體來說設(shè)計模式分為三大類:創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原...
    lifeline丿毅閱讀 1,347評論 0 2
  • 簡述: 本節(jié)主要是針對行為型模式,下面為大家一一道來。 行為型設(shè)計模式: 13. Interpreter(解釋器)...
    CyrusChan閱讀 251評論 0 0
  • [設(shè)計模式六大原則] 1、單一職責(zé)原則(Single Responsibility Principle) 定義 :...
    PersonChen_QJ閱讀 923評論 1 1
  • 工廠模式 首先需要先說一下工廠模式,工廠模式根據(jù)抽象程度的不同分為三種:簡單工廠模式(也叫靜態(tài)工廠模式)、工廠方法...
    Airing閱讀 1,358評論 0 7
  • 這可能是到現(xiàn)在為止辦活動最難忘的一次經(jīng)歷了,雖然只是一個參與者,承擔(dān)著小小的任務(wù),但在迎新晚會結(jié)束的那一刻還是享受...
    怡然趴閱讀 294評論 0 0

友情鏈接更多精彩內(nèi)容