Composite Pattern === ###### tags: `Design Pattern` `Composite Pattern` # 小故事 1. 菜單/選單 老王和阿花上次去StarBuck讀書約會之後, 這周想去電影院看神力女超人1984, 電影院的飲料是1杯50$, 口味自己選擇. ![](https://i.imgur.com/nDHOcSn.jpg) 門口擺著一台汽水調配器, 如上圖. 這台可以提供6種品牌的飲料(可苦可樂、芬達、雪碧、百事、維大力?、麥根)跟蘇打水, 選好品牌後, 在選擇口味; 可苦可樂有提供可樂, Zero, 香草, 櫻桃, 纖維+...; 芬達有橘子、蘋果、葡萄、青檸、芒果、水蜜桃、西瓜... 思考了一下, 這裡的飲品提供的選擇, 是一種```階層結構``` Soda本身是一個Root Component; 品牌則是Child Component;汽水口味則是Leaf Component. ```plantuml @startuml Sodas ^-- CocaCola Sodas ^-- Fanta Sodas ^-- Sprite Sodas ^-- Pepsi Sodas ^-- Vitali Sodas ^-- RootBeer Sodas ^-- Soda CocaCola ^-- Cola CocaCola ^-- ColaZero CocaCola ^-- ColaVanilla CocaCola ^-- ColaCherry Fanta ^-- Orange Fanta ^-- Apple Fanta ^-- Grape RootBeer ^-- Strawberry RootBeer ^-- Vanilla @enduml ``` 簡化了一下大概這樣呈現. 然後就建模存入資料庫了. 一切看似美好, 想說這樣設計 ```sql= CREATE TABLE `brand` ( `brand` int(11) unsigned NOT NULL, `name` varchar(45) DEFAULT NULL, PRIMARY KEY (`brand`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE `sodas` ( `id` int(10) unsigned NOT NULL, `name` varchar(45) NOT NULL, `brand_id` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` Sodas每個產品都有對到某一個品牌. 疑? 品項中, 有一個Soda蘇打水的產品沒品牌阿QQ 沒差, 建個自有品牌Self, Soda放在它底下. ```plantuml @startuml Sodas ^-- CocaCola Sodas ^-- Fanta Sodas ^-- Sprite Sodas ^-- Pepsi Sodas ^-- Vitali Sodas ^-- RootBeer Sodas ^-- Self CocaCola ^-- Cola CocaCola ^-- ColaZero CocaCola ^-- ColaVanilla CocaCola ^-- ColaCherry Fanta ^-- Orange Fanta ^-- Apple Fanta ^-- Grape RootBeer ^-- Strawberry RootBeer ^-- Vanilla Self ^-- Soda @enduml ``` ---------- 2. 決策樹 今天週一, 企劃想對遊戲內的玩家做個活動, 來發放活動序號, 兌換SSR武將. 下班前就要, 男性玩家發放SSR貂蟬, 女性玩家發放SSR關羽(草,男女不平等阿) 想了想, 不難, 就如下去修改 ```go= for player := range players { if player.Gender == Male { player.SendNotify("SSR貂蟬序號:XXYY1234") } else { player.SendNotify("SSR關羽序號:XXYY1235") } } ``` 好! 打完收工, 開心下班領SSR魔關羽(?) 週二來上班了, 企劃表示, 昨天活動反應非常好, 下載次數激增!!! 今天我們要推出新活動, 按照年紀, 年輕、成年、中年, 不同年紀做判斷, 要對不同年齡層做不同的刺激消費: 年輕人(<18)就贈送S級武將*5. 成年人([18, 25])送S級武將*10. 中年人([25,?])送SS級武將*5. 很急, 今天就要!!! 做完, 這個月業績獎金+5000$ ```go= for player := range players { switch { case player.Age < 18: player.SendNotify("S級武將*5") case player.Age < 25: player.SendNotify("S級武將*10") case player.Age < 999 : player.SendNotify("SS級武將*5") } } ``` 很棒! 打完收工, 開心下班領S級武將*5(?) 週三說要, 區分單身、結婚、有小孩的加上判斷 週四說要針對學生身份且非單身的做特別禮包... ... 這團代碼被一堆if-else/switch給弄的沒人看得懂了. 且還很難測試,被客戶投訴都沒收到序號QQ 週末來加班, 隔天通知業績獎金-5000$ 這是非常簡化的行銷規則```決策樹DecisionTree```, 根據性別、年紀不同來發放不同類型的活動序號, 刺激目標客群的消費來達到此目的. ![](https://i.imgur.com/GNx3ZF4.png) ```go= func Process(player model.Player) { if player.Gender == model.FEMALE { switch { case player.Age < 18: log.Println("S級武將*10") case player.Age < 25: log.Println("S級武將*15") case player.Age < 255: log.Println("SS級武將*10") } } else { switch { case player.Age < 18: log.Println("S級武將*5") case player.Age < 25: log.Println("S級武將*10") case player.Age < 255: log.Println("SS級武將*5") } } return } ``` 為了求快, 波動拳開始了- .- [參考網站](https://www.mdeditor.tw/pl/pBGR/zh-tw) ---------- 3. 資料庫一個表中, 該row有的parent_id的欄位指向同一表中其他的row record. ![](https://i.imgur.com/P4VZbAt.png) ![](https://i.imgur.com/R1MMEpX.png) ![](https://i.imgur.com/M4eZYQM.png) 4. AST語法樹 ```sql= SELECT column0 FROM table0 UNION SELECT column1 FROM table1 WHERE a = 1; ``` 這段經過Parser會被解析成一個與SQL語句 ![](https://i.imgur.com/DL1YYA5.png) 左子樹和右子樹, 本身就是個完整的樹. 只是被組合進來. # Composite Pattern > In software engineering, the composite pattern is a **partitioning** design pattern. > The composite pattern describes a group of objects that are treated the same way as a single instance of the same type of object. > The intent of a composite is to "compose" objects into **tree structures** to represent **part-whole** hierarchies. > Implementing the composite pattern lets clients **treat** individual objects and compositions **uniformly**. [UML](https://refactoringguru.cn/design-patterns/composite) # 小問題 * 既然要client針對Leaf和Compitee是一樣的, 那怎麼Leaf裡面沒有add/remove/getChildren等行為呢? * 跟Decorator有點像, 都是在類別當中儲存成員變數來完成功能上的擴展, 那有何不同呢?