美文网首页程序员Java设计模式
小M学设计模式:组合模式在TableView中的妙用

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

作者: 溪石iOS | 来源:发表于2019-03-21 23:18 被阅读8次

    徒弟小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,阅读代码中有任何问题,欢迎通过各种方式“骚扰”楼主。

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/prtkvqtx.html