最后更新日期: 2017-12-10
引言
最近一直在看《Head First 设计模式》一书,此篇文章是基于 “第九章-迭代器与组合模式”, 我将此节分为两个部分: 迭代器、组合模式。
强烈推荐此书。
什么是组合模式?
定义:
允许你讲对象组合成树形结构来表现“整体/部分”层次结构。 组合能让客户以一致的方式处理个别对象以及对象组合。
其实生活中有很多组合的例子:
- 文件管理系统: 文件管理系统就是典型的组合模式。 存在一个根目录,然后目录底下可能是文件、也可能是文件夹,组合起来,就构成了文件的管理。
- 公司的体制: 现在公司的体制也有点像组合. 公司有这一层层领导, 领导下面有各自团队,团队底下有项目组等,最后到底下的执行人员, 这种体制也很像一种树形的结构。
用 swift 来设计组合模式,需要理解其对应关系
- Component: 组合之中的接口的声明,定义 Leaf 与 Composite 的公共行为的接口, 对应 swift 中的 protocol;
- Leaf(树叶): 表示叶节点的对象, 不能存储其他对象. 在文件管理系统中表示每一个具体的文件;
- Composite(组件): 可以用来存储 Leaf 对象, 也可以存储 Composite 对象. 在文件管理系统中, 可表示每一个文件夹。
有时候,为了方便处理,你也可以将 Leaf 理解为 没有子节点的 Composite
组合模式设计菜单
需求:
现在有如下的需求:
- 设计早中晚菜单,
- 早餐包括: 包子、馒头、鸡蛋、粽子等;
- 午餐包括: 小鸡炖蘑菇、 大白菜、 卷心菜、羊肉等, 而且午餐还包括水果: 西瓜、香蕉、橙子等;
- 晚餐: 白酒、大虾等
对菜单设计需要更改合适,添加以及删除都需要比较方便。
分析
这种典型的树形结构图,可以利用组合模式来设计。 可以根据swift 的 协议扩展特效,和适合的设计出对应的模型
源码
Component <-> MenuComponet
/**
name 与 display() 是遵守次协议必须实现的
利用协议扩展,可以默认实现协议声明的内容
*/
protocol MenuComponet {
var name : String { get }
var price : Double? { get }
var isVegetarian : Bool { get }
func display()
func add(_ comp : MenuComponet)
func remove(_ comp : MenuComponet)
}
extension MenuComponet {
var price : Double? { return nil }
var isVegetarian : Bool { return false }
func add(_ comp : MenuComponet) {}
func remove(_ comp : MenuComponet) {}
}
Leaf <-> MenuItem
/**
这是每一个具体的菜单类,必须有价格金额等信息
当然,此处也是可以用struct来实现
*/
class MenuItem : MenuComponet {
var name: String
var price: Double
var isVegetarian: Bool
init(name: String, price: Double, isVegetarian: Bool) {
self.name = name
self.price = price
self.isVegetarian = isVegetarian
}
func display() {
print("(name) price is (price)")
}
}
Composite <-> Menu
/**
相当于惨淡类型,早餐,午餐,水果、晚餐的类型
它可以包含子的 Menu, 也可以包含 MenuItem
*/
class Menu : MenuComponet {
var name: String
var subComps = [MenuComponet]()
init(with name : String) {
self.name = name
}
func add(_ comp: MenuComponet) {
subComps.append(comp)
}
func remove(_ comp: MenuComponet) {
if let index = subComps.index(where: { (comp1) -> Bool in
return comp.name == comp1.name
}) {
subComps.remove(at: index)
}
}
func display() {
print(name)
subComps.forEach { $0.display()}
}
}
在 客户端(Client) 调用的时候, 我们可以实现对应的方法, 例如 display()
class Waiter {
var allMenus = [MenuComponet]()
func initial() -> Self {
let breakFastMenu = Menu(with: "早餐")
let bun = MenuItem(name: "包子", price: 2.5, isVegetarian: false)
let steamedBread = MenuItem(name: "馒头", price: 1, isVegetarian: false)
let egg = MenuItem(name: "鸡蛋", price: 1.5, isVegetarian: false)
let zongzi = MenuItem(name: "粽子", price: 3, isVegetarian: true)
breakFastMenu.add(bun)
breakFastMenu.add(steamedBread)
breakFastMenu.add(egg)
breakFastMenu.add(zongzi)
let lunchMenu = Menu(with: "午餐")
let chickenSoup = MenuItem(name: "小鸡炖蘑菇", price: 25, isVegetarian: false)
let cabbage = MenuItem(name: "大白菜", price: 10, isVegetarian: true)
let spinach = MenuItem(name: "卷心菜", price: 8, isVegetarian: true)
let lamb = MenuItem(name: "羊肉", price: 20, isVegetarian: false)
lunchMenu.add(chickenSoup)
lunchMenu.add(cabbage)
lunchMenu.add(spinach)
lunchMenu.add(lamb)
let lunchWithFruit = Menu(with: "水果")
let watermelon = MenuItem(name: "西瓜", price: 10, isVegetarian: false)
let orange = MenuItem(name: "橘子", price: 8, isVegetarian: false)
let banana = MenuItem(name: "香蕉", price: 5, isVegetarian: false)
lunchWithFruit.add(watermelon)
lunchWithFruit.add(orange)
lunchWithFruit.add(banana)
lunchMenu.add(lunchWithFruit)
let dinner = Menu(with: "晚餐")
let wine = MenuItem(name: "高级白酒", price: 100, isVegetarian: false)
let shrimp = MenuItem(name: "油焖大虾", price: 40, isVegetarian: false)
dinner.add(wine)
dinner.add(shrimp)
allMenus.append(breakFastMenu)
allMenus.append(lunchMenu)
allMenus.append(dinner)
return self
}
// 此处仅仅设计了一个 display 的功能, 你可以添加更多功能
func display() {
allMenus.forEach { $0.display() }
}
}
组合模式其实不是很复杂, 类似树形结构的都可以用组合模式来实现(App中的badge系统也可以如此设计)。