設計模式學習筆記(持續更新)

設計模式

設計模式的原則目的

設計模式是爲了讓程序有更好的:java

  1. 代碼重用性(相同的代碼,不用屢次編寫)
  2. 可讀性(編寫規範性,便於其餘程序員的閱讀和理解)
  3. 可擴展性(當須要增長新的功能時候,很是的方便和容易)
  4. 可靠性(當增長新的功能時候,對原來的功能沒有影響)
  5. 使程序呈現高內聚,低耦合的特性

設計模式七大設計原則

單一職責原則

基本介紹

對類來講,即一個類只負責一項職責。如類A負責兩個不一樣職責:職責1和職責2。當職責1需求變動而改變A時,也有可能致使職責2執行錯誤,所以須要把類A的粒度分解爲A1與A2。android

注意事項和細節
  1. 下降類的複雜度,一個類只負責一項職責。
  2. 提升類的可讀性,可維護性。
  3. 下降變動引發的風險。
  4. 一般狀況下,咱們應當遵循單一職責原則,只有邏輯足夠簡單,才能夠在代碼級違反單一職責原則;只有類中方法數量足夠少,能夠在方法級別保持單一職責原則。

接口隔離原則

基本介紹

客戶端不該該依賴它不須要的接口,即一個類對另外一個類的依賴應該創建在最小的接口上。接口儘可能細化,同時接口中的方法儘可能少。ios

依賴倒置原則

基本介紹

經過抽象(接口或者抽象類)使各個類或模塊的實現彼此獨立,不相互影響,實現模塊間的鬆耦合。程序員

中心思想

面向接口編程算法

注意事項和細節
  1. 低層模塊儘可能都要有抽象類或接口,或者二者都有,程序穩定性更好。
  2. 變量的聲明類型儘可能是抽象類或接口,這樣咱們的變量引用和實際對象間就存在一個緩衝層,利於程序的擴展和優化。
  3. 繼承是遵循裏式替換原則。

裏式替換原則

基本介紹

父類能出現的地方子類就能夠出現,並且替換成子類也不會出現任何錯誤或者異常,而使用者也無需知道父類仍是子類。數據庫

注意事項和細節
  1. 在使用繼承時,子類儘可能不要重寫父類的方法。
  2. 里氏替換原則告訴咱們,繼承實際上讓兩個類耦合性加強了,在適當的狀況下,能夠經過聚合、組合、依賴來解決問題。

開閉原則

基本介紹

軟件實體(包括類、模塊、功能等)應該對擴展開放,可是對修改關閉。編程

迪米特法則(最少知道法則)

基本介紹
  1. 一個類應該對本身須要耦合或調用的類知道得最少。
  2. 類與類的關係越密切,耦合度越大。
  3. 迪米特法則又稱最少知道法則,即一個類對本身依賴的類知道的越少越好
  4. 迪米特法則還有個更簡單的定義:只與直接的朋友通訊

UML類圖

UML基本介紹

UML--Unified modeling language(統一建模語言),是一種用於軟件系統分析和設計的語言工具,它用於幫助軟件開發人員進行思考和思路的結果。 ####UML類圖分類設計模式

  1. Dependency 表示依賴(使用)---->只要是在類中用到了對方,那麼他們之間就存在依賴關係。若是A用到了B,代表A依賴B,即A——>B。
  2. Association 表示關聯----->表示類與類之間的聯繫是依賴的特例,若是A用到了B,即表示A——>B。
  3. Generalization 表示泛化(繼承)---->實際上就是繼承關係,是依賴關係的特例。若是A繼承B,即代表A——>B。
  4. Realization 表示實現---->A類實現B接口,是依賴關係的特例。若是A實現B,即代表A——>B。
  5. Aggregation 表示聚合----->表示總體和部分的關係,總體與部分能夠分開是關聯關係的特例
  6. Composite 表示組合----->表示總體和部分的關係,總體與部分不能夠能夠分開是關聯關係的特例

設計模式概述

設計模式介紹

  1. 設計模式是程序員在面對同類軟件工程設計問題所總結出來的有用的經驗,模式不是代碼,而是某類問題的通用解決方案,設計模式(Design Pattern) 表明了最佳的實踐。
  2. 設計模式的本質提升軟件的維護性,通用性和擴展性,並下降軟件的複雜度。

