行為模式 – 5 個 PK | Command vs Strategy vs State | Observer vs Chain | 最佳實踐

行為模式 – 5 個 PK | Command vs Strategy vs State | Observer vs Chain | 最佳實踐

Overview of Content

行為模式涵蓋了多種設計模式,如 責任鏈、命令、解釋器、迭代、仲介、備忘錄、觀察者、狀態、策略、模板、訪問者等。儘管它們各有不同,但它們都旨在解決特定的設計問題,並且在應用中具有各自獨特的行為。

本文將重點討論三個主要的創建模式:命令、策略和狀態,以及它們與其他行為模式的關係。透過深入研究每種模式的原理、特性和最佳實踐,我們將幫助讀者更好地理解何時以及如何應用這些模式來改進軟件設計。

命令模式中,我們將探討如何封裝具體的操作,以便將其與發送命令的對象解耦。而在策略模式中,我們將討論如何實現不同的算法並使其可互換,從而使客戶端代碼更具靈活性。最後,在狀態模式中,我們將研究如何管理對象的狀態變化並根據不同的狀態執行不同的行為。

通過比較和對比觀察者和責任鏈模式,我們將進一步探討不同行為模式之間的異同,並提供最佳實踐指南,以幫助讀者更好地選擇和應用適合其需求的模式。


命令 vs 策略

命令、策略設計模式 - 兩者區別

策略模式

封裝算法,並且每個算法都可以相互替換、並獨立維護

每個算法皆為原子業務,不可再拆分的任務

命令模式

對 動作 的解耦,把一個動作(行為)封裝程一個對象(接收者),並透過執行對象(接收命令者)執行動作

解耦「執行」、「行為」兩個動作,行為有時候不可相互替換(不同設計概念就會產生不同的特性,之後會說明)


策略之替換算法

● 策略模式的核心概念在於 算法的相互替換

Example: 接下來實做一個儲存數據的方案,每個方案代表不同算法,並且這些方案都可以相互替換

A. 抽象算法

對外說明使用該合約界面可以取得、設定數據… 以抽象的角度來說明這界的功能;而具體的方案實做要了解實做類(就是讀 Document 啦~)


interface IStorage {
    fun getData() : Any

    fun setData(data: Any)
}

B. 實現算法:各個算法的實做


// 儲存 Field
class FieldStorage : IStorage {

    private var data : Any? = null

    override fun getData(): Any {
        data.apply { 
            if (this == null) {
                throw RuntimeException()
            }

            return this
        }
    }

    override fun setData(data: Any) {
        this.data = data
    }

}

// ------------------------------------------------------
// 儲存 List 列表
class ListStorage : IStorage {

    private var list = mutableListOf<Any>()

    override fun getData(): Any {
        if (list.size == 0) {
            throw RuntimeException()
        }

        return list[0].also {
            list.remove(it)
        }
    }

    override fun setData(data: Any) {
        list.add(data)
    }

}

C. 環境類

環境類依賴於界面(interface, IStorage),而非實做,符合依賴倒置原則

之所以將其稱為環境類,是因為它程載了 IStorage 的實體,但是又不是真正的實做類,相對來講是運行界面的平台


class Context constructor(var storage: IStorage) {

    fun getData() : String {
        return storage.getData() as String
    }

    fun setData(data: String) {
        storage.setData(data)
    }

}

使用者:使用者會直接接觸、選擇具體的算法(這裡不符合迪米特原則),並透過 Context 環境類來運行(但環境類符合依賴倒置的方案,依賴抽象而不是實做)


fun main() {
    val initStorage = ListStorage()

    val contextEnv = Context(initStorage).apply {
        setData("123")
        println(getData())
    }

    contextEnv.apply {
        storage = FieldStorage()

        setData("888")
        println(getData())
    }

}

命令之封裝

● 命令模式的核心概念在於 封裝命令

讓請求者、實現者(接收者)解耦,不管接收者如何變化,都不會影響請求者

● 實做範例如上,實現與上面策略模式相同的需求(不同的儲存方案)

A. 抽象命令、具體命令

使用跟策略模式相同的界面是抽象(對外保證)、實做的類(對內實做、實現)


interface IStorage {
    fun getData() : Any

    fun setData(data: Any)
}

// ------------------------------------------------------
// 具體命令
class FieldStorage : IStorage {

    private var data : Any? = null

    override fun getData(): Any {
        data.apply { 
            if (this == null) {
                throw RuntimeException()
            }

            return this
        }
    }

    override fun setData(data: Any) {
        this.data = data
    }

}


// 具體命令
class ListStorage : IStorage {

    private var list = mutableListOf<Any>()

    override fun getData(): Any {
        if (list.size == 0) {
            throw RuntimeException()
        }

        return list[0]
    }

    override fun setData(data: Any) {
        list.add(data)
    }

}

B. 抽象命令

命令的基礎類,它的核心要點在於「抽象化命令」,並且依賴於抽象接收者(IStorage),符合依賴倒置原則


abstract class CommonCmd {
    companion object {
        @JvmStatic
        protected val fieldStorage: IStorage = FieldStorage()
        @JvmStatic
        protected val listStorage: IStorage = ListStorage()
    }

    abstract fun controlData(data: String) : Any
}

C. 具體命令

既然上面有抽象化的命令,那這裡就會有命令的實做類;

它會將每個命令細分為不同的類,每個類代表了一個功能…


class StorageOneData : CommonCmd() {

    override fun controlData(data: String): Any {
        fieldStorage.setData(data)

        return Unit
    }

}

class GetOneStorageData : CommonCmd() {

    override fun controlData(data: String): Any {
        return fieldStorage.getData()
    }

}

class StorageListData : CommonCmd() {

    override fun controlData(data: String): Any {
        listStorage.setData(data)

        return Unit
    }

}

class GetListStorageData : CommonCmd() {

    override fun controlData(data: String): Any {
        return listStorage.getData()
    }

}

D. 調用者(環境類):

其功能就跟策略模式的 環境類相同,它的存在是為了調用抽象命令;

但它可以持有 1 ~ 多個 抽象命令,並且可以在這邊做出不同花樣(紀錄、替換、取消... 等等行為)


class Invoker(var cmd: CommonCmd) {

    fun controlData(data: String) : Any {
        return cmd.controlData(data)
    }

}

使用者調用

抽象命令的替換(實做替換)是個關鍵,替換命令代表了不同的行為實做

● 但相對的,這種設計方式不符合迪米特(最少知識)原則

使用者仍要了解到每個實做的含意


fun main() {
    val initCmd : CommonCmd = StorageOneData()

    val invoker = Invoker(initCmd).apply {
        controlData("Hello")

        // 換命令
        cmd = GetOneStorageData()
        println(controlData(""))
    }

    invoker.apply {
        // 換命令
        cmd = StorageListData()
        controlData("World")

        // 換命令
        cmd = GetListStorageData()
        println(controlData(""))
    }

}

● 在這裡其實有另外一個關鍵概念:

以上我們使用策略模式的實做,它的 界面是依照功能分類,在這裡我們可以換個抽象概念,依照 職責 去重新規劃界面

A. 抽象接收者界面:改成依照責任去規劃


interface IStorageData {
    fun fieldDataControl(data: Any) : Any

    fun listDataControl(data: Any) : Any
}

B. 接收者實做:依照具體接收者(執行者)實做


class SaveData constructor(var data: Any?, val list: MutableList<Any>) : IStorageData {

    override fun fieldDataControl(data: Any): Any {
        this.data = data

        return Unit
    }

    override fun listDataControl(data: Any): Any {
        list.add(data)

        return Unit
    }

}

class GetData constructor(var data: Any?, val list: MutableList<Any>): IStorageData {

    override fun fieldDataControl(data: Any): Any {
        this.data.apply {
            if (this == null) {
                throw RuntimeException()
            }

            return this
        }
    }

    override fun listDataControl(data: Any): Any {
        if (list.size == 0) {
            throw RuntimeException()
        }

        return list[0]
    }

}

C. 抽象命令:命令使用的類也會跟著修改,修改為依照責任的接收者(執行者)


abstract class CommonCmd2 {
    companion object {
        var data: Any? = null
        val list = mutableListOf<Any>()

        @JvmStatic
        protected val saveData: IStorageData = SaveData(data, list)
        @JvmStatic
        protected val getStorage: IStorageData = GetData(data, list)
    }

    abstract fun controlData(data: String) : Any
}

D. 具體命令:既然接收者(執行者)更改,那命令的調用方式也會跟著修改

雖然依照接收者修改邏輯,仍符單一職責,一個類包裝一個命令


class StorageOneData2 : CommonCmd2() {

    override fun controlData(data: String): Any {
        saveData.fieldDataControl(data)

        return Unit
    }

}

class GetOneStorageData2 : CommonCmd2() {

    override fun controlData(data: String): Any {
        return getStorage.fieldDataControl("")
    }

}

class StorageListData2 : CommonCmd2() {

    override fun controlData(data: String): Any {
        saveData.listDataControl(data)

        return Unit
    }

}

class GetListStorageData2 : CommonCmd2() {

    override fun controlData(data: String): Any {
        return getStorage.listDataControl("")
    }

}

● 調用者:調用者只須修改依賴界面即可


class Invoker2(var cmd: CommonCmd2) {

    fun controlData(data: String) : Any {
        return cmd.controlData(data)
    }

}

  • 從這裡可以看出來命令模式並不一定要跟策略相同,上面 依照責任設計界面,就不適用於策略模式!(因為 責任不可相互替換

命令 vs 策略:最佳實踐

關注點不同

策略模式:關注於算法的完整性、封裝性,只有同時具備這兩個特點才能簡單、自由切換不同算法

命令模式:關注於解耦呼叫者、執行者(接收者),解耦兩者是這個模式最注重的要點,並且會細分每個命令(一個命令一個類)

由於封裝了命令,所以可以對命令做不同的處理… 像是撤銷、紀錄

角色功能不同

策略模式具體算法是一個原子業務,一旦變更就是對算法整體的變更

命令模式:命令模式只要沒有個接收者,其實就跟策略模式差不多;換個角度來說,命令模式的抽象算法 + 實做算法,就類似於策略模式的執行者(接收者)

可以清楚拆分請求者、執行者的關係,當執行只被修改,也不會影響到請求者


策略 vs 狀態

兩者的目標(業務的關注點)不同

策略模式

A. 封裝算法,並交替算法,算法之間不會相互交集(也不會調用到)

B. 策略的切換由使用者操作

狀態模式

A. 封裝不同狀態,透過狀態的切換達到相同方法,不同行為反應

B. 狀態的切換可由外部、也可由內部(子類)切換

策略之獨立算法

● 策略模式封裝算法,並且每個算法都可以獨立維護,不同算法之間沒有相互交集

● 以播放音樂為例,每種音樂的風格都不同,不同風格用不同策略,要切換風格時也由使用者切換

A. 抽象策略:每個類的共同抽象方法

每個歌曲都必須區分哪個歌手是屬於哪種曲風


interface IMusic {

    fun musicStyle()

}

B. 策略實做每個策略只須完成自身邏輯即可


class Classical : IMusic {
    override fun musicStyle() {
        println("莫札特")
        println("巴哈")
        println("蕭邦")
    }

}

class Soul: IMusic {

    override fun musicStyle() {
        println("Johnny")
        println("LoFi")
    }

}

class Pop : IMusic {

    override fun musicStyle() {
        println("周杰倫")
        println("張學友")
    }

}

C. 環境類:操作策略的環境,隔離使用者直接操作實例;並且這邊對外開放的方法可以略做修改,不必與策略同名(可以再進行包裝)


class MusicRadioStation constructor(var music: IMusic) {

    fun playMusic() {
        music.musicStyle()
    }

}

使用者使用:在這邊使用者要關注具體作法(不符合迪米特原則),並 由使用者按照商業邏輯切換算法


fun main() {
    MusicRadioStation(Classical()).apply {
        println("--- Morning ---")
        playMusic()

        println("--- Afternoon ---")
        music = Soul()
        playMusic()

        println("--- Night ---")
        music = Pop()
        playMusic()
    }

}

狀態之自動切換

● 狀態模式會包裝每個狀態,並在適當時機由內部自己切換不同狀態

● 同樣實現音樂風格的播放,不過這邊多了個商業邏輯,每個時段播放不同風格的音樂(也就是 按照時間切換狀態

A. 抽象狀態:與策略模式相同,有一個必須實現的方法,但是 它多了一個環境參數,這個環境參數相當重要,它用來給子類切換狀態用


abstract class MusicCentral {

    var station: DailyMusicRadioStation? = null

    abstract fun musicStyle()
}

B. 狀態實做:這裡有時做需要達成 兩件事1. 完成自身狀態該做的行為2. 在合適時機取父類的狀態參數切換狀態


class ClassicalCentral : MusicCentral() {

    override fun musicStyle() {
        println("莫札特")
        println("巴哈")
        println("蕭邦")

        println("Morning music finish play.")
        station?.changeStyle(DailyMusicRadioStation.Style.SOUL)
    }

}


class SoulCentral : MusicCentral() {

    override fun musicStyle() {
        println("Johnny")
        println("LoFi")

        println("Afternoon music finish play.")
        station?.changeStyle(DailyMusicRadioStation.Style.POP)
    }

}


class PopCentral : MusicCentral() {

    override fun musicStyle() {
        println("周杰倫")
        println("張學友")

        println("Night music finish play.")
        station?.changeStyle(DailyMusicRadioStation.Style.CLASSICAL)
    }

}

C. 環境類:狀態模式的環境類比起策略模式的環境類更加的重要,它對外 1. 提供切換狀態的方法(給其他子類使用),並 2. 提供操作的方法


class DailyMusicRadioStation {


    private val classical = ClassicalCentral().apply {
        station = this@DailyMusicRadioStation
    }
    private val soul = SoulCentral().apply {
        station = this@DailyMusicRadioStation
    }
    private val pop = PopCentral().apply {
        station = this@DailyMusicRadioStation
    }

    private var curStyle: MusicCentral = classical

    enum class Style {
        CLASSICAL,
        SOUL,
        POP
    }

    // 提供切換狀態的方法
    fun changeStyle(style: Style) {
        curStyle = when(style) {
            Style.CLASSICAL -> classical
            Style.SOUL -> soul
            Style.POP -> pop
        }
    }

    // 提供操作的方法
    fun playDailyMusic() {
        curStyle.musicStyle()
    }

}

使用者使用

● 切換狀態的責任交給狀態子類,而不是由外部主動切換(管理)狀態!


fun main() {

    DailyMusicRadioStation().apply {
        playDailyMusic()
        playDailyMusic()
        playDailyMusic()
        playDailyMusic()
    }

}

策略 vs. 狀態:最佳實踐

以 UML 設計圖來看,兩者的差異相差不大,但從上面範例來看,實際使用起來的功能相差其實是非常大的;差異比較如下

環境角色的差異

● 策略模式:環境角色作為一個委託,代理執行實際的算法

● 狀態模式:具有 紀錄狀態、操作狀態 的兩大功能,並擁有所有狀態的實現

目標解決方案的差異(設計想解決的問題)

● 策略模式:把內部實做的算法包裝,讓其對外部的影響降到最低

● 狀態模式:每個子類的狀態看似沒有關係,但其實 內部狀態的切換會影響最終的商業邏輯結果

封裝狀態並暴露行為,內部狀態的改變在外部看起來就像是同個方法但切換了作法

解決問題的方案不同

● 策略模式:只保證算法的功能,但切換算法要由外部決定

● 狀態模式:狀態的變化由環境角色、具體狀態共同完成(封裝了狀態的變化,但暴露了不同的行為、結果!

應用場景的不同

● 策略模式:策略模式由於相互可替換,所有算法必須平起平坐(平行的!)

● 狀態模式:一系列的狀態觸發了相同方法但不同結果的行為,常用於一個物件有 2 維描述的時候(有狀態、行為)

如果只有狀態,或是只有行為,那狀態模式就失去了其核心意義

複雜度不同

● 策略模式:對於設計來說相當清晰,並且也方便拓展,複雜度低

● 狀態模式:狀態模式下由於內部的切換邏輯,對外部產生了不同的行為結果,雖說方便拓展,但是要寫測試,測試商業邏輯,相對來說 複雜度較高


觀察者 vs 責任鏈

觀察者、責任鏈的相關性在於

A. 觸發性:都是透過一個函數調用,來達到觸發各個相關類的被調用

B. 觀察、被觀察:責任鏈當中的每個節點,都可以用觀察者(接收上一個任務結果)、被觀察者(傳遞結果給下一個任務)來解釋

責任鏈之責任交接

● 這裡來看 DNS 機制,DNS 可以幫我們把查看網域對應的 IP 地址

DNS 機制:當然不會由一個伺服器記住所有對應數據,DNS 協議會讓每個區域記住自己的網域名,查找不到才往上傳遞查詢!

這就是 責任鏈機制,交接責任到下一個任務

● UML 如下圖

● 使用標準責任鏈實做 DNS 傳遞查詢的機制

A. 請求者類:這裡做標準的儲存類;礎觀察者,不抽像話請求者

這個簡單的儲存類也可以稱為 BO(Business object) 對象


data class Recorder constructor(val domain: String, val ip: String, val owner: String) {
    override fun toString(): String {
        return "Domain: $domain, ip: $ip, owner: $owner"
    }
}

B. 責任抽象類:宣告每個子類都必須實做的方法(包括 判斷、處理


abstract class DnsServer {

    var nextServer: DnsServer? = null

    fun resolve(dns: String): Recorder {
        if (isRecord(dns)) {
            return echo(dns)
        }
        nextServer.let {
            if (it == null) {
                throw RuntimeException("Cannot resolve.")
            }
            return it.resolve(dns)
        }
    }

    // 判斷
    abstract fun isRecord(dns: String) : Boolean

    // 處理
    abstract fun echo(dns: String) : Recorder

    protected fun randomIp(): String {
        Random().apply {
            var ip = ""
            repeat(4) {
                ip += "${nextInt(255)}."
            }

            return ip.removeRange(ip.length - 1, ip.length)
        }
    }
}

C. 責任實做類:完成每個完成自生邏輯判斷,符合最少知識原則(它們不需要知道其他類的傳遞,只須關心自身邏輯)


class CityDnsService: DnsServer() {

    override fun isRecord(dns: String): Boolean {
        return dns.endsWith(".taipei").apply {
            println("${this@CityDnsService::class.simpleName} record? $this")
        }
    }

    override fun echo(dns: String): Recorder {
        return Recorder(dns, randomIp(), "Taipei 101 DNS")
    }

}

class CountryDnsService: DnsServer() {

    override fun isRecord(dns: String): Boolean {
        return dns.endsWith(".tw").apply {
            println("${this@CountryDnsService::class.simpleName} record? $this")
        }
    }

    override fun echo(dns: String): Recorder {
        return Recorder(dns, randomIp(), "Taiwan DNS")
    }

}

class WorldDnsService: DnsServer() {

    override fun isRecord(dns: String): Boolean {
        return true.apply {
            println("${this@WorldDnsService::class.simpleName} record? $this")
        }
    }

    override fun echo(dns: String): Recorder {
        return Recorder(dns, randomIp(), "World DNS")
    }

}

使用者使用


fun main() {
    val cityDnsService = CityDnsService().apply {
        nextServer = CountryDnsService().apply {
            nextServer = WorldDnsService()
        }
    }

    cityDnsService.resolve("www.androidJavaKt.tw").apply {
        println(this)
    }
}

觀察者之優化責任鏈

修正 DNS 修正

在上面我們使用責任鏈來完成了 DNS 的機制,但其實 並不完全實現了 DNS 機制,以下幾點尚未達成

A. DNS 會緩存之前已經請求過得數據(方便下次快速回覆)

B. DNS 只與自身的左右(節點)交互

● 透過觀察者修正之後,有以下差異

A. 雙重身份DNS 伺服器具有觀察者、被觀察者的雙重身份!

B. 業務邏輯:設定下一個時,其實是設定自己的觀察者,而被 通知更新時,也是 觸發事件 通知自己的觀察者

● UML 如下圖

● 首先要結合 觀察者模式,就必須先把觀察者的基礎結構建立


// 觀察者
interface IObserver<T> {
    fun update(data: T)
}


// 被觀察者
open class Observable<T> {

    var list = mutableSetOf<IObserver<T>>()

    fun notify(data: T) {
        list.forEach {
            it.update(data)
        }
    }
}

● 接著就可以按照 DNS 協議的邏輯來優化責任鏈

A. 責任鏈抽象類:建立責任鏈抽象類,該類修改很大,它 既是觀察者,又是被觀察者,所以必須實做關者者界面(IObserver),並繼承被觀察者(Observable

● 修改如下幾點

符合商業邏輯:添加 getOwner 抽象函數來取得每個 DNS 的名稱,之後可用


abstract fun getOwner() : String

添加觀察者:移除 nextServer 成員,改由註冊(添加)觀察者(IObserver)


fun setNextServer(nextServer: DnsServer2) {
    super.list.clear()

    // 註冊觀察者(IObserver)
    super.list.add(nextServer)
}

觸發被觀察者:透過觸發被觀察者,實際上就是通知觀察者(update 函數),在這裡就可以做責任鏈的原先判斷

如果無法處理,再往下一層處發(notify 函數)

一級對一級負責

並且在這裡我們可以完成 DNS 協議的概念,將請求後的網域修改為自己


override fun update(data: Recorder2) {
    if (isRecord(data.domain)) {
        echo(data.domain).apply {
            data.domain = domain
            data.ip = ip
        }
    } else {
        // 作為被觀察者,通知下一個觀察者
        notify(data)
    }
    
    // 將請求後的網域修改為自己
    data.owner = getOwner()
}

B. 責任鏈實做類:很簡單,修改不大


class CityDnsService2: DnsServer2() {

    override fun isRecord(dns: String): Boolean {
        return dns.endsWith(".taipei").apply {
            println("${this@CityDnsService2::class.simpleName} record? $this")
        }
    }

    override fun getOwner(): String {
        return "Taipei 101 DNS"
    }

}

class CountryDnsService2: DnsServer2() {

    override fun isRecord(dns: String): Boolean {
        return dns.endsWith(".tw").apply {
            println("${this@CountryDnsService2::class.simpleName} record? $this")
        }
    }

    override fun getOwner(): String {
        return "Taiwan DNS"
    }

}

class WorldDnsService2: DnsServer2() {

    override fun isRecord(dns: String): Boolean {
        return true.apply {
            println("${this@WorldDnsService2::class.simpleName} record? $this")
        }
    }

    override fun getOwner(): String {
        return "World DNS"
    }

}

C. 請求者類:修改不大,只將其成員修改為 var (可 setter)


data class Recorder2 constructor(var domain: String, var ip: String, var owner: String) {
    override fun toString(): String {
        return "Domain: $domain, ip: $ip, owner: $owner"
    }
}

使用者使用


fun main() {
    val cityDnsService = CityDnsService2()
    val countryDnsService = CountryDnsService2()
    val worldDnsService = WorldDnsService2()

    cityDnsService.setNextServer(countryDnsService)
    countryDnsService.setNextServer(worldDnsService)

    val record = Recorder2("www.androidJavaKt.org", "", "")

    cityDnsService.update(record)

    println(record)
}

觀察者 vs. 責任鏈:最佳實踐

鏈中的消息對象

● 傳統責任鏈:傳統責任鏈對象不會去改變傳遞的物件類型(可能從頭到尾都是 Person 對象,在傳遞過程中不會修改)

● 觸發行責任鏈:可以在過程中修改物件類型每個節點只須對鄰居節點負責即可!

上下節點關係

● 傳統責任鏈:節點之間較無關係,不修改物件類,只須為自身邏輯負責

● 觸發行責任鏈:節點之間由於修改了物件類型,所以產生較為緊密的(左右)關係,上級對下級的絕對信任

消息的分銷方式不同

● 傳統責任鏈:從頭到尾一一觸發,基本上每個鏈都會被觸發到(依照需求)

● 觸發行責任鏈:由於使用觀察者模式通知,通知方式可以多變,可以使用廣播形式觸發(for 迴圈),也可以跳躍式觸發(通知時判斷邏輯)

但相對的邏輯複雜度也高


更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式:Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式:Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響