這七種設計模式,還學不會來打我!

java碼農之路 發佈 2024-01-12T03:57:12.095727+00:00

前言設計模式就像是編程界的「寶藏」,是一些經過驗證和總結的最佳實踐,可以幫助我們更好地組織代碼、解決問題,並提高代碼的可讀性、可重用性和可維護性。而這些「寶藏」是由一些很厲害的程式設計師們歷經多年的開發實踐和經驗總結得來的。

前言

設計模式就像是編程界的「寶藏」,是一些經過驗證和總結的最佳實踐,可以幫助我們更好地組織代碼、解決問題,並提高代碼的可讀性、可重用性和可維護性。而這些「寶藏」是由一些很厲害的程式設計師們歷經多年的開發實踐和經驗總結得來的。他們在面對各種問題時,都會思考如何使用最有效的方式來解決,同時也在這個過程中不斷總結優秀的編程思想和方法論,從而形成了各種設計模式。

當然,這些設計模式並不是銀彈,不能解決所有問題,只有在特定的場景下才能發揮出它們的優勢。所以,作為程式設計師,我們需要根據具體情況選擇合適的模式,並將其靈活運用,才能真正發揮設計模式的作用。

工廠模式

假設你是一個巧克力工廠的老闆,你需要生產不同口味的巧克力。你可以選擇自己親自動手製作每個口味的巧克力,但這樣你既辛苦又效率低下。那麼,該怎麼辦呢?

這時候,你可以僱傭一些巧克力師傅(具體工廠類),讓他們專門負責製作不同口味的巧克力(具體產品類)。而你只需要提供原材料(工廠方法)和製作巧克力的流程(抽象父類),就可以讓巧克力師傅們按照你的要求製作出口感和口味都完美的巧克力。

這樣,當你需要新增一種口味的巧克力時,只需要再僱傭一個會製作這種口味的巧克力師傅,並提供對應的原材料和製作流程即可。這樣,你就無需親自動手,也能夠輕鬆地擴展巧克力的種類了。

總之,工廠模式就像是一個巧克力工廠,它為我們提供了一種方便、高效、可擴展的巧克力生產方式,讓我們能夠輕鬆製作出各種口味的美味巧克力。

首先,我們需要定義一個抽象的巧克力類 Chocolate,它有一個製作巧克力的方法 make()。

public abstract class Chocolate {
    public abstract void make();
}
複製代碼

然後,我們定義具體的巧克力類,比如牛奶巧克力(MilkChocolate)和黑巧克力(DarkChocolate),它們繼承自 Chocolate 類,並實現了 make() 方法。

public class MilkChocolate extends Chocolate {
    @Override
    public void make() {
        System.out.println("製作牛奶巧克力");
    }
}

public class DarkChocolate extends Chocolate {
    @Override
    public void make() {
        System.out.println("製作黑巧克力");
    }
}
複製代碼

接下來,我們定義一個抽象的巧克力工廠類 ChocolateFactory,它有一個製作巧克力的抽象工廠方法 makeChocolate()。

public abstract class ChocolateFactory {
    public abstract Chocolate makeChocolate();
}
複製代碼

然後,我們可以定義具體的巧克力工廠類,比如牛奶巧克力工廠(MilkChocolateFactory)和黑巧克力工廠(DarkChocolateFactory),它們都繼承自 ChocolateFactory 類,並實現了 makeChocolate() 方法。這樣,每個具體工廠就可以生產對應種類的巧克力。

public class MilkChocolateFactory extends ChocolateFactory {
    @Override
    public Chocolate makeChocolate() {
        return new MilkChocolate();
    }
}

public class DarkChocolateFactory extends ChocolateFactory {
    @Override
    public Chocolate makeChocolate() {
        return new DarkChocolate();
    }
}
複製代碼

``

最後,我們就可以在客戶端使用巧克力工廠來生產不同種類的巧克力了。

public class Client {
    public static void main(String[] args) {
        // 創建牛奶巧克力工廠
        ChocolateFactory milkFactory = new MilkChocolateFactory();
        // 生產牛奶巧克力
        Chocolate milkChocolate = milkFactory.makeChocolate();
        // 製作巧克力
        milkChocolate.make();

        // 創建黑巧克力工廠
        ChocolateFactory darkFactory = new DarkChocolateFactory();
        // 生產黑巧克力
        Chocolate darkChocolate = darkFactory.makeChocolate();
        // 製作巧克力
        darkChocolate.make();
    }
}
複製代碼

以上就是工廠模式的一個簡單示例代碼,通過抽象父類、具體子類和抽象工廠類、具體工廠類的組合關係,實現了一種高效、可擴展、易於維護的對象創建方式。