設計模式的類型

  1. 建立型模式:單例模式、抽象工廠模式、原型模式、建造者模式、工廠模式。
  2. 結構性模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
  3. 行爲型模式:模板方法模式、命令模式、訪問者模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式。

單例設計模式

單例設計模式介紹

所謂類的單例設計模式,就是採起必定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,而且該類只提供一個取得其對象實例的方法(靜態方法)。安全

單例設計模式八種方式

  1. 餓漢式(靜態常量)
  2. 餓漢式(靜態代碼塊)
  3. 懶漢式(線程不安全)
  4. 懶漢式(線程安全,同步方法)
  5. 雙重檢查
  6. 靜態內部類
  7. 枚舉

餓漢式(靜態常量)

步驟
  1. 構造類私有化(防止外部調用產生新的對象)
  2. 類的內部建立對象
  3. 向外暴露一個靜態公共方法。
代碼
class Singleton {
    private Singleton() {

    }

    private static final Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}
複製代碼
優缺點
優勢

這種寫法比較簡單,就是在類裝載的時候就完成實例化,避免了線程同步的問題。多線程

缺點

在類加載的時候就完成了實例化,沒有達到懶加載的效果。若是從始至終都沒有用過這個實例,則會形成內存的浪費。

結論

這種單例設計模式可用,可是可能會出現內存浪費。

餓漢式(靜態代碼塊)

代碼
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;
    static {
        singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return singleton;
    }
}
複製代碼
優缺點

與餓漢式(靜態常量)的方式相似,只不過將類實例化的過程放在了靜態代碼塊中,優缺點與餓漢式(靜態常量)同樣。

懶漢式(線程不安全)

代碼
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;
    /** * 當調用的時候再建立對象,可是線程不安全。好比線程1執行到singleton爲空,此時線程2也執行到此爲止,而後就會產生兩個對象。 * @return */
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
複製代碼
優缺點
  1. 起到了懶加載(Lazy Loading)的效果,可是線程不安全,只能在單線程下使用。
  2. 若是在多線程下,一個線程進入了if (singleton == null)判斷語句塊,還將來得及往下執行,另外一個線程也經過了這個判斷語句,這是便會產生多個實例。所以在多線程的環境下不可使用這個方式。

懶漢式(線程安全,同步方法)

代碼
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
複製代碼
優缺點
  1. 解決了線程安全問題
  2. 效率低下,每一個線程在獲取類的實例的時候,都須要執行同步方法。
  3. 不推薦這種方法。

雙重檢查

代碼
class Singleton {
    private Singleton() {

    }
    //爲了不初始化操做的指令重排序
    private static volatile Singleton singleton;

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

            }
        }
        return singleton;
    }
}
複製代碼
優缺點
  1. Double-Check概念是多線程開發中常用到的,進行兩次檢查,就能夠保證線程安全了。
  2. 實例代碼只用執行一次,實現了懶加載。
  3. 線程安全,延遲加載,效率高。

靜態內部類

代碼
class Singleton {
    private Singleton() {

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

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
複製代碼
優缺點
  1. 這種方式採用了類裝載的機制來保證初始化實例時只有一個線程。
  2. 當調用getInstance方法,纔會裝載SingletonInstance類,從而完成Singleton的實例化。
  3. 類的靜態屬性只會在第一次加載類的時候初始化,JVM幫助咱們保證了線程的安全性。
  4. 推薦使用

枚舉

代碼
enum Singleton{
    INSTANCE;
}
複製代碼
分析
  1. 枚舉類實現其實省略了private的構造方法
  2. 枚舉類的域(field)實際上是相應的enum類型的一個實例對象。

單例模式在JDK應用的源碼分析

java.lang.Runtime就是經典的單例模式(餓漢式),代碼以下:

private static Runtime currentRuntime = new Runtime();

    /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
複製代碼

單例模式注意事項和細節說明

  1. 單例模式保證系統內存中該類只存在一個對象,節省了系統資源,對於一些須要頻繁建立銷燬的對象,使用單例模式能夠提升系統性能。
  2. 單利模式使用的場景,須要頻繁的進行建立和銷燬的對象、建立對象時耗時過多或耗費資源過多,但又常常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象。

工廠模式

簡單工廠模式

經過舉個例子說明一下: 我喜歡養寵物,抽象一個寵物父類或者接口。

/** * 描述要養的寵物 * * @author bf * @date 2019/9/18 17:01 */
public interface Animal {
    void getAnimal();
}
複製代碼

先養一個小狗吧:

public class Dog implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養了一隻小狗");
    }
}
複製代碼

再養一隻小貓:

public class Cat implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養了一隻小貓");
    }
}
複製代碼

再養一隻竹鼠:

public class Mouse implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養一隻竹鼠");
    }
}
複製代碼

準備工做完成了,咱們去寵物館(簡單工廠類),寵物種類以下:

public class SimpleAnimalFactory {
    private static final int TYPE_DOG = 1;
    private static final int TYPE_CAT = 2;
    private static final int TYPE_MOUSE = 3;

    public static Animal createAnimals(int type) {
        switch (type) {
            case TYPE_DOG:
                return new Dog();
            case TYPE_CAT:
                return new Cat();
            case TYPE_MOUSE:
                return new Mouse();
            default:
                return null;
                
        }
    }

}
複製代碼

簡單寵物館就提供三種動物,你說要什麼,他就給你什麼。這裏我要了一隻狗:

public static void main(String[] args) {
        Animal animals = SimpleAnimalFactory.createAnimals(1);
        animals.getAnimal();
    }
複製代碼

輸出以下:

養了一隻小狗

特色
  1. 簡單工廠模式是一個具體的類,並非接口抽象類。
  2. 因爲createAnimals()方法是靜態的,因此也稱之爲靜態工廠
缺點
  1. 擴展性差(好比我要添加一個寵物的種類,還須要修改工廠類的方法)。

經過反射建立的簡單工廠模式

使用反射實現簡單工廠:

public static <T> T createAnimals(Class<T> clz) throws Exception {
        T result = null;
        result = (T) Class.forName(clz.getName()).newInstance();
        return result;
    }
複製代碼

買寵物時調用:

Dog dog = SimpleReflectAnimalFactory.createAnimals(Dog.class);
 dog.getAnimal();
複製代碼
優缺點
  1. 當須要添加寵物的種類時,不須要修改工廠類的代碼
  2. Class.forName(clz.getName()).newInstance()調用的是無參構造方法,它和new對象是同樣的性質,而工廠方法應該用於複雜對象的初始化,當須要調用有參的構造方法時便無能爲力了。

工廠方法模式

工廠方法模式是簡單工廠的進一步深化,在工廠方法模式中,咱們再也不提供一個統一的工廠類來建立全部的對象,而是針對不一樣的對象提供不一樣的工廠。也就是說每個對象都有一個與之對應的工廠

定義

定義一個用於建立對象的接口,讓子類決定實例化哪個類。工廠方法使一個類的實例化延遲到其子類。

咱們能夠先經過實例來詳細解釋一下這個定義:

實例

依然使用養寵物的例子: 首先,編寫一個寵物接口:

public interface Animal {
    void getAnimal();
}
複製代碼

養狗的代碼:

public class Dog implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養了一隻小狗");
    }
}
複製代碼

養貓的代碼:

public class Cat implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養了一隻小貓");
    }
}

複製代碼

養竹鼠的代碼:

public class Mouse implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("養一隻竹鼠");
    }
}
複製代碼

如今咱們按照定義所說定義一個抽象的工廠接口GetAnimalFactory

public interface GetAnimalFactory {
    Animal getAnimal();
}

複製代碼

Dog加載器工廠

public interface GetAnimalFactory {
    Animal getAnimal();
}
複製代碼

Cat加載器工廠

public class GetCat implements GetAnimalFactory {
    @Override
    public Animal getAnimal() {
        return new Cat();
    }
}
複製代碼

Mouse加載器工廠

public class GetMouse implements GetAnimalFactory {
    @Override
    public Animal getAnimal() {
        return new Mouse();
    }
}
複製代碼

和簡單工廠對比一下,最根本的區別在於簡單工廠只有一個統一的工廠類,而工廠方法是針對每一個要建立的對象都會提供一個工廠類。

抽象工廠模式

定義

提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。(在抽象工廠模式中,每個具體工廠都提供了多個工廠方法用於產生多種不一樣類型的對象) 抽象工廠能夠劃 分爲4大部分:

  1. AbstractFactory(抽象工廠):聲明瞭一組用於建立對象的方法,注意是一組。
  2. ConcreteFactory(具體工廠):它實現了抽象工廠中聲明的建立對象的方法,生成一組具體對象。
  3. AbstractProduct(抽象產品):它爲每種對象聲明接口,在其中聲明瞭對象所具備的業務方法。
  4. ConcreteProduct(具體產品):它定義具體工廠生產的具體對象。
實例

如今須要作一款跨平臺的遊戲,須要兼容Android和Ios兩個移動操做系統,該遊戲針對每一個系統都設計了一套操做控制器(OperationController)和界面控制器(UIController),下面經過抽象工廠方式完成這款遊戲的架構設計。 先建立兩個抽象產品接口。

抽象操做控制器

public interface OperationController {
    void control();
}
複製代碼

抽象界面控制器

public interface UIController {
    void display();
}
複製代碼

而後完成這兩個系統平臺的具體操做控制器和界面控制器

Android平臺

public class AndroidOperationController implements OperationController {
    @Override
    public void control() {
        System.out.println("AndroidOperationController");
    }
}
複製代碼
public class AndroidUIController implements UIController {
    @Override
    public void display() {
        System.out.println("AndroidUIController");
    }
}
複製代碼

Ios

public class IosOperationController implements OperationController {
    @Override
    public void control() {
        System.out.println("IosOperationController");
    }
}

複製代碼
public class IosUIController implements UIController {
    @Override
    public void display() {
        System.out.println("IosUIController");
    }
}

複製代碼

下面定義一個抽象工廠,該工廠能夠建立OperationController和UIController

public interface SystemFactory {
    OperationController createOperationController();
    UIController createUIController();
}
複製代碼

而後在各平臺具體的工廠類中完成操做控制器和界面控制器的建立過程

Android

public class AndroidFactory implements SystemFactory {
    @Override
    public OperationController createOperationController() {
        return new AndroidOperationController();
    }

    @Override
    public UIController createUIController() {
        return new AndroidUIController();
    }
}
複製代碼

Ios

public class IOSFactory implements SystemFactory {
    @Override
    public OperationController createOperationController() {
        return new IosOperationController();
    }

    @Override
    public UIController createUIController() {
        return new IosUIController();
    }
}

複製代碼

方法調用:

public static void main(String[] args) {
        //android
        AndroidFactory androidFactory = new AndroidFactory();
        OperationController androidOperationController = androidFactory.createOperationController();
        UIController androidUiController = androidFactory.createUIController();
        androidOperationController.control();
        androidUiController.display();

        //ios
        IOSFactory iosFactory = new IOSFactory();
        OperationController iosFactoryOperation = iosFactory.createOperationController();
        UIController iosUiController = iosFactory.createUIController();
        iosFactoryOperation.control();
        iosUiController.display();
    }
複製代碼

針對不一樣平臺只經過建立不一樣的工廠對象就完成了操做和UI控制器的建立。

使用場景
  1. 和工廠方法同樣,客戶端不須要知道他所建立的對象的類。
  2. 須要一組對象共同完成某種功能是,而且可能存在多組對象完成不一樣功能的狀況。
  3. 系統接口穩定,不會頻繁的增長對象。由於一旦增長就須要修改原有代碼,不符合開閉原則

模板方法模式(Template Method Pattern)

定義

定義一個操做中的算法的框架,而將一些步驟延遲到子類中。使得子類能夠不改變一個算法的結構便可從新定義該算法的某些特定步驟。

舉例

舉個騎共享單車的例子,無論是騎哈羅單車仍是騎摩拜單車,咱們都要經歷掃碼解鎖、騎車、上鎖、支付這四個過程,所以能夠設計成一個抽象方法。

public abstract class RideBike {
    /** * 掃碼解鎖 */
    public abstract void unlock();

    /** * 騎車 */
    public abstract void ride();

    /** * 上鎖 */
    public abstract void lock();

    /** * 支付 */
    public abstract void pay();

    /** * 模擬使用共享單車 */
    public abstract void use();

}

複製代碼

上班去了,開始去騎共享單車,發現了一輛哈羅單車,開始使用。

public class RideHelloBike extends RideBike {
    @Override
    public void unlock() {
        System.out.println("掃碼解鎖哈羅單車");
    }

    @Override
    public void ride() {
        System.out.println("開啓騎哈羅單車");
    }

    @Override
    public void lock() {
        System.out.println("給哈羅單車上鎖");
    }

    @Override
    public void pay() {
        System.out.println("支付哈羅單車的費用");
    }

    @Override
    public void use() {
        this.unlock();
        this.ride();
        this.ride();
        this.pay();
    }

}

複製代碼

終於下班了,發現公司樓下的哈羅單車都被騎完了,那就騎摩拜單車吧。

public class RideMoBaiBike extends RideBike {
    @Override
    public void unlock() {
        System.out.println("掃碼解鎖摩拜單車");
    }

    @Override
    public void ride() {
        System.out.println("開啓騎摩拜單車");
    }

    @Override
    public void lock() {
        System.out.println("給摩拜單車上鎖");
    }

    @Override
    public void pay() {
        System.out.println("支付摩拜單車的費用");
    }

    @Override
    public void use() {
        this.unlock();
        this.ride();
        this.ride();
        this.pay();
    }

}

複製代碼

騎摩拜和騎哈羅都用到了一樣的方法,user()方法,代碼重複了,這是病得治,藥房就是使用模板方法模式。

模板方法模式相信你們都用過,就是抽象類裏面的方法,不須要改變的方法。那麼,下面咱們開始編寫代碼。

抽象類代碼

public abstract class AbstractClass {
    protected abstract void unlock();
    protected abstract void ride();
    protected abstract void lock();
    protected abstract void pay();

    protected final void use() {
        this.unlock();
        this.ride();
        this.lock();
        this.pay();
    }

}
複製代碼

這裏說明一下:使用protected,只有同包下的父子類能夠訪問。 final修飾的方法代表子類不能重寫父類方法。

實現類代碼

public class ScanBicycle extends AbstractClass {
    @Override
    protected void unlock() {
        System.out.println("掃碼解鎖");
    }

    @Override
    protected void ride() {
        System.out.println("騎車");
    }

    @Override
    protected void lock() {
        System.out.println("上鎖");
    }

    @Override
    protected void pay() {
        System.out.println("支付");
    }



    public static void main(String[] args) {
        ScanBicycle bicycle = new ScanBicycle();
        bicycle.use();
    }
}
複製代碼

運行結果以下:

public class ScanBicycle extends AbstractClass {
    @Override
    protected void unlock() {
        System.out.println("掃碼解鎖哈羅單車");
    }

    @Override
    protected void ride() {
        System.out.println("騎哈羅車");
    }

    @Override
    protected void lock() {
        System.out.println("上哈羅鎖");
    }

    @Override
    protected void pay() {
        System.out.println("支付哈羅費用");
    }
    
    public static void main(String[] args) {
        ScanBicycle bicycle = new ScanBicycle();
        bicycle.use();
    }
}

複製代碼
優缺點及使用場景
優勢
  1. 良好的封裝性。把共有不變的方法封裝在父類,而子類負責其餘須要改變方法的實現邏輯。即封裝不變部分,擴展可變部分
  2. 良好的擴展性。增長功能由子類實現基本方法拓展,符合單一職責原則和開閉原則。
  3. 複用代碼。
缺點
  1. 因爲是經過繼承實現代碼複用來改變算法,靈活度下降。
  2. 子類的執行影響父類的結果,增長代碼的閱讀難度。
使用場景
  1. 多個子類有公有方法,而且邏輯基本相同時。
  2. 重要、複雜的算法,能夠把核心算法設計爲模板方法,周邊的相關細節功能則由各個子類實現。
總結

模板方法看上去簡單,可是整個模式涉及到的都是面向對象設計的核心,好比繼承封裝、基於繼承代碼的複用、方法實現等等。當中還有涉及到一些關鍵詞的使用,也是咱們在Java編程中須要掌握的基礎。

建造者模式

相關文章
相關標籤/搜索