设计模式-组合模式

最后更新日期: 2017-12-10

引言

最近一直在看《Head First 设计模式》一书,此篇文章是基于 “第九章-迭代器与组合模式”, 我将此节分为两个部分: 迭代器、组合模式。

强烈推荐此书。


什么是组合模式?

定义:
允许你讲对象组合成树形结构来表现“整体/部分”层次结构。 组合能让客户以一致的方式处理个别对象以及对象组合。

其实生活中有很多组合的例子:

  • 文件管理系统: 文件管理系统就是典型的组合模式。 存在一个根目录,然后目录底下可能是文件、也可能是文件夹,组合起来,就构成了文件的管理。
  • 公司的体制: 现在公司的体制也有点像组合. 公司有这一层层领导, 领导下面有各自团队,团队底下有项目组等,最后到底下的执行人员, 这种体制也很像一种树形的结构。

用 swift 来设计组合模式,需要理解其对应关系

  • Component: 组合之中的接口的声明,定义 Leaf 与 Composite 的公共行为的接口, 对应 swift 中的 protocol;
  • Leaf(树叶): 表示叶节点的对象, 不能存储其他对象. 在文件管理系统中表示每一个具体的文件;
  • Composite(组件): 可以用来存储 Leaf 对象, 也可以存储 Composite 对象. 在文件管理系统中, 可表示每一个文件夹。

有时候,为了方便处理,你也可以将 Leaf 理解为 没有子节点的 Composite


组合模式设计菜单

需求:

现在有如下的需求:

  1. 设计早中晚菜单,
  • 早餐包括: 包子、馒头、鸡蛋、粽子等;
  • 午餐包括: 小鸡炖蘑菇、 大白菜、 卷心菜、羊肉等, 而且午餐还包括水果: 西瓜、香蕉、橙子等;
  • 晚餐: 白酒、大虾等

对菜单设计需要更改合适,添加以及删除都需要比较方便。

分析

这种典型的树形结构图,可以利用组合模式来设计。 可以根据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系统也可以如此设计)。

posted @ 2017-12-10 20:37  洒水先生  阅读(101)  评论(0编辑  收藏  举报