小M学设计模式:组合模式在TableView中的妙用

徒弟小M接到一个私活,给朋友的川菜馆做个订餐APP,在开发点菜菜单时,遇到了困难。
一开始他是这么做的,将菜单项放入一个数组作为TableView的数据源:

["宫保鸡丁", "干烧鱼", "回锅肉", "麻婆豆腐", "家常豆腐", "黄焖鸭", "夫妻肺片", "盐水鸭", "锅巴肉片"]

可给朋友一看,朋友说不行,原来朋友不光做中晚餐,还兼做早餐,提供的是一些四川小吃,希望与主菜分开显示,方便用户选择,于是菜单变成了这样:


菜单分组

“相当于两个菜单组合”小M很自然想到,用二维数组将两个菜单组织到一起:

[["宫保鸡丁", "干烧鱼", "回锅肉", "麻婆豆腐", "家常豆腐", "黄焖鸭", "夫妻肺片", "盐水鸭", "锅巴肉片"], // 主菜
["担担面", "川北凉粉", "麻辣小面", "酸辣面", "酸辣粉"]] // 早餐

为了使两个菜单组能分别展开/收起,小M开辟了两个数组,用来表示菜单组“展开/收起”和组名:

var groupExpandFlag:Array<Bool> = [true, true]
var groupName:Array<String> = ["主菜", "早餐"]

显示 cell 的代码有点儿别扭,不过还在小M控制范围内,只是需要小心处理数组的下标:


用数组实现

朋友对新菜单表示满意,正在小M暗自庆幸时,朋友一拍脑袋,说到:“哎呀,忘了加酒水单了,这可是赚钱的大头啊,你可得帮我加上!”
小M看了一眼cellForRowAt 中已如乱麻的if-else,一时不知该从何下手了。

用组合模式进行简化

为什么用二维数组加个菜单组这么麻烦呢?我们注意到 cellForRowAt 中的代码主要是为了区分第一组/第二组,判断依据是(居然是)indexPath.row ,由于菜单组会展开/收起,indexPath.row 对应的菜单项也在变化,每增加一组,偏移的计算就要更新一次;
而 tableView 实际上不关心要显示的是菜单组还是菜单项,只要能正确获得菜单项目和每项的数据就可以了,于是矛盾就在于:

对每个菜单项来说,必须区分是菜单组还是菜单项,才能正确处理数据;而对调用者来说,它们是一个整体,都是同一个菜单,像菜单这样明显有“整体/部分”关系的数据集合,就需要组合模式来帮忙了。

为对组合模式的作用有直观的了解,我们先来看实现后达到的效果。

组合的访问者

作为菜单的调用者,tableView的代码如下:

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.menu.count() - 1 // 根菜单不需要显示
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellID)
        let menuItem = self.menu.itemAt(index: indexPath.row + 1)
        
        var indent = "    "
        if ((menuItem?.isGroup)!) {
            indent = ""
        }
        cell?.textLabel?.text = "\(indent)\(menuItem?.name ?? "")"
        return cell!
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        var menuItem = self.menu.itemAt(index: indexPath.row + 1)
        menuItem!.isExpand = !(menuItem!.isExpand)
        tableView.reloadData()
    }

除了显示所需的代码外,没有任何多余的代码,从 tableView 看来,根菜单、组菜单、菜单项之间,没有任何区别,比如在处理展开菜单时,didSelectRowAt 对所有 MenuItem 都处理了 isExpand ,并没有具体区分组菜单还是菜单项,isExpand 对两者 count 的不同影响,由 MenuItem 自行处理,菜单项实际上没有对 isExpand 做任何处理(但依然实现了 isExpand,从而避免调用者做判断)。

组合的构造者

因为组合模式是一种结构模式,该模式主要处理的是对象的结构和它们的组合方式,而生成组合对象是一种行为,需要额外的访问者,下面代码片段展示了主菜的构造过程:

        let mainCoursesMenu = MenuItem()
        mainCoursesMenu.name = "主菜"
        for name in ["宫保鸡丁", "干烧鱼", "回锅肉", "麻婆豆腐", "家常豆腐", "黄焖鸭", "夫妻肺片", "盐水鸭", "锅巴肉片"] {
            let menuItem = MenuItem()
            menuItem.name = name
            mainCoursesMenu.add(item:menuItem)
        }
        self.menu.add(item:mainCoursesMenu)

组合对象的实现

MenuComponent 协议表示组菜单、菜单项,统一它们的操作

protocol MenuComponent {
    var name:String { get }
    var child:Array<MenuComponent> { get }
    var isExpand:Bool { get set }
    var isGroup:Bool { get }
    func add(item:MenuComponent)
    func itemAt(index:Int) -> MenuComponent?
    func count() -> Int
}

MenuItem 实现,这里以 count 方法为代表:

func count() -> Int {
        var count = 1 //自己为第一项
        if (self.isExpand) {
            for item in self.child {
               count += item.count()
            }
        }
        return count
    }

这里可以看出,主要是利用了递归对组合对象进行了遍历。

完整代码请参阅SichuanFood,阅读代码中有任何问题,欢迎通过各种方式“骚扰”楼主。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容