1. 程式人生 > >Java設計模式(個人整理)

Java設計模式(個人整理)

  設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使程式碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。專案中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

  前言:本文只對各種設計模式的概念和基本實現原理進行分析,示例程式碼不一定是安全可執行的

設計模式的分類

  總體來說設計模式分為三大類:

  建立型模式,共五種:單例模式、工廠方法模式、抽象工廠模式、建造者模式、原型模式。

  結構型模式,共七種:介面卡模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

  行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。

設計模式的六大原則

  1. 開閉原則(Open Close Principle)

      開閉原則就是說對擴充套件開放,對修改關閉。在程式需要進行拓展的時候,不能去修改原有的程式碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程式的擴充套件性好,易於維護和升級。想要達到這樣的效果,我們需要使用介面和抽象類,後面的具體設計中我們會提到這點。

  2. 里氏代換原則(Liskov Substitution Principle)

      里氏代換原則(Liskov Substitution Principle LSP)面向物件設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟體單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。

  3. 依賴倒轉原則(Dependence Inversion Principle)

      這個是開閉原則的基礎,具體內容:真對介面程式設計,依賴於抽象而不依賴於具體。

  4. 介面隔離原則(Interface Segregation Principle)

      這個原則的意思是:使用多個隔離的介面,比使用單個介面要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟體的設計思想,從大型軟體架構出發,為了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

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

      為什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模組相對獨立。

  6. 合成複用原則(Composite Reuse Principle)

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

Java的23種設計模式

1. 單例模式(Singleton)

  單例模式是一種常用的設計模式。在Java應用中,單例物件能保證在一個JVM中,該物件只有一個例項存在。這樣的模式有幾個好處:

  1. 某些類建立比較頻繁,對於一些大型的物件,這是一筆很大的系統開銷。

  2. 省去了new操作符,降低了系統記憶體的使用頻率,減輕GC壓力。

程式碼示例(有關單例模式執行緒安全的問題在原文參考裡面有詳細講解,具體使用案例可參考Calendar類。):

public class Singleton {

    //1.將構造方法私有化,不允許外部直接建立物件
    private Singleton() {
    }

    //2.建立類的唯一例項
    private static Singleton singleton = null;

    //3.提供一個用於獲取例項的方法
    public static Singleton getInstance() {
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

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

  工廠方法模式是類的建立模式,又叫做虛擬構造子(Virtual Constructor)模式或者多型性工廠(Polymorphic Factory)模式。工廠方法模式的用意是定義一個建立產品物件的工廠介面,將實際建立工作推遲到子類中。

程式碼示例(關於工廠方法的改進請看原文):

1.建立一個二者的共同介面

public interface Sender {

    public void Send();

}

2.建立兩個實現類

public class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("this is mail sender!");
    }

}
public class SmsSender implements Sender {

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

}

3.建立工廠類

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 Test {

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

}

3. 抽象工廠模式(Abstract Factory)

  抽象工廠模式有一個問題就是,類的建立依賴工廠類,也就是說,如果想要拓展程式,必須對工廠類進行修改,這違背了開閉原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到抽象工廠模式,建立多個工廠類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的程式碼。

程式碼示例:

1.建立一個二者的共同介面

public interface Sender {

    public void Send();

}

2.建立兩個實現類

public class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("this is mail sender!");
    }

}
public class SmsSender implements Sender {

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

}

3.建立兩個工廠類

public class SendMailFactory implements Provider {

    @Override
    public Sender produce() {
        return new MailSender();
    }

}
public class SendSmsFactory implements Provider {

    @Override
    public Sender produce() {
        return new SmsSender();
    }

}

4.建立一個提供介面

public interface Provider {

    public Sender produce();

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Provider provider = new SendMailFactory();
        Sender sender = provider.produce();
        sender.Send();
    }

}

  這裡如果要擴充套件的話,只需要寫一個實現類,實現Sender介面,再寫一個工廠類,實現Provider介面就可以了,不用修改原有的程式碼,符合開閉原則。

4. 建造者模式(Builder)

  建造者模式是物件的建立模式。建造模式可以將一個產品的內部表象(internal representation)與產品的生產過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品物件。

程式碼示例:

1.建立產品和部件介面

public interface Product { }
public interface Part { }

2.建立一個建造者介面

public interface Builder {

    //建立部件A  比如建立汽車車輪
    void buildPartA();

   //建立部件B 比如建立汽車方向盤
   void buildPartB();

   //建立部件C 比如建立汽車發動機
   void buildPartC();

   //返回最後組裝成品結果 (返回最後裝配好的汽車)
   //成品的組裝過程不在這裡進行,而是轉移到下面的Director類中進行,
   //從而實現瞭解耦過程和部件
   Product getResult();

}

3.建立一個具體建造者類

public class ConcreteBuilder implements Builder {

    //複雜產品的各個部件
    Part partA, partB, partC;

    public void buildPartA() {
        //這裡是具體如何構建partA的程式碼
    };

    public void buildPartB() {
        //這裡是具體如何構建partB的程式碼
    };

    public void buildPartC() {
        //這裡是具體如何構建partB的程式碼
    };

    public Product getResult() {
        //返回最後組裝成品結果
    };

}

4.建立導演者類

public class Director {

    private Builder builder;

   public Director( Builder builder ) {
      this.builder = builder;
   }

   // 將部件partA partB partC最後組成複雜物件
   //這裡是將車輪 方向盤和發動機組裝成汽車的過程
   public void construct() {
        builder.buildPartA();
       builder.buildPartB();
       builder.buildPartC();
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director( builder );
        director.construct();
        Product product = builder.getResult();
    }

}

  建造者模式利用一個導演者物件和具體建造者物件一個個地建造出所有的零件,從而建造出完整的產品物件。建造者模式將產品的結構和產品的零件的建造過程對客戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的。

5. 原型模式(Prototype)

  原型模式是物件的建立模式。通過給出一個原型物件來指明所有建立的物件的型別,然後用複製這個原型物件的辦法創建出更多同類型的物件。

程式碼示例:

1.建立抽象原型介面

public interface Prototype {

    /**
     * 克隆自身的方法(方法名可以隨意更改)
     * @return 一個從自身克隆出來的物件
     */
    public Prototype clone();

}

2.建立具體原型類

public class ConcretePrototype implements Prototype, Cloneable {

    private int field;

    public Prototype clone() {
        //淺克隆
        Prototype prototype = (Prototype) super.clone();
        return prototype;
    }

}

更多關於淺克隆和深克隆的分析請看原文

6. 介面卡模式(Adapter)

  介面卡模式把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。

程式碼示例:

1.建立一個三相插座介面

public interface ThreePlugIf {

    //使用三相電流供電
    public void powerWithThree();

}

2.建立一個二相插座類

public class TowPlug {

    public void powerWithTwo() {
        System.out.println("使用二相電流供電");
    }

}

3.建立一個筆記本類

public class NoteBook {

    private ThreePlugIf plug;

    public NoteBook(ThreePlugIf plug) {
        this.plug = plug;
    }

    //使用插座充電
    public void charge() {
        plug.powerWithThree();
    }

}

4.建立二相轉三相介面卡類

public class TwoPlugAdapter implements ThreePlugIf {

    private TwoPlug plug;

    public TwoPlugAdapter(TwoPlug plug) {
        this.plug = plug;
    }

    @Override
    public void powerWithThree() {
        plug.powerWithTwo();
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        TowPlug two = new TowPlug();
        ThreePlugIf three = new TwoPlugAdapter(two);
        NoteBook nb = new NoteBook(three);
        nb.charge();
    }

}

  例子中通過介面卡類的轉換使得擁有三相插頭的筆記本可以使用二相插座充電。介面卡模式也分為好幾種方式實現,更多方式可在原文中檢視。

7. 裝飾模式(Decorator)

  裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴充套件物件的功能,是繼承關係的一個替代方案。

程式碼示例:

1.建立一個抽象構件類

public interface Sourceable {

    public void method();

}

2.建立被裝飾角色類

public class Source implements Sourceable {

    @Override
    public void method() {
        System.out.println("the original method!");
    }

}

3.建立裝飾類

public class Decorator implements Sourceable {

    private Sourceable source;

    public Decorator(Sourceable source) {
        super();
        this.source = source;
    }

    @Override
    public void method() {
        System.out.println("before decorator!");
        source.method();
        System.out.println("after decorator!");
   }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Sourceable source = new Source();
        Sourceable obj = new Decorator(source);
        obj.method();
    }

}

優點:

  • 裝飾模式與繼承關係的目的都是要擴充套件物件的功能,但是裝飾模式可以提供比繼承更多的靈活性。裝飾模式允許系統動態決定“貼上”一個需要的“裝飾”,或者除掉一個不需要的“裝飾”。繼承關係則不同,繼承關係是靜態的,它在系統執行前就決定了。

  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。

缺點:

  • 產生過多相似的物件,不易排錯。

使用場景:

  • 需要擴充套件一個類的功能。

  • 動態的為一個物件增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。)

8. 代理模式(Proxy)

  所謂代理,就是一個人或者機構代表另一個人或者機構採取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。AOP就是使用的代理模式。

程式碼示例:

1.建立抽象物件類

public abstract class AbstractObject {

    //操作方法
    public abstract void operation();

}

2.建立目標物件類

public class RealObject extends AbstractObject {

    @Override
    public void operation() {
        System.out.println("目標物件執行操作");
    }

}

3.建立代理物件類

public class ProxyObject extends AbstractObject {

    RealObject realObject = new RealObject();

    @Override
    public void operation() {
        //呼叫目標物件之前可以做相關操作
        System.out.println("before");    
        realObject.operation();     
        //呼叫目標物件之後可以做相關操作
        System.out.println("after");
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        AbstractObject obj = new ProxyObject();
        obj.operation();
    }

}

  從上面的例子可以看出代理物件將客戶端的呼叫委派給目標物件,在呼叫目標物件的方法之前跟之後都可以執行特定的操作。

9. 外觀模式(Facade)

  外觀模式為子系統中的各類(或結構與方法)提供一個簡明一致的介面,隱藏子系統的複雜性,使子系統更加容易使用。

程式碼示例:

1.建立兩個子系統功能類

public class FeatureA {

    public void methodA() {
        System.out.println("執行A功能的方法");
    }

}
public class FeatureB {

    public void methodB() {
        System.out.println("執行B功能的方法");
    }

}

2.建立外觀類

public class Facade {

    private FeatureA featureA;
    private FeatureB featureB;

    public void method() {
        featureA.methodA();
        featureB.methodB();
        System.out.println("執行外觀類的方法");
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.method();
    }

}

  例子中,客戶端只需要建立一個外觀類物件,然後呼叫外觀類提供的方法,隱藏了具體功能的實現,也免去了建立多個功能類物件的麻煩。

優點:

  • 鬆散耦合,門面模式鬆散了客戶端與子系統的耦合關係,讓子系統內部的模組能更容易擴充套件和維護。

  • 簡單易用,門面模式讓子系統更加易用,客戶端不再需要了解子系統內部的實現,也不需要跟眾多子系統內部的模組進行互動,只需要跟門面類互動就可以了。

  • 更好的劃分訪問層次,通過合理使用Facade,可以幫助我們更好地劃分訪問的層次。有些方法是對系統外的,有些方法是系統內部使用的。把需要暴露給外部的功能集中到門面中,這樣既方便客戶端使用,也很好地隱藏了內部的細節。

10. 橋接模式(Bridge)

  橋接模式就是把事物和其具體實現分開,使他們可以各自獨立的變化。

程式碼示例:

1.建立傳送訊息介面

public interface MessageIf {

    //傳送訊息方法
    public void send(String message,String toUser);
}

2.建立抽象的訊息物件類

public abstract class AbstractMessage {

    //持有一個實現傳送訊息介面的物件
    protected MessageIf messageIf;

    public AbstractMessage(MessageIf messageIf) {
        this.messageIf = messageIf;
    }

    public void sendMessage(String message,String toUser) {
        messageIf.send(message,toUser);
    }

}

3.建立實際訊息物件類

public class QQMessage implements MessageIf {

    @Override
    public void send(String message,String toUser) {
        System.out.println("傳送QQ訊息:"+message+"給好友->"+toUser);
    }

}
public class WeChatMessage implements MessageIf {

    @Override
    public void send(String message,String toUser) {
        System.out.println("傳送微信訊息:"+message+"給好友->"+toUser);
    }

}

4.建立發訊息橋接類

public class MessageBridge extends AbstractMessage {

    public MessageBridge (MessageIf messageIf) {
        super(messageIf);
    }

    public void sendMessage(String message, String toUser) {
        //這裡可以對方法做擴充套件
        super.sendMessage(message, toUser);
    }

    //也可以擴充套件自己的方法

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        MessageIf message = new QQMessage();
        AbstractMessage bridge = new MessageBridge(message);
        bridge.sendMessage("橋接模式傳送QQ訊息","mandy");
        message = new WeChatMessage();
        bridge = new MessageBridge(message);
        bridge.sendMessage("橋接模式傳送微信訊息","mandy");
    }

}

  例子中一個MessageBridge同時支援兩種傳送訊息的方式,對外提供的方法只有一個,如果傳送訊息的方式需要做擴充套件,只需要新建一個類然後繼承發訊息介面即可。連線資料庫使用的DriverManager就是橋接模式實現的。

11. 組合模式(Composite)

  組合模式將物件組合成樹形結構以表示“部分-整體”的層次結構,使得使用者對單個物件和組合物件的使用具有唯一性。

程式碼示例:

1.建立部件抽象類

public abstract class Component {

    String name;

    public abstract void add(Component c);

    public abstract void remove(Component c);

    public abstract void eachChild();

}  

2.建立葉子結點類

public class Leaf extends Component {

    // 葉子節點不具備新增的能力,所以不實現
    @Override
    public void add(Component c) {
    }

    // 葉子節點不具備新增的能力必然也不能刪除
    @Override
    public void remove(Component c) {
    }

    // 葉子節點沒有子節點所以顯示自己的執行結果
    @Override
    public void eachChild() {
        System.out.println(name + "執行了");
    }

}

3.建立組合類

public class Composite extends Component {

    // 用來儲存節點的子節點,子節點也可以是組合
    List<Component> list = new ArrayList<Component>();

    // 新增節點 新增組合 新增部件
    @Override
    public void add(Component c) {
        list.add(c);
    }

    // 刪除節點 刪除組合 刪除部件
    @Override
    public void remove(Component c) {
        list.remove(c);
    }

    // 遍歷子部件
    @Override
    public void eachChild() {
        System.out.println(name + "執行了");
        for (Component c : list) {
            c.eachChild();
        }
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Composite rootComposite = new Composite();
        rootComposite.name = "根節點";

        // 左節點
        Composite compositeLeft = new Composite();
        compositeLeft.name = "左節點";

        // 構建右節點,新增兩個葉子幾點,也就是子部件
        Composite compositeRight = new Composite();
        compositeRight.name = "右節點";
        Leaf leaf1 = new Leaf();
        leaf1.name = "右-子節點1";
        Leaf leaf2 = new Leaf();
        leaf2.name = "右-子節點2";
        compositeRight.add(leaf1);
        compositeRight.add(leaf2);

        // 左右節點加入 根節點
        rootComposite.add(compositeRight);
        rootComposite.add(compositeLeft);
        // 遍歷組合部件
        rootComposite.eachChild();
    }

}

  使用場景:當發現需求中是體現部分與整體層次結構時,以及你希望使用者可以忽略組合物件與單個物件的不同,統一地使用組合結構中的所有物件時,就應該考慮組合模式了。

12. 享元模式(Flyweight)

  享元模式的主要目的是實現物件的共享,即共享池,當系統中物件多的時候可以減少記憶體的開銷,通常與工廠模式一起使用。

  享元模式採用一個共享來避免大量擁有相同內容物件的開銷。這種開銷最常見、最直觀的就是記憶體的損耗。享元物件能做到共享的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。

  一個內蘊狀態是儲存在享元物件內部的,並且是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態並可以共享。

  一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元物件的外蘊狀態必須由客戶端儲存,並在享元物件被建立之後,在需要使用的時候再傳入到享元物件內部。外蘊狀態不可以影響享元物件的內蘊狀態,它們是相互獨立的。

  享元模式可以分成單純享元模式和複合享元模式兩種形式。

單純享元模式程式碼示例(更多享元模式內容請看原文):

1.建立享元角色介面

public interface Flyweight {

    //一個示意性方法,引數state是外蘊狀態
    public void operation(String state);

}

2.建立享元角色類

public class ConcreteFlyweight implements Flyweight {

    private Character intrinsicState = null;

    /**
     * 建構函式,內蘊狀態作為引數傳入
     */
    public ConcreteFlyweight(Character state) {
        this.intrinsicState = state;
    }

    /**
     * 外蘊狀態作為引數傳入方法中,改變方法的行為,
     * 但是並不改變物件的內蘊狀態。
     */
    @Override
    public void operation(String state) {
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }

}

3.建立享元工廠類

public class FlyweightFactory {

    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();

    public Flyweight factory(Character state) {
        //先從快取中查詢物件
        Flyweight fly = files.get(state);
        if(fly == null){
            //如果物件不存在則建立一個新的Flyweight物件
            fly = new ConcreteFlyweight(state);
            //把這個新的Flyweight物件新增到快取中
            files.put(state, fly);
        }
        return fly;
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight fly = factory.factory(new Character('a'));
        fly.operation("First Call");

        fly = factory.factory(new Character('b'));
        fly.operation("Second Call");

        fly = factory.factory(new Character('a'));
        fly.operation("Third Call");
    }

}

  例子中,雖然客戶端申請了三個享元物件,但是實際建立的享元物件只有兩個,這就是共享的含義。

優點:

  • 大幅度地降低記憶體中物件的數量,提升了系統的效能。

缺點:

  • 使得系統更加複雜。為了使物件可以共享,需要將一些狀態外部化,這使得程式的邏輯複雜化。

  • 將享元物件的狀態外部化,而讀取外部狀態使得執行時間稍微變長。

13. 策略模式(strategy)

  策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。

策略模式一般主要分為以下三個角色:

  • 環境角色(Context):持有一個策略類引用

  • 抽象策略(Strategy):定義了多個具體策略的公共介面,具體策略類中各種不同的演算法以不同的方式實現這個介面;Context使用這些介面呼叫不同實現的演算法。一般的,我們使用介面或抽象類實現。

  • 具體策略(ConcreteStrategy):實現抽象策略類中的相關的演算法或操作。

程式碼示例:

1.建立抽象策略類

public abstract class AbstractStrategy {

    /**
     * 某個希望有不同策略實現的演算法
     */
    public abstract void algorithm();

}

2.建立兩個具體策略類

public class ConcreteStrategy1 extends AbstractStrategy {

    @Override
    public void algorithm() {
        System.out.println("-------------我是策略一演算法-------------");
    }

}
public class ConcreteStrategy2 extends AbstractStrategy {

    @Override  
    public void algorithm() {
        System.out.println("-------------我是策略二演算法-------------");
    }

}

3.建立環境角色類

public class Context {

    private AbstractStrategy strategy;

    public Context(AbstractStrategy strategy) {
        this.strategy = strategy;
    }

    public void algorithm() {
        strategy.algorithm();
    }

}

測試程式碼:

public class Test {

    public static void main(String[] args) {
        Context context = new Context(new ConcreteStrategy1());
        context.algorithm();

        context = new Context(new ConcreteStrategy2());
        context.algorithm();
    }

}

  測試程式碼中,客戶端選擇不同的具體策略,從而呼叫了不同的演算法,具體使用場景如購物網站的支付頁面,不同的銀行卡提供了不同的支付演算法,但是支付頁面提供了統一的介面,只要使用者選擇對應的銀行就可以使用不同的支付方法。

優點:

  • 策略模式提供了管理相關的演算法族的辦法。策略類的等級結構定義了一個演算法或行為族。恰當使用繼承可以把公共的程式碼轉移到父類裡面,從而避免重複的程式碼。

  • 策略模式提供了可以替換繼承關係的辦法。繼承可以處理多種演算法或行為。如果不是用策略模式,那麼使用演算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的演算法或行為。但是,這樣一來演算法或行為的使用者就和演算法或行為本身混在一起。決定使用哪一種演算法或採取哪一種行為的邏輯就和演算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動態改變演算法或行為變得不可能。

  • 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把採取哪一種演算法或採取哪一種行為的邏輯與演算法或行為的邏輯混合在一起,統統列在一個多重轉移語句裡面,比使用繼承的辦法還要原始和落後。

缺點:

  • 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別,以便適時選擇恰當的演算法類。換言之,策略模式只適用於客戶端知道所有的演算法或行為的情況。

  • 策略模式造成很多的策略類,每個具體策略類都會產生一個新類。有時候可以通過把依賴於環境的狀態儲存到客戶端裡面,而將策略類設計成可共享的,這樣策略類例項可以被不同客戶端使用。換言之,可以使用享元模式來減少物件的數量。

使用場景:

  • 多個類只區別在表現行為不同,可以使用Strategy模式,在執行時動態選擇具體要執行的行為。

  • 需要在不同情況下使用不同的策略(演算法),或者策略還可能在未來用其它方式來實現。

  • 對客戶隱藏具體策略(演算法)的實現細節,彼此完全獨立。

14. 模板方法模式(Template Method)

  模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體建構函式的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。這就是模板方法模式的用意。

模式涉及到的角色如下:

  • AbstractClass(抽象類):在抽象類中定義了一系列基本操作(PrimitiveOperations),這些基本操作可以是具體的,也可以是抽象的,每一個基本操作對應演算法的一個步驟,在其子類中可以重定義或實現這些步驟。同時,在抽象類中實現了一個模板方法(Template Method),用於定義一個演算法的框架,模板方法不僅可以呼叫在抽象類中實現的基本方法,也可以呼叫在抽象類的子類中實現的基本方法,還可以呼叫其他物件中的方法。

  • ConcreteClass(具體子類):它是抽象類的子類,用於實現在父類中宣告的抽象基本操作以完成子類特定演算法的步驟,也可以覆蓋在父類中已經實現的具體基本操作。

程式碼示例:

1.建立模板抽象類

public abstract class AbstractTemplate {

    /**
     * 模板方法,演算法框架
     */
    public final void templateMethod() {
        // 步驟1
        step1();
        // 步驟2
        step2();
        // 步驟3
        step3();
        if (hook()) {
            // 步驟4
            step4();
        }
    }

    /**
     * 鉤子方法
     */
    protected boolean hook() {
        return true;
    }

    /**
     * 步驟1基本方法
     */
    private void step1() {
        System.out.println("步驟1完成");
    }

    /**
     * 步驟2抽象基本方法
     */
    public abstract void step2();

    /**
     * 步驟3基本方法
     */
    private void step3() {
        System.out.println("步驟3完成");
    }

    /**
     * 步驟4抽象基本方法
     */
    public abstract void step4();

}

2.建立實現子類

public class ConcreteClass1 extends AbstractTemplate {

    @Override
    public void step2() {
        System.out.println("ConcreteClass1 步驟2完成");
    }

    @Override
    public void step4() {
        System.out.println("ConcreteClass1 步驟4完成");
    }

}
public class ConcreteClass2 extends AbstractTemplate {

    @Override
    public void step2() {
        System.out.println("ConcreteClass2 步驟2完成");
    }

    @Override
    public void step4() {
        System.out.println("ConcreteClass2 步驟4完成");
    }

    @Override
    protected boolean hook() {
        return false;
    }

}

測試程式碼:

public