這個時候可能有小夥伴問:

然並卵?我怎麼沒看出來這有啥用啊,而且好像我們日常開發就是這樣啊?

沒錯!

工廠模式是一種常用的軟體設計模式,在項目開發中被廣泛應用。工廠模式的主要思想是將對象的創建過程封裝起來,以使得程序更加靈活和易於維護。

在項目中使用工廠模式可以帶來以下幾個優點:

降低耦合度:通過工廠模式,客戶端代碼不需要知道具體的產品類,只需要知道它們的抽象接口即可。這樣可以降低客戶端與具體產品之間的依賴關係,提高系統的可擴展性和可維護性。

提高代碼復用性:工廠模式通過對對象創建的過程進行封裝,可以讓多個客戶端共享同一個對象實例。這樣可以避免重複創建對象,提高系統的性能,並且可以提高代碼的復用性。

簡化對象的創建過程:由於工廠模式將對象的創建過程封裝起來,客戶端代碼無需關心對象的創建過程,從而簡化了代碼的編寫過程。

因此,工廠模式已經成為項目開發中的常用設計模式之一,應用廣泛。無論是在面向對象編程還是函數式編程中,都可以看到工廠模式的應用。

作者

其實我們可以把工廠模式與Java中的封裝思想進行聯合記憶,與Java封裝數據於類中不同的是它將對象的創建過程封裝在一個工廠類中。客戶端只需通過工廠類來獲取所需的對象實例,而無需了解對象的具體創建過程和內部實現。這樣就實現了對對象創建過程的封裝,同時提高了代碼的可讀性和可維護性。

因此,封裝和工廠模式都是對實現細節進行封裝的策略,它們的目的都是提高代碼復用性、簡化調用者的使用過程,同時避免直接暴露實現細節帶來的安全問題。雖然封裝和工廠模式的實現機制不同,但它們的目標是一致的,都是為了提高代碼質量和開發效率。

單例模式

單例模式就像是一個特別摳門的大老闆,它只讓你在整個公司里創建一個實例,其他地方都不行。這樣做有什麼好處呢?首先,它能夠保證在整個系統中只有一個特定類型的對象,這樣能夠避免重複創建對象造成的內存浪費和性能問題。其次,因為單例模式只允許創建一個實例,所以不同的代碼段獲取到的都是同一個實例,這樣能夠確保數據的一致性和正確性,方便我們進行資源共享和數據交互。

如果你還不理解,那麼可以想像一下自己是一個賣糖葫蘆的小販,而單例就像是你手裡的糖葫蘆籃子,你始終只有一個籃子,所有的顧客都從這個籃子裡拿糖葫蘆。這樣可以確保所有的顧客都買到了同樣的糖葫蘆,並且你也用最少的資源去完成了生意。

當然,這只是一個簡單的比喻,單例模式的具體實現還需要考慮線程安全等問題,但是相信通過這個比喻,您已經初步了解了單例模式的概念和作用

單例模式可以保證一個類只有唯一的實例,並提供全局訪問點。

在單例模式中,通常會將構造函數聲明為私有方法,以防止直接創建類的實例。同時,該類還會提供一個靜態方法(通常稱之為getInstance()),以便調用者能夠獲取類的唯一實例。

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有構造函數
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
複製代碼

在上面的代碼中,Singleton是一個單例類,它包含一個私有構造函數和一個公共靜態方法getInstance()。當第一次調用getInstance()時,會創建Singleton類的實例並將其存儲在instance變量中,隨後的調用都將返回同一個實例。這樣,就可以確保Singleton類的所有對象均為同一實例,並且可以通過getInstance()方法進行全局訪問。

單例模式的主要作用是確保一個類只有唯一的實例,並提供全局訪問點,使得這個實例可以被整個系統中的其他對象所共享和訪問。

在某些情況下,如果我們需要確保某個類的實例只有一個,例如配置信息類、日誌記錄器類等,那麼使用單例模式就非常適合。由於單例模式可以保證某個類的實例永遠只有一個,因此可以避免不必要的內存分配和資源消耗,提高系統的性能和效率。

另外,使用單例模式還可以提高代碼的可維護性和可擴展性。如果在未來需要對該類進行修改或拓展,只需修改該類的代碼即可,無需修改系統中大量的代碼,減少了維護成本。 總而言之,單例模式在軟體系統中具有廣泛的應用,可以提高系統的性能、效率、可維護性和可擴展性

作者

記憶理解單例模式可以從以下幾個方面考慮:

  1. 唯一性:單例模式的主要特點是確保某個類只有唯一的實例。
  2. 全局訪問:單例模式提供了全局訪問點,使得該實例可以被整個系統中的其他對象所共享和訪問。
  3. 私有構造函數:單例模式通常將構造函數聲明為私有方法,以防止直接創建類的實例。

我們在日常開發中,確實會經常使用到單例模式。例如配置信息類、日誌記錄器類等,它們都需要確保只有一個實例存在,並且可以被整個系統所共享和訪問。在這些情況下,使用單例模式就非常適合。

對於配置類來說,我們可以通過單例模式實現,將配置文件中的數據讀取到內存中,並保存在一個唯一的實例中,在系統中需要訪問配置數據時,直接通過該實例進行訪問即可。這樣可以避免重複讀取配置文件,提高系統性能和效率。

綜上所述,單例模式是一個非常常用且實用的設計模式,在我們的日常開發中也經常會用到。通過理解其核心思想和應用場景,我們可以更好地掌握和運用該模式。

那麼為什麼我們經常編寫的配置類信息中 並沒有體現出單例模式呢?

這是因為這個配置類所返回的實例是由Spring容器管理的,而Spring默認情況下會將它們的作用範圍設置為「單例」(singleton),也就是說,在整個應用程式中只會創建一次這些實例,並且可以被所有需要它們的對象所共享和訪問。

因此,雖然代碼中並沒有顯式地使用單例模式,但是在Spring容器的管理下,這些實例確實表現出了單例模式的特點。

觀察者模式

假設你是某個小區的物業管理員,需要隨時知道小區里居民的動態,比如他們搬進、搬出、有什麼新的需求等。但是,如果每次都要一個一個去問居民,那工作量也太大了吧!

這時候,你可以使用觀察者模式,將自己定義成觀察者,居民們定義成被觀察者。當有人搬進、搬出、或者有新的需求時,居民們就會發出通知,這些通知就相當於觀察者模式中的事件。

而觀察者(也就是你)則會及時接收到這些事件,並進行相應的處理,比如派人去幫忙搬家,安排維修人員修理問題等等。

這樣一來,你就不用一個一個去問居民的情況了,只需要等待居民們主動通知你就好了,工作效率也能提高不少呢!

首先,我們需要定義兩個角色:被觀察者和觀察者。被觀察者需要維護一個觀察者列表,當自己狀態發生改變時,需要通知所有觀察者。觀察者則需要實現一個接收通知的方法。

import java.util.ArrayList;
import java.util.List;

interface Subject { // 被觀察者接口
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

class ConcreteSubject implements Subject { // 具體被觀察者
    private List<Observer> observers = new ArrayList<>();
    private String State;

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    public String getState() {
        return state;
    }
}

interface Observer { // 觀察者接口
    void update(Subject subject);
}

class ConcreteObserver implements Observer { // 具體觀察者
    private String name;

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

    public void update(Subject subject) {
        System.out.println(name + " 收到了消息: " + ((ConcreteSubject)subject).getState());
    }
}
複製代碼

在被觀察者中,我們定義了 attach、detach 和 notifyObservers 三個方法,分別用於添加觀察者、移除觀察者以及通知觀察者。當被觀察者狀態發生改變時,會調用 notifyObservers 方法,通知所有觀察者。

然後,在觀察者中,我們定義了一個 update 方法,用於接收被觀察者的通知,並做出相應的處理。

最後,我們可以定義具體的被觀察者和觀察者類,來實現我們需要的功能。

public class ObserverPatternDemo {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer1 = new ConcreteObserver("張三");
        ConcreteObserver observer2 = new ConcreteObserver("李四");
        subject.attach(observer1);
        subject.attach(observer2);
        subject.setState("狀態1");
        subject.detach(observer1);
        subject.setState("狀態2");
    }
}
複製代碼

在這個例子中,我們定義了一個具體的被觀察者 ConcreteSubject 和一個具體的觀察者 ConcreteObserver。當狀態發生改變時,觀察者會接收到通知,並輸出相應的提示信息。同時,在這個例子中,我們還演示了如何添加和移除觀察者的過程。

在實現觀察者模式時,需要注意以下幾個問題:

觀察者列表的線程安全性:如果有多個線程同時進行添加、刪除觀察者操作,那麼需要考慮同步和線程安全問題。

觀察者的執行順序:當被觀察者狀態發生改變時,通知觀察者的順序可能會影響結果,請在實現時注意這一點。

如何記住觀察者模式呢?可以從它的名字入手,觀察者就是觀察某個對象的變化並做出相應反應的人或者物體。觀察者模式就是讓我們將這個場景抽象出來,並用代碼實現。你可以想像自己在看電視,而電視機上的遙控器就是一個觀察者,當你按下遙控器上的按鈕時,電視機就會發生改變,這時遙控器就會接收到通知並作出相應的反應——比如調節音量、切換頻道等等。

作者

在實際項目中,觀察者模式運用還是比較多的。舉個例子,可能你們公司的 HR 部門需要經常了解員工的情況,包括調動、離職、晉升等等。如果每次都要手動去詢問員工的情況,那效率太低了。這時候,HR 部門可以作為觀察者,員工們則是被觀察者,當員工狀態發生改變時,HR 部門就能夠及時接收到通知並做出相應的處理。

再舉一個例子,在遊戲開發中,可能需要實現多個遊戲對象之間的協作。比如,當玩家攻擊敵人時,敵人需要受到傷害並可能死亡,同時玩家也會獲得一定的分數、金幣等獎勵。如果每次都要手動去實現這些邏輯,代碼量很大,而且也不夠靈活。這時候,我們可以將玩家和敵人作為被觀察者,將計分板、掉落物品等作為觀察者,當玩家攻擊敵人時,被觀察者就會發出通知,所有的觀察者就能夠及時接收到通知並做出相應的處理,從而簡化代碼,並提高了可擴展性。

裝飾者模式

假設你是一家咖啡店的老闆,你想要給顧客提供各種不同口味的咖啡。但是你又不想要過多的商品,因為對於每個口味都建立一個品類實在太繁瑣了。

這時候,你可以考慮使用裝飾者模式。它能夠動態地為對象添加新功能,而無需修改原有代碼。這樣你就可以只維護幾種基本的咖啡類型了,然後通過添加裝飾器來實現各種不同的口味。

具體來說,我們可以定義一個基礎的咖啡對象,並通過繼承和組合的方式,來動態地添加各種配料(比如牛奶、糖、巧克力等)。

首先,我們定義一個 Coffee 接口:

public interface Coffee {
    double getCost();
    String getDescription();
}
複製代碼

其中 getCost() 方法用於獲取咖啡的價格,getDescription() 方法用於獲取咖啡的描述信息。

然後,我們定義一個基礎的咖啡實現類 SimpleCoffee:

public class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }

    public String getDescription() {
        return "Simple Coffee";
    }
}
複製代碼

接下來,我們定義一個抽象類 CoffeeDecorator,它同時實現了 Coffee 接口:

public abstract class CoffeeDecorator implements Coffee {
    private final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    public double getCost() {
        return decoratedCoffee.getCost();
    }

    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
}
複製代碼

注意到這個抽象類裡面包含了一個私有成員變量 decoratedCoffee,它的類型是 Coffee。這個變量用於保存被裝飾的 Coffee 對象。

然後,我們可以通過繼承這個抽象類實現各種具體的裝飾器。比如,牛奶、糖和巧克力:

public class Milk extends CoffeeDecorator {
    public Milk(Coffee coffee) {
        super(coffee);
    }

    public double getCost() {
        return super.getCost() + 0.5;
    }

    public String getDescription() {
        return super.getDescription() + ", Milk";
    }
}

public class Sugar extends CoffeeDecorator {
    public Sugar(Coffee coffee) {
        super(coffee);
    }

    public double getCost() {
        return super.getCost() + 0.2;
    }

    public String getDescription() {
        return super.getDescription() + ", Sugar";
    }
}

public class Chocolate extends CoffeeDecorator {
    public Chocolate(Coffee coffee) {
        super(coffee);
    }

    public double getCost() {
        return super.getCost() + 0.3;
    }

    public String getDescription() {
        return super.getDescription() + ", Chocolate";
    }
}
複製代碼

最後,我們可以用以下方式來使用裝飾器模式:

Coffee coffee = new SimpleCoffee();
coffee = new Milk(coffee);
coffee = new Sugar(coffee);
coffee = new Chocolate(coffee);
System.out.println("Coffee Description: " + coffee.getDescription());
System.out.println("Coffee Cost: " + coffee.getCost());
複製代碼

在這個例子中,我們首先創建了一個基礎的咖啡對象 SimpleCoffee,然後通過添加 Milk、Sugar 和 Chocolate 裝飾器,來生成一個具有多種口味的咖啡對象。通過調用咖啡對象的 getDescription() 和 getCost() 方法,我們可以獲取到咖啡的描述和價格。

對象的初始狀態:被裝飾的對象在創建時應該處於一個完整的、可用的狀態。否則,在添加裝飾器時可能會出現意想不到的問題。

裝飾器與被裝飾對象的接口一致:裝飾器必須實現與被裝飾對象相同的接口,這樣才能保證裝飾器可以替換原始對象,並且客戶端代碼不需要做任何改變。

裝飾器之間的順序:如果有多個裝飾器,其添加順序可能會影響結果。一般來說,先添加的裝飾器會先起作用,後添加的裝飾器會依次疊加。因此,在設計時要仔細考慮裝飾器的順序。

避免過度裝飾:如果添加過多的裝飾器,可能會導致代碼變得混亂和難以維護。因此,在設計時要注意避免對對象進行過度裝飾。

作者

關於裝飾者模式的應用場景,它的應用範圍非常廣泛,特別是在需要動態地增加或刪除對象功能的情況下。例如:

Java 中的 IO 類庫:Java 的 IO 類庫就大量使用了裝飾者模式。比如,我們可以通過添加緩衝、壓縮、加密等裝飾器來實現不同的文件操作功能。

GUI 編程中的組件:在 GUI 編程中,經常需要動態地擴展或改變組件的顯示和行為。這時候就可以使用裝飾者模式來實現。

Web 開發中的過濾器:Web 應用程式中,經常需要過濾、驗證、轉換輸入或輸出數據。這時候就可以使用裝飾者模式來實現。

日誌記錄:在日誌記錄中,可以使用裝飾者模式來增加或刪除記錄方式,比如記錄到文件、控制台或資料庫等。

因此,裝飾者模式在日常編程中應用非常廣泛,是一個很實用的設計模式。

策略模式

想像一下,你是個餐廳老闆,菜單上有各種各樣的菜式。但是你發現有些客人可能對辣椒比較敏感,有些客人可能喜歡口味偏甜的菜式,而有些客人則喜歡鹹味的菜式。

這時候,如果你為每個客人都準備一份特別的菜單,那工作量肯定會很大,也不太實際。所以,你可以使用策略模式。

策略模式就像是一個菜單中的不同選項,你可以根據客人的口味選擇不同的策略來提供菜式。例如,如果客人喜歡辣味,你可以選擇辣椒醬作為調料;如果客人喜歡甜味,你可以加入糖或蜜糖,如果客人喜歡鹹味,你可以多加一點鹽等等。

在程序設計中,策略模式就是定義一系列算法,將它們分別封裝起來,並且使它們可以相互替換。這樣,在運行時,你可以根據需要選擇不同的算法。就像你可以根據客人的口味選擇不同的調料一樣。

首先,我們定義一個接口 Strategy,用於聲明支持的所有算法:

public interface Strategy {
    int calculate(int a, int b);
}
複製代碼

接下來,我們定義具體的算法類,實現 Strategy 接口:

public class AddStrategy implements Strategy {
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
}

public class SubStrategy implements Strategy {
    @Override
    public int calculate(int a, int b) {
        return a - b;
    }
}

public class MulStrategy implements Strategy {
    @Override
    public int calculate(int a, int b) {
        return a * b;
    }
}
複製代碼

然後,我們定義一個上下文對象 Calculator,它持有一個 Strategy 對象引用,並提供一個 setStrategy() 方法,用於切換算法:

public class Calculator {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public int calculate(int a, int b) {
        return strategy.calculate(a, b);
    }
}
複製代碼

最後,我們可以在客戶端代碼中使用這些類進行計算:

Calculator calculator = new Calculator();

// 使用加法算法
calculator.setStrategy(new AddStrategy());
int result = calculator.calculate(10, 5);  // 輸出 15

// 使用減法算法
calculator.setStrategy(new SubStrategy());
result = calculator.calculate(10, 5);  // 輸出 5

// 使用乘法算法
calculator.setStrategy(new MulStrategy());
result = calculator.calculate(10, 5);  // 輸出 50
複製代碼

在上面的代碼中,我們通過 setStrategy() 方法來切換不同的算法,並且可以動態地修改計算器的行為。這就是策略模式的核心思想。

策略模式是一種行為設計模式,它將一組算法封裝在獨立的類中,並使它們之間可以相互替換。以下是在實際項目中使用策略模式時需要注意的幾點:

將變化隔離:策略模式的一個主要特點是將算法的實現與客戶端代碼分離,使得算法的實現可以更容易地修改或擴展。因此,在選擇一個算法實現時,需要考慮其可能需要被修改或替換的程度。

統一接口:為了達到不同算法之間相互替換的目的,所有算法必須實現相同的接口。這意味著在添加新算法時,需要確保新算法實現了該接口。

策略選擇:在運行時動態選擇算法的過程中,需要根據具體情況進行權衡和選擇。這可能涉及到性能、可靠性、複雜度等方面的因素。因此,在選擇算法時,需要綜合考慮各種因素,並作出最優的選擇。

可讀性:由於策略模式可能涉及到多個類的交互,因此代碼的可讀性尤為重要。為了提高代碼的可讀性,需要採用清晰的命名約定、注釋和代碼組織方式。

作者

策略模式在實際項目中的應用場景非常廣泛,以下是一些常見的使用場景:

算法和規則:對於需要根據不同條件選擇不同算法或規則的場景,策略模式可以提供一種簡單而靈活的解決方案。

數據格式轉換:在將數據從一種格式轉換為另一種格式時,可能需要使用不同的轉換器。策略模式可以使得這些轉換器相互替換,從而提高系統的靈活性和可擴展性。

輸入輸出處理:當需要處理不同類型的輸入或輸出時,策略模式可以提供一種統一的接口,並幫助我們選擇最適合的輸入或輸出方式。

訂單處理:在訂單處理系統中,根據訂單狀態、支付方式、配送地址等因素選擇不同的處理方式可能會非常複雜。策略模式可以使得這些處理方式相互獨立,並更容易地修改或擴展。

遊戲開發:在遊戲開發中,可能需要使用不同的策略來控制遊戲中的角色行為、AI 敵人行為、道具效果等方面。策略模式可以使得這些策略相互替換,從而提高遊戲的可玩性和可擴展性。

策略模式可以理解為一個策略選擇器。

在實際應用中,我們需要根據不同的情況選擇不同的算法或規則來達到最優的效果。這個過程類似於在一個策略選擇器中選擇不同的策略來應對不同的問題。

具體地說,策略模式把一組算法封裝在獨立的類中,並使它們之間可以相互替換。這樣,當需要使用某種算法時,只需要從策略選擇器中選擇相應的算法即可,而不必關心算法的具體實現細節。

疊代器模式

疊代器模式就像是一個超市裡的購物車。你可以通過購物車把你想要買的商品放進去,然後遍歷一遍購物車裡面的商品,逐個結算。在這個過程中,你不需要關心購物車裡面的商品具體是哪些,只需要按照順序逐個處理即可。而疊代器模式的實現就是讓用戶無需知道集合內部的結構,就可以逐個處理集合中的元素。

疊代器模式是一種設計模式,它允許我們訪問一個容器中的所有元素,而不需要暴露容器的內部實現細節。在Java中,疊代器模式通常使用Iterator 接口來實現。

Iterator接口包含三個主要方法:hasNext()、next() 和remove()。其中,hasNext() 方法用於判斷是否還有下一個元素, next() 方法用於獲取下一個元素, remove() 方法用於從容器中刪除當前元素(可選操作)。

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorPatternDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 獲取疊代器對象
        Iterator<String> iterator = list.iterator();

        // 遍歷容器中的元素
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println(fruit);
        }
    }
}
複製代碼

在上面的代碼中,我們首先創建了一個ArrayList容器,並向其中添加了幾個字符串元素。然後,通過調用list的iterator()方法,獲得了一個疊代器對象。接著,我們使用while循環遍歷了整個容器,並在循環體內通過iterator的next()方法獲取了容器中的每一個元素並列印出來。

這就是疊代器模式在Java中的應用。通過使用疊代器,我們可以方便地遍歷容器中的元素,並且不需要了解容器的內部實現細節。


關於疊代器對象

public Iterator<E> iterator() {
    return new Itr();
}

private class Itr implements Iterator<E> {
    //...
}
複製代碼

在Java中,List、Set和Map等集合類都實現了 Iterable 接口。這意味著,可以通過調用它們的 iterator() 方法來獲取一個疊代器對象。

該方法返回一個內部類 Itr 的實例,而該類則實現了 Iterator 接口。由於 Itr 類是ArrayList的內部類,因此它可以直接訪問ArrayList的成員變量。

類似地,在 HashSet 和 HashMap 中都有一個內部類 HashIterator,該類同樣實現了 Iterator 接口,用於遍歷其中的元素。

因此,當我們調用 list.iterator() 時,實際上是獲取到了一個內部類 Itr 的實例,該實例封裝了對 ArrayList 內部結構的訪問方式,並提供了 hasNext()、next() 和 remove() 等方法,以支持遍歷 ArrayList 中的元素。

在使用疊代器模式時,需要注意以下幾點:

  1. 確保容器的數據結構穩定:由於疊代器是基於容器實現的,因此在使用疊代器時要確保容器的數據結構穩定。如果容器的結構發生了變化(如添加、刪除元素),則可能會導致疊代器失效或不正確。
  2. 不要直接修改容器中的元素:雖然疊代器可以通過remove()方法移除當前位置的元素,但不建議直接修改容器中的元素。如果需要修改容器中的元素,最好先將其複製到一個臨時變量中進行修改,避免對疊代器造成影響。
  3. 不要同時使用多個疊代器:由於疊代器是基於容器實現的,因此在使用疊代器時不建議同時使用多個疊代器來遍歷同一個容器。這可能會導致某些元素被漏掉或重複遍歷同一個元素。
  4. 注意異常處理:在使用疊代器時,需要注意異常處理。例如,在遍歷容器時,如果已經到達容器的末尾,則 hasNext() 方法會返回 false,此時如果仍然調用 next() 方法,則會拋出 NoSuchElementException 異常。
  5. 實現自定義疊代器時,應該遵循疊代器接口的規範:如果需要實現自定義疊代器,則應該遵循疊代器接口的規範,包括實現 hasNext()、next() 和 remove() 三個方法,並且要根據具體的容器類型來實現這些方法。

作者

疊代器模式的主要使用場景是需要遍歷容器(如列表、樹等)中的元素,並且不想暴露容器的內部結構給客戶端。疊代器模式可以將遍歷操作和容器內部結構解耦,從而更加靈活地處理容器中的元素。

在實際應用中,疊代器模式常常與其他設計模式一起使用,例如工廠方法模式、觀察者模式等。下面是一些典型的應用場景:

  1. 需要遍歷一個容器中的元素,但不想暴露容器的內部實現細節給客戶端。
  2. 需要對容器中的元素進行過濾、排序或其他操作。
  3. 需要同時遍歷多個容器中的元素,然後將它們合併到一個容器中。

對於理解疊代器模式,我們可以把它想像成一個抽象的「遍歷器」,它可以幫助我們逐個訪問容器中的元素,而無需了解容器的具體實現方式。從這個角度上來說,疊代器模式可以看作是一種「遍歷器」模式,它將遍歷操作和容器的實現分離開來,從而更好地組織代碼、提高代碼復用性和可維護性。

模板方法模式

假設你要製作一份烤雞翅的食譜,你需要準備好雞翅、調料和烤箱等工具。但是,你可能不知道應該先塗什麼調料、要烤多長時間、要加多少鹽等等。這時候,你可以向專業廚師學習製作烤雞翅的方法。

專業廚師會告訴你,製作烤雞翅的步驟大致如下:

先將雞翅洗淨,再用紙巾擦乾水分。 將雞翅放入碗中,加入適量的鹽、胡椒粉、孜然粉等調料,攪拌均勻。 在烤盤上鋪上錫紙,將雞翅擺放在烤盤上,放入預熱好的烤箱中,烤20分鐘。 取出雞翅,反面翻轉,再烤10分鐘。

這就是一個簡單的製作烤雞翅的模板方法。每個步驟都是固定的,只是其中的具體細節可能有所不同。例如,不同的人可能會用不同的調料、烤箱溫度也可能有所不同等等。

在軟體開發中,模板方法模式也是類似的一種設計模式。它定義了一個算法的骨架,而將具體實現留給子類來完成。這樣,在編寫代碼時就可以避免重複代碼,並且可以靈活地修改算法的具體實現。

因此,我們可以將模板方法模式理解為一種「食譜」,其中定義了一個固定的算法流程,而將具體實現留給子類來完成。只要保證算法流程的正確性和穩定性,就可以在具體實現上進行靈活的調整和擴展。

abstract class AbstractClass {
    public void templateMethod() {
        // Step 1
        operation1();

        // Step 2
        operation2();

        // Step 3
        operation3();
    }

    protected abstract void operation1();

    protected abstract void operation2();

    protected void operation3() {
        System.out.println("AbstractClass.operation3()");
    }
}

class ConcreteClass extends AbstractClass {
    protected void operation1() {
        System.out.println("ConcreteClass.operation1()");
    }

    protected void operation2() {
        System.out.println("ConcreteClass.operation2()");
    }
}

public class TemplateMethodDemo {
    public static void main(String[] args) {
        AbstractClass obj = new ConcreteClass();
        obj.templateMethod();
    }
}
複製代碼

在上面的代碼中,AbstractClass 是抽象類,其中定義了一個 templateMethod() 方法,該方法包含了算法的骨架。具體實現留給子類來完成。

ConcreteClass 則是具體子類,實現了 operation1() 和 operation2() 方法,並且繼承了 operation3() 方法的默認實現。

在 main() 方法中,我們創建了一個 ConcreteClass 的對象,並調用其 templateMethod() 方法。由於 ConcreteClass 繼承了 AbstractClass,因此可以使用 templateMethod() 方法的實現。

在程序運行時,輸出的結果為:

ConcreteClass.operation1()
ConcreteClass.operation2()
AbstractClass.operation3()
複製代碼

這是因為在 templateMethod() 方法中先調用了 operation1() 和 operation2() 方法,而在默認實現中又調用了 operation3() 方法。

通過使用模板方法模式,我們可以將算法的骨架和具體實現分離開來,從而更好地組織代碼並提高代碼的可復用性。

在使用模板方法模式時,需要注意以下幾點:

  1. 確定好模板方法的算法流程:模板方法是整個模板方法模式的核心,因此在使用模板方法模式時,需要先確定好算法流程。通常,算法流程中會包含若干個抽象方法和一個具體方法。
  2. 抽象方法要求子類必須實現:在定義抽象方法時,需要注意這些方法必須由子類來實現,而不能在抽象類中直接實現。如果需要在抽象類中提供默認實現,可以使用具體方法來實現。
  3. 具體方法可有可無:在定義具體方法時,需要注意這些方法是可有可無的,並且不一定需要被子類覆蓋。如果某個步驟不需要子類實現,則可以在具體方法中提供默認實現。
  4. 避免過度設計:在使用模板方法模式時,需要避免過度設計,保持算法流程簡單明了,不要讓算法流程變得過於複雜或難以理解。
  5. 注意抽象類和具體子類之間的耦合:由於抽象類與具體子類緊密相關,因此在使用模板方法模式時,需要注意抽象類和具體子類之間的耦合性。如果抽象類太過於複雜或難以維護,可能會影響具體子類的開發和維護。

總之,在使用模板方法模式時,需要注意確定好算法流程,並在其中合理地使用抽象方法和具體方法。同時,還需要注意抽象類和具體子類之間的耦合性,保持代碼的簡潔、靈活和易於維護。

作者

為了更好地記住模板方法模式,你可以將其看作是一種「模板」或者「骨架」,用於定義算法的流程。 類似於烤雞翅的食譜,模板方法定義了一個固定的步驟,而具體實現留給子類來完成。

在實際工作中,模板方法模式有很多應用場景:

  1. 作業系統的啟動流程:作業系統的啟動過程需要執行多個步驟,例如加載內核、初始化設備、啟動服務等等。這些步驟的順序和細節可能會因為不同的作業系統而有所不同,但整個啟動流程的邏輯是相同的,可以使用模板方法模式來定義。
  2. HTTP請求的處理流程:在Web開發中,HTTP請求的處理流程也需要執行多個步驟,例如解析請求、調用控制器、渲染模板等等。這些步驟的具體實現可能因為不同的框架而有所不同,但處理請求的流程是相同的,可以使用模板方法模式來定義。
  3. 資料庫訪問的流程:資料庫訪問通常需要建立連接、發送查詢語句、獲取結果集等等。這些步驟的細節可能因為不同的資料庫而有所不同,但整個訪問流程的邏輯是相同的,可以使用模板方法模式來定義。

總的來說,模板方法模式適用於需要使用固定算法流程,但又希望在具體實現上保持靈活和可擴展性的場景。通過使用模板方法模式,我們可以避免重複代碼,並且可以更好地組織代碼,提高代碼的可讀性和可維護性。

總結

寫在最後 看到這裡你應該發現:沒錯設計模式是一個抽象的東西,因為它不過就是一些資深工作者所總結的經驗;提供了一些通用的解決方案來應對各種軟體設計問題、本質上是一些抽象的思想和原則,而不是具體的代碼實現或技術細節。

相反 我們在實際工作中可能也是這麼做的,只不過那個時候我們由於缺乏系統化的知識和理論指導,我們可能無法準確地描述出這些解決方案的本質和優缺點; 而看了本篇文章後你會發現 :噢~原來這個就叫單例模式、原來這個就是工廠模式啊。

所以真正的設計模式的學習其實重要的更多的是包含了一個程式設計師對代碼的思想以及經驗,如果你想好好的學習設計模式,最重要的其實是多學習一些知名開源項目、並閱讀其中的源碼,模仿這些知名程式設計師的架構。

相信我!這會給你帶來收穫的~

最後最後~

關注公眾號【程式設計師黑哥】,更多乾貨等著你!!!

關鍵字: