鳥瞰設計模式

寫完後倒回來看一下這篇文章,感受有點亂糟糟的,權當本身的筆記了。本文未完待續。html

我的認爲,所謂的設計模式,就是前人總結的面向接口編程中的最佳實踐,爲何是接口呢?由於設計模式中通常涉及三個類:java

  • 客戶端
  • 接口
  • 接口實現類

客戶端(通常指業務代碼)持有接口,設計模式就是對接口進行實現。本文不講UML類圖,由於代碼實踐中的設計模式跟文獻中學院派的設計模式有些是有點不一樣的,學院派理論很完美但複雜,代碼中若是寫太複雜的可能後期維護成本大。本文主要說明Java中23種設計模式在工做中的應用,同時結合Spring框架或JDK源碼等經常使用的代碼,說說設計模式在Java中的例子。git

總結概括

Java設計模式主要分爲三種類型:建造者模式、結構性模式、行爲型模式。
恰好對應於對象的生命週期:實例化、實例之間的關係、實例的使用。
Spring框架的設計也符合這個順序:IoC(建造者)、AOP(結構型)、其餘如Data Access(行爲型)。 github

20191105085652.png
如上圖,我已經把經常使用設計模式背景色改成黑色了。Java設計模式一共23種,上圖中標紅的簡單工廠模式不是GoF總結出來的23種設計模式之一。一句話總結以下:

  • 建造型:
    • 工廠模式:根據傳入參數實例化對象,如BeanFactory.getBean("helloBean");
    • 單例模式:構造方法私有化,提供靜態方法獲取實例。如Singleton.getInstance();
    • 建立者模式:根據屬性命令方法,該方法返回一個自己Builer對象,build()方法建造一個實例,如HelloBean helloBean = new HelloBeanBuilder().name("donggua").age(26).build()
  • 結構型:
    • 代理模式:代理對象中持有被代理對象的實例,加強接口的抽象方法。如:HelloIntf helloIntf = new HelloIntfProxy();
    • 裝飾模式:裝飾對象中經過參數傳入持有被裝飾對象的實例,加強接口的抽象方法。如HelloIntf helloIntf = new HelloIntfDecorator(new HelloIntfImpl());
    • 適配器模式:鏈接兩個不一樣的接口,實現左邊接口,持有右邊接口對象。如:class USBAdapter implements USBSerialPort; class TFCard; USBAdapter{ void readSerial(tfCard.readTF()) };
  • 行爲型:
    • 模板方法模式:抽象類中定義好方法順序,子類繼承後順序不變。如:class HelloTemplate { void display(){ preHello(), hello(); postHello();} };
    • 策略模式:抽象類定義好策略,子類實現,客戶端選擇具體子類,如:class PayPolicy{ pay(); }; class AliPayPolicy { pay(){}; }; PayPolicy pp = new AliPayPolicy()
    • 觀察者模式:被觀察對象持有觀察對象的列表,事件觸發時遍歷通知。如:class Observable{ List<Observer> observerList; };
    • 責任鏈模式:抽象類中定義級別和本抽象類的引用,事件觸發時,級別大於本節點級別的不處理。如:class Chain{ int level; Chain nextNode; do(level){ if(level < this.level) dosomething(); nextNode.do(); }; };

建造型

建立型模式,就是建立對象的模式,抽象了實例化的過程。主要是爲了規範化new實例的過程。好比,工廠模式能夠對對象進行統一管理,修改一個配置就能夠實例化出單例的對象或者多例對象。Spring最基本的IoC容器就是工廠模式。算法

工廠模式

工廠模式通常類名以Factory結尾,工廠模式三劍客:簡單工廠模式、工廠方法模式、抽象工廠模式。數據庫

簡單工廠模式(經常使用)

簡單工廠模式又叫靜態工廠方法模式,工廠類提供一個靜態方法,對不一樣的入參返回不一樣的具體實現類。如slf4j + logback的實現。其中。編程

private Logger logger = LoggerFactory.getLogger(HelloTest.class); 
複製代碼
  • 客戶端:業務代碼
  • 接口:Logger
  • 接口實現:定義一個LoggerFactory工廠,在logback中返回具體的實例

工廠方法模式

連載中。。。設計模式

抽象工廠模式

連載中。。。api

單例模式(經常使用)

單例模式是 Java 中最簡單的設計模式之一。其涉及到一個單一的類,該類負責建立本身的對象(通常使用getInstance()方法),同時確保只有單個對象被建立。單例有懶漢、餓漢、雙重檢查鎖,靜態內部類等多種實現方式。JDK和Spring源碼中暫未找到比較經常使用的實例。雙重檢查鎖代碼以下:安全

package me.zebin.demo.javaio;

public class Singleton {

    // 這裏的變量務必使用volatile關鍵字,不然會有線程安全問題
    public static volatile Singleton singleton = null;
    // 構造方法私有化,禁止直接new
    private Singleton(){

    }
    // 獲取惟一實例的方法
    public static Singleton getInstance(){
        if (null != singleton) {
            return singleton;
        }
        // 多個線程在此等待
        synchronized (Singleton.class) {
            // 只有第一個線程執行了new代碼,第二個線程條件不成立
            if (null == singleton) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
複製代碼

建立者模式(經常使用)

建立者模式(建造者模式)將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。說人話,建造者和工廠模式不一樣之處在於,工廠模式只須要說明我要哪一個對象,工廠就能幫你造,而建立者則須要提供原材料。建立者一個典型的建造過程就是鏈式建造,最後調用build()方法觸發。JDK源碼中的例子就是StringBuilder類。

StringBuilder sb = new StringBuilder("Hello").append("world").sppend("!").toString();
複製代碼

普通的JavaBean也能夠很容易實現建造者模式,使用lombok插件,在Bean上加@Builder就可給你建立一個建造者模式的類。而後你使用建造者的鏈式寫法來建立一個實例,如:

Person person = new Person().age(26).name("dongua").city("guangzhou").build();
複製代碼

lombok是提升編碼效率的很強大的插件,不瞭解的能夠搜索一下。

原型模式

連載中。。。

結構型

結構型模式是爲解決怎樣組裝現有的類,設計它們的交互方式,從而達到實現必定的功能目的。結構型模式包容了對不少問題的解決。例如,Spring的AOP就是解決類間的結構融合,使用的時結構型的代理模式。

代理模式

代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。可是JDK和Spring源碼中大多使用動態代理,雖然靜態代理實現比較簡單,可是在實際項目中咱們須要爲每一個類都寫一個代理類,須要寫不少重複冗餘的代碼,不利於代碼的解耦與擴展。爲一個類作代理主要是爲了加強該類的功能,咱們這裏只討論靜態代理,JDK和Spring中沒找到合適的靜態代理的例子,這裏手動寫一個吧。

以下,定義一個Car接口,只有一個drive()方法,全部品牌的車都繼承自該接口,普通的奔馳車實現了Car接口的drive()方法,經過定義奔馳車的代理,加強車的功能。爲了方便,我寫在了一個類裏。

package me.zebin.demo.javaio;

import org.junit.Test;

public class ProxyTest {

    @Test
    public void client() {
        // 面向接口編程,持有一個Car接口
        Car car = new BenzCarProxy();
        car.drive();
    }

    // Car接口,Car能drive
    interface Car{
        void drive();
    }

    // 普通的奔馳車,能開
    class BenzCar implements Car{

        @Override
        public void drive() {
            System.out.println("我是奔馳車,我在開車呢老鐵...");
        }
    }

    // 代理,加強型的奔馳
    class BenzCarProxy implements Car{

        // 持有普通奔馳引用
        BenzCar benzCar = new BenzCar();
        @Override
        public void drive() {
            System.out.println("普通車進化,超級車,能飛了兄dei...");
            benzCar.drive();
        }
    }

}

複製代碼
  • 客戶端:業務代碼client
  • 接口:Car接口
  • 接口實現:BenzCar,BenzCarProxy。

裝飾模式

裝飾模式容許向一個現有的對象添加新的功能,同時又不改變其結構。因此,裝飾模式是加強類的功能的,這點和代理模式很像。相同之處是,代理類或裝飾類都持有源類的引用,而不一樣點是,代理類在內部直接new出一個被代理對象,裝飾類在初始化時須要傳入被代理類做爲參數。因此裝飾模式纔是在源類自己上增長功能,源類變強了。而代理模式只是在代理類在源類上作一些操做,實際上源類並無加強。但整個代理類看起來是加強了。
Java中的源碼好比io流就大量使用裝飾模式,如:

FileInputStream  fis = new FileInputStream(inputStream);
複製代碼

其中FileInputStream加強了InputStream的功能。

適配器模式

適配器模式(Adapter Pattern)是做爲兩個不兼容的接口之間的橋樑。例如,讀卡器是做爲內存卡和筆記本之間的適配器。您將內存卡插入讀卡器,再將讀卡器插入筆記本,這樣就能夠經過筆記原本讀取內存卡。代碼以下:

package me.zebin.demo.javaio;

import org.junit.Test;

public class AdapterTest {

    // USB計算機串口,能夠讀USB設備
    interface USBSerialPort{
        void readUSB();
    }

    // 手機的tf卡
    class TFCard{

        // TFCard能夠被讀取
        public void readTFCard(){
            System.out.println("我是TFCard,數據正在輸出,被讀取中...");
        }
    }

    // TFCard適配器是一種USB串口設備,因此實現了該接口,USBSerialPort和TFCard接口不兼容,須要適配器轉接
    class TFAdapater implements USBSerialPort{

        // TF適配器是專門適配TFCard的,因此持有一個TFCard
        TFCard tfCard = new TFCard();

        // 計算機讀取TFCard適配器時,適配器讀取TFCard中的內容
        @Override
        public void readUSB() {
            tfCard.readTFCard();
        }
    }

    @Test
    public void computerClient(){

        // USB接口插上適配器
        USBSerialPort usbSerialPort = new TFAdapater();
        usbSerialPort.readUSB();
    }

}
複製代碼

固然,USB串口上能夠插各類各樣的其餘設備,如鍵盤等,只要你作一個適配器就能夠了。

JDK源碼中,早期日誌框架使用的是java自帶的JUL,後來出現了log4j,這二者都是具體的實現,若是一個項目中這兩種日誌都使用了,那麼就很難控制日誌的配置,好比,調整日誌級別等。因而出現了common-logging這種面向接口的設計,客戶端只須要持有common-logging中的Logger引用,commom-logging對接JUL或者log4j,固然這JUL和log4j具體的接口是不一致的,common-logging就使用了適配器來對接二者。以下圖部分源碼。

20191104144503.png

還有JDK IO中的InputStreamReader也屬於適配器模式,InputStreamReader適配器兩端鏈接了Reader和InputStream。

門面模式

門面模式是對象的結構模式,外部與一個子系統的通訊必須經過一個統一的門面對象進行。門面模式提供一個高層次的接口,使得子系統更易於使用。說白了,就是提供給外部使用的要儘量簡單,外部系統可能完成一件事要調用你多個api,這時可使用門面模式,將這些步驟整合在一塊兒,提供一個門面接口便可。例如,slf4j和common-logging,他們並不具體實現寫日誌的功能,而只是一個門面,咱們只須要和門面交互,就能夠實現打日誌的功能,而不須要和具體的log4j,jul等交互。

橋接模式

橋接模式將抽象部分與它的實現部分分離,使它們均可以獨立地變化。舉個例子,汽車Car有品牌Brand、駕駛模式DriveMode兩種模式。品牌有寶馬、奔馳等,駕駛模式又有手動擋和自動擋等,至關於在橋的一端有寶馬、奔馳,另外一端有自動擋、手動擋。若是以品牌爲實現類,爲駕駛模式建立子類,如寶馬手動擋、寶馬自動擋。那麼將會創造出大量的類。這個時候,能夠將DriveMode類型不做爲Brand的子類,而是與Brand平級,Brand持有DriveMode的實例,即Brand橋接到DriveMode。如此,新增一個Brand就不會影響DriveMode,二者獨立變化。
JDK中的例子,就是jdbc,然而jdbc的實現看起來跟橋接模式定義有點不同,我分不清,如今先不討論了。

行爲型模式

在對象的結構和對象的建立問題都解決了以後,就剩下對象的行爲問題了,若是對象的行爲設計的好,那麼對象的行爲就會更清晰,它們之間的協做效率就會提升。好比Spring中JdbcTemplate即便來控制數據庫訪問流程,使用的是行爲型的模板方法模式。

策略模式

在策略模式(Strategy Pattern)中,一個類的行爲或其算法能夠在運行時更改。策略模式比較簡單,通俗的講,就是客戶端持有一個策略抽象,並實例化須要的具體策略,進行調用便可。
策略模式常常用來解決if-else嵌套過多的問題。
JDK中使用策略模式的有ThreadPoolExecutor中的拒絕策略,總共四個拒絕策略實現了RejectedExecutionHandler,子類實現其拒絕算法rejectedExecution(),使用時能夠本身選擇不一樣算法。這就是策略模式。

模板方法模式

在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。通俗的講,模板方法就是一個思路很清晰的智者,他將第一步作啥第二步作啥都清清楚楚的羅列出來,等子類來繼承,從新子類本身須要的步驟。
Spring源碼中jdbcTemplate就是使用模板方法模式,他將鏈接數據庫的步驟放置在模板中,子類去從新自定義的步驟。

責任鏈模式

責任鏈模式(Chain of Responsibility Pattern)爲請求建立了一個接收者對象的鏈。在這種模式中,一般每一個接收者都包含對另外一個接收者的引用。若是一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。通俗的講,就是每一個子類都給自身定一個級,並持有下一個父引用,每次執行抽象方法先判斷級別,級別不夠的轉入鏈中的下一個節點。

觀察者模式

當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。好比,當一個對象被修改時,則會自動通知它的依賴對象。通俗的說,就是被觀察者持有觀察者對象列表,當某個事件發生時,會遍歷列表執行方法。

參考資料

23種設計模式要在一篇文章中寫完,篇幅有點太長了,花了一週有些設計模式還沒寫到。心力有點交瘁,感受越寫到後面越簡略,只能當成本身的筆記來看了。先把文章post出來吧,改成連載模式。

相關文章
相關標籤/搜索