--- title: 'PK - 行為模式' disqus: kyleAlien --- PK - 行為模式 === ## Overview of Content 行為模式包括責任鏈、命令、解釋器、迭代、仲介、備忘錄、觀察者、狀態、策略、模板、訪問者模式(相對的多),它們有很多相似點 每種不同的設計都有其「**特殊的行為**」 :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**行為模式 – 5 個 PK | Command vs Strategy vs State | Observer vs Chain | 最佳實踐**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) ::: [TOC] ## 命令、策略 命令、策略設計模式 - 兩者區別 * **策略模式**: **封裝算法**,並且每個算法都可以相互替換、並獨立維護 > **每個算法皆為原子業務**,不可再拆分的任務 * **命令模式**: **對 ++動作++ 的解耦**,把一個動作(行為)封裝程一個對象(接收者),並透過執行對象(接收命令者)執行動作 > 解耦「執行」、「行為」兩個動作,**行為有時候不可相互替換**(不同設計概念就會產生不同的特性,之後會說明) ### 策略之替換算法 * 策略模式的核心概念在於 **算法的相互替換** Example: 接下來實做一個儲存數據的方案,每個方案代表不同算法,並且這些方案都可以相互替換 > ![](https://hackmd.io/_uploads/Bk1CtQMLn.png) 1. **抽象算法**: 對外說明使用該合約界面可以取得、設定數據… 以抽象的角度來說明這界的功能;而具體的方案實做要了解實做類(就是讀 `Document` 啦~) ```kotlin= interface IStorage { fun getData() : Any fun setData(data: Any) } ``` 2. **實現算法**:各個算法的實做 ```kotlin= // 儲存 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) } } ``` 3. **環境類**: 環境類依賴於界面(`interface`, `IStorage`),而非實做,**符合依賴倒置原則** > 之所以將其稱為環境類,是因為它程載了 `IStorage` 的實體,但是又不是真正的實做類,相對來講是運行界面的平台 ```kotlin= class Context constructor(var storage: IStorage) { fun getData() : String { return storage.getData() as String } fun setData(data: String) { storage.setData(data) } } ``` * **使用者**:使用者會直接接觸、選擇具體的算法(這裡不符合迪米特原則),並透過 `Context` 環境類來運行(但環境類符合依賴倒置的方案,依賴抽象而不是實做) ```kotlin= fun main() { val initStorage = ListStorage() val contextEnv = Context(initStorage).apply { setData("123") println(getData()) } contextEnv.apply { storage = FieldStorage() setData("888") println(getData()) } } ``` > ![](https://hackmd.io/_uploads/Hyr-omGLn.png) ### 命令之封裝 * 命令模式的核心概念在於 **++封裝++命令** **讓請求者、實現者(接收者)解耦,不管接收者如何變化,都不會影響請求者** :::info * 實做範例如上,實現與上面策略模式相同的需求(不同的儲存方案) ::: 1. **抽象命令、具體命令**: 使用跟策略模式相同的界面是抽象(對外保證)、實做的類(對內實做、實現) ```kotlin= 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) } } ``` 2. **抽象命令**: 命令的基礎類,它的核心要點在於「**抽象化命令**」,並且依賴於抽象接收者(`IStorage`),符合依賴倒置原則 ```kotlin= abstract class CommonCmd { companion object { @JvmStatic protected val fieldStorage: IStorage = FieldStorage() @JvmStatic protected val listStorage: IStorage = ListStorage() } abstract fun controlData(data: String) : Any } ``` 3. **具體命令**: 既然上面有抽象化的命令,那這裡就會有命令的實做類; 它會將每個命令細分為不同的類,每個類代表了一個功能… ```kotlin= 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() } } ``` 4. **調用者**(環境類): 其功能就跟策略模式的 **環境類相同**,它的存在是為了調用抽象命令; 但它可以持有 `1 ~ 多個` **抽象命令**,並且可以在這邊做出不同花樣(紀錄、替換、取消... 等等行為) ```kotlin= class Invoker(var cmd: CommonCmd) { fun controlData(data: String) : Any { return cmd.controlData(data) } } ``` * **使用者調用**: **抽象命令的替換(實做替換)是個關鍵**,替換命令代表了不同的行為實做 :::warning * 但相對的,這種設計方式不符合迪米特(最少知識)原則 使用者仍要了解到每個實做的含意 ::: ```kotlin= 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("")) } } ``` > ![](https://hackmd.io/_uploads/BJjrW4MI2.png) * 在這裡其實有另外一個關鍵概念: 以上我們使用策略模式的實做,它的 **界面是依照功能分類**,在這裡我們可以換個抽象概念,**依照 ==職責== 去重新規劃界面** 1. 抽象接收者界面:**改成依照責任去規劃** ```kotlin= interface IStorageData { fun fieldDataControl(data: Any) : Any fun listDataControl(data: Any) : Any } ``` 2. **接收者實做**:依照具體接收者(執行者)實做 ```kotlin= 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] } } ``` 3. **抽象命令**:命令使用的類也會跟著修改,修改為依照責任的接收者(執行者) ```kotlin= 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 } ``` 4. **具體命令**:既然接收者(執行者)更改,那命令的調用方式也會跟著修改 > 雖然依照接收者修改邏輯,仍符單一職責,**一個類包裝一個命令** ```kotlin= 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("") } } ``` * 調用者:調用者只須修改依賴界面即可 ```kotlin= class Invoker2(var cmd: CommonCmd2) { fun controlData(data: String) : Any { return cmd.controlData(data) } } ``` :::success - 從這裡可以看出來命令模式並不一定要跟策略相同,上面 **依照責任設計界面,就不適用於策略模式!**(因為 **責任不可相互替換**) ::: ### 命令 vs 策略 - 最佳實踐 * **關注點不同** * **策略模式**:關注於算法的完整性、封裝性,只有同時具備這兩個特點才能簡單、自由切換不同算法 * **命令模式**:關注於解耦呼叫者、執行者(接收者),解耦兩者是這個模式最注重的要點,並且會細分每個命令(一個命令一個類) :::info 由於封裝了命令,所以可以對命令做不同的處理… 像是撤銷、紀錄 ::: * **角色功能不同** * **策略模式**:**具體算法是一個原子業務**,一旦變更就是對算法整體的變更 * **命令模式**:命令模式只要沒有個接收者,其實就跟策略模式差不多;換個角度來說,**命令模式的抽象算法 + 實做算法,就類似於策略模式的執行者(接收者)** :::info 可以清楚拆分請求者、執行者的關係,當執行只被修改,也不會影響到請求者 ::: ## 策略、狀態 兩者的目標(++業務的關注點++)不同 * **策略模式**: 1. 封裝算法,並交替算法,**算法之間不會相互交集(也不會調用到)** 2. **策略的切換由使用者操作** * **狀態模式**: 1. 封裝不同狀態,**透過狀態的切換達到相同方法,不同行為反應** 2. **狀態的切換可由外部、也可由內部(子類)切換** ### 策略之獨立算法 * 策略模式封裝算法,並且每個算法都可以獨立維護,不同算法之間沒有相互交集 :::info * 以播放音樂為例,每種音樂的風格都不同,不同風格用不同策略,要切換風格時也由使用者切換 > ![](https://hackmd.io/_uploads/SJQKxAEI2.png) ::: 1. **抽象策略**:每個類的共同抽象方法 > 每個歌曲都必須區分哪個歌手是屬於哪種曲風 ```kotlin= interface IMusic { fun musicStyle() } ``` 2. **策略實做**:**每個策略只須完成自身邏輯即可** ```kotlin= 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("張學友") } } ``` 3. **環境類**:操作策略的環境,隔離使用者直接操作實例;並且這邊對外開放的方法可以略做修改,不必與策略同名(可以再進行包裝) ```kotlin= class MusicRadioStation constructor(var music: IMusic) { fun playMusic() { music.musicStyle() } } ``` * **使用者使用**:在這邊使用者要關注具體作法(不符合迪米特原則),並 **由使用者按照商業邏輯切換算法** ```kotlin= fun main() { MusicRadioStation(Classical()).apply { println("--- Morning ---") playMusic() println("--- Afternoon ---") music = Soul() playMusic() println("--- Night ---") music = Pop() playMusic() } } ``` ### 狀態之自動切換 * 狀態模式會包裝每個狀態,並在適當時機由內部自己切換不同狀態 :::info * 同樣實現音樂風格的播放,不過這邊多了個商業邏輯,每個時段播放不同風格的音樂(也就是 **按照時間切換狀態**) > ![](https://hackmd.io/_uploads/rkjw-CEL2.png) ::: 1. **抽象狀態**:與策略模式相同,有一個必須實現的方法,但是 **它多了一個環境參數,這個環境參數相當重要,它用來給子類切換狀態用** ```kotlin= abstract class MusicCentral { var station: DailyMusicRadioStation? = null abstract fun musicStyle() } ``` 2. **狀態實做**:這裡有時做需要達成 ++兩件事++,^1.^ **完成自身狀態該做的行為**、^2.^ **在合適時機取父類的狀態參數切換狀態** ```kotlin= 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) } } ``` 3. **環境類**:狀態模式的環境類比起策略模式的環境類更加的重要,**它對外 ^1.^ 提供切換狀態的方法**(給其他子類使用),並 ^2.^ 提供操作的方法 ```kotlin= 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() } } ``` * **使用者使用**: :::info * 切換狀態的責任交給狀態子類,而不是由外部主動切換(管理)狀態! ::: ```kotlin= fun main() { DailyMusicRadioStation().apply { playDailyMusic() playDailyMusic() playDailyMusic() playDailyMusic() } } ``` > ![](https://hackmd.io/_uploads/SkGV2a4U2.png) ### 策略 vs. 狀態 - 最佳實踐 以 UML 設計圖來看,兩者的差異相差不大,但從上面範例來看,實際使用起來的功能相差其實是非常大的;差異比較如下 * **環境角色的差異** * 策略模式:環境角色作為一個委託,代理執行實際的算法 * 狀態模式:具有 **紀錄狀態、操作狀態** 的兩大功能,並擁有所有狀態的實現 * **目標解決方案的差異**(設計想解決的問題) * 策略模式:把內部實做的算法包裝,讓其對外部的影響降到最低 * 狀態模式:每個子類的狀態看似沒有關係,但其實 **內部狀態的切換會影響最終的商業邏輯結果** > 封裝狀態並暴露行為,內部狀態的改變在外部看起來就像是同個方法但切換了作法 * **解決問題的方案不同** * 策略模式:只保證算法的功能,但切換算法要由外部決定 * 狀態模式:狀態的變化由環境角色、具體狀態共同完成(**封裝了狀態的變化,但暴露了不同的行為、結果!**) * **應用場景的不同**: * 策略模式:策略模式由於相互可替換,**所有算法必須平起平坐**(平行的!) * 狀態模式:**一系列的狀態觸發了相同方法但不同結果的行為**,常用於一個物件有 2 維描述的時候(有狀態、行為) > 如果只有狀態,或是只有行為,那狀態模式就失去了其核心意義 * **複雜度不同**: * 策略模式:對於設計來說相當清晰,並且也方便拓展,複雜度低 * 狀態模式:狀態模式下由於內部的切換邏輯,對外部產生了不同的行為結果,雖說方便拓展,但是要寫測試,測試商業邏輯,相對來說 **複雜度較高** ## 觀察者、責任鏈 觀察者、責任鏈的相關性在於 1. **觸發性**:都是透過一個函數調用,來達到觸發各個相關類的被調用 2. **觀察、被觀察**:責任鏈當中的每個節點,都可以用觀察者(接收上一個任務結果)、被觀察者(傳遞結果給下一個任務)來解釋 ### 責任鏈之責任交接 :::info * 這裡來看 DNS 機制,DNS 可以幫我們把查看網域對應的 IP 地址 **DNS 機制**:當然不會由一個伺服器記住所有對應數據,DNS 協議會讓每個區域記住自己的網域名,查找不到才往上傳遞查詢! > 這就是 **責任鏈機制**,交接責任到下一個任務 > > ![](https://hackmd.io/_uploads/BJEqcQII2.png) * UML 如下圖 > ![](https://hackmd.io/_uploads/BkyHFwPL2.png) ::: * 使用標準責任鏈實做 DNS 傳遞查詢的機制 1. **請求者類**:這裡做標準的儲存類;礎觀察者,不抽像話請求者 :::success 這個簡單的儲存類也可以稱為 BO(`Business object`) 對象 ::: ```kotlin= data class Recorder constructor(val domain: String, val ip: String, val owner: String) { override fun toString(): String { return "Domain: $domain, ip: $ip, owner: $owner" } } ``` 2. **責任抽象類**:宣告每個子類都必須實做的方法(包括 **判斷、處理**) ```kotlin= 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) } } } ``` 3. **責任實做類**:完成每個完成自生邏輯判斷,**符合最少知識原則(它們不需要知道其他類的傳遞,只須關心自身邏輯)** ```kotlin= 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") } } ``` * **使用者使用**: ```kotlin= fun main() { val cityDnsService = CityDnsService().apply { nextServer = CountryDnsService().apply { nextServer = WorldDnsService() } } cityDnsService.resolve("www.androidJavaKt.tw").apply { println(this) } } ``` > ![](https://hackmd.io/_uploads/SJ7kI7IUh.png) ### 觀察者之優化責任鏈 :::warning * **修正 DNS 修正**? 在上面我們使用責任鏈來完成了 DNS 的機制,但其實 **並不完全實現了 DNS 機制**,以下幾點尚未達成 1. DNS 會緩存之前已經請求過得數據(方便下次快速回覆) 2. DNS 只與自身的左右(節點)交互 > ![](https://hackmd.io/_uploads/HkLzoXILn.png) :::success * 透過觀察者修正之後,有以下差異 1. **雙重身份**:**DNS 伺服器具有觀察者、被觀察者的雙重身份!** 2. **業務邏輯**:設定下一個時,其實是設定自己的觀察者,而被 **通知更新時,也是 ++觸發事件++ 通知自己的觀察者** ::: * UML 如下圖 > ![](https://hackmd.io/_uploads/Hy6QcwvI2.png) ::: * 首先要結合 **觀察者模式**,就必須先把觀察者的基礎結構建立 ```kotlin= // 觀察者 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 協議的邏輯來優化責任鏈 1. **責任鏈抽象類**:建立責任鏈抽象類,該類修改很大,它 **既是觀察者,又是被觀察者**,所以必須實做關者者界面(`IObserver`),並繼承被觀察者(`Observable`) * 修改如下幾點 * **符合商業邏輯**:添加 `getOwner` 抽象函數來取得每個 DNS 的名稱,之後可用 ```kotlin= abstract fun getOwner() : String ``` * **添加觀察者**:移除 nextServer 成員,改由註冊(添加)觀察者(IObserver) ```kotlin= fun setNextServer(nextServer: DnsServer2) { super.list.clear() // 註冊觀察者(IObserver) super.list.add(nextServer) } ``` * **觸發被觀察者**:透過觸發被觀察者,實際上就是通知觀察者(`update` 函數),在這裡就可以做責任鏈的原先判斷 如果無法處理,再往下一層處發(`notify` 函數) > 一級對一級負責 :::info 並且在這裡我們可以完成 DNS 協議的概念,將請求後的網域修改為自己 ::: ```kotlin= 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() } ``` :::spoiler 責任鏈抽象類 - 全程式 ```kotlin= abstract class DnsServer2: Observable<Recorder2>(), IObserver<Recorder2> { fun setNextServer(nextServer: DnsServer2) { super.list.clear() // 註冊觀察者(IObserver) super.list.add(nextServer) } abstract fun isRecord(dns: String) : Boolean fun echo(dns: String) : Recorder2 { return Recorder2(dns, randomIp(), getOwner()) } abstract fun getOwner() : String protected fun randomIp(): String { Random().apply { var ip = "" repeat(4) { ip += "${nextInt(255)}." } return ip.removeRange(ip.length - 1, ip.length) } } 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() } } ``` ::: 2. **責任鏈實做類**:很簡單,修改不大 ```kotlin= 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" } } ``` 3. **請求者類**:修改不大,只將其成員修改為 var (可 `setter`) ```kotlin= data class Recorder2 constructor(var domain: String, var ip: String, var owner: String) { override fun toString(): String { return "Domain: $domain, ip: $ip, owner: $owner" } } ``` * **使用者使用** ```kotlin= 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) } ``` > ![](https://hackmd.io/_uploads/r188-DPI2.png) ### 觀察者 vs. 責任鏈 - 最佳實踐 * **鏈中的消息對象** * 傳統責任鏈:**傳統責任鏈對象不會去改變傳遞的物件類型**(可能從頭到尾都是 Person 對象,在傳遞過程中不會修改) * 觸發行責任鏈:**可以在過程中修改物件類型**,**每個節點只須對鄰居節點負責即可!** * **上下節點關係** * 傳統責任鏈:節點之間較無關係,不修改物件類,只須為自身邏輯負責 * 觸發行責任鏈:**節點之間由於修改了物件類型,所以產生較為緊密的(左右)關係,上級對下級的絕對信任** * **消息的分銷方式不同** * 傳統責任鏈:從頭到尾一一觸發,基本上每個鏈都會被觸發到(依照需求) * 觸發行責任鏈:由於使用觀察者模式通知,**通知方式可以多變**,可以使用廣播形式觸發(for 迴圈),**也可以跳躍式觸發**(通知時判斷邏輯) > 但相對的邏輯複雜度也高 ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式`