小M學(xué)設(shè)計(jì)模式:組合模式在TableView中的妙用

徒弟小M接到一個(gè)私活,給朋友的川菜館做個(gè)訂餐APP,在開發(fā)點(diǎn)菜菜單時(shí),遇到了困難。
一開始他是這么做的,將菜單項(xiàng)放入一個(gè)數(shù)組作為TableView的數(shù)據(jù)源:

["宮保雞丁", "干燒魚", "回鍋肉", "麻婆豆腐", "家常豆腐", "黃燜鴨", "夫妻肺片", "鹽水鴨", "鍋巴肉片"]

可給朋友一看,朋友說不行,原來朋友不光做中晚餐,還兼做早餐,提供的是一些四川小吃,希望與主菜分開顯示,方便用戶選擇,于是菜單變成了這樣:


菜單分組

“相當(dāng)于兩個(gè)菜單組合”小M很自然想到,用二維數(shù)組將兩個(gè)菜單組織到一起:

[["宮保雞丁", "干燒魚", "回鍋肉", "麻婆豆腐", "家常豆腐", "黃燜鴨", "夫妻肺片", "鹽水鴨", "鍋巴肉片"], // 主菜
["擔(dān)擔(dān)面", "川北涼粉", "麻辣小面", "酸辣面", "酸辣粉"]] // 早餐

為了使兩個(gè)菜單組能分別展開/收起,小M開辟了兩個(gè)數(shù)組,用來表示菜單組“展開/收起”和組名:

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

顯示 cell 的代碼有點(diǎn)兒別扭,不過還在小M控制范圍內(nèi),只是需要小心處理數(shù)組的下標(biāo):


用數(shù)組實(shí)現(xiàn)

朋友對(duì)新菜單表示滿意,正在小M暗自慶幸時(shí),朋友一拍腦袋,說到:“哎呀,忘了加酒水單了,這可是賺錢的大頭啊,你可得幫我加上!”
小M看了一眼cellForRowAt 中已如亂麻的if-else,一時(shí)不知該從何下手了。

用組合模式進(jìn)行簡(jiǎn)化

為什么用二維數(shù)組加個(gè)菜單組這么麻煩呢?我們注意到 cellForRowAt 中的代碼主要是為了區(qū)分第一組/第二組,判斷依據(jù)是(居然是)indexPath.row ,由于菜單組會(huì)展開/收起,indexPath.row 對(duì)應(yīng)的菜單項(xiàng)也在變化,每增加一組,偏移的計(jì)算就要更新一次;
而 tableView 實(shí)際上不關(guān)心要顯示的是菜單組還是菜單項(xiàng),只要能正確獲得菜單項(xiàng)目和每項(xiàng)的數(shù)據(jù)就可以了,于是矛盾就在于:

對(duì)每個(gè)菜單項(xiàng)來說,必須區(qū)分是菜單組還是菜單項(xiàng),才能正確處理數(shù)據(jù);而對(duì)調(diào)用者來說,它們是一個(gè)整體,都是同一個(gè)菜單,像菜單這樣明顯有“整體/部分”關(guān)系的數(shù)據(jù)集合,就需要組合模式來幫忙了。

為對(duì)組合模式的作用有直觀的了解,我們先來看實(shí)現(xiàn)后達(dá)到的效果。

組合的訪問者

作為菜單的調(diào)用者,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 看來,根菜單、組菜單、菜單項(xiàng)之間,沒有任何區(qū)別,比如在處理展開菜單時(shí),didSelectRowAt 對(duì)所有 MenuItem 都處理了 isExpand ,并沒有具體區(qū)分組菜單還是菜單項(xiàng),isExpand 對(duì)兩者 count 的不同影響,由 MenuItem 自行處理,菜單項(xiàng)實(shí)際上沒有對(duì) isExpand 做任何處理(但依然實(shí)現(xiàn)了 isExpand,從而避免調(diào)用者做判斷)。

組合的構(gòu)造者

因?yàn)榻M合模式是一種結(jié)構(gòu)模式,該模式主要處理的是對(duì)象的結(jié)構(gòu)和它們的組合方式,而生成組合對(duì)象是一種行為,需要額外的訪問者,下面代碼片段展示了主菜的構(gòu)造過程:

        let mainCoursesMenu = MenuItem()
        mainCoursesMenu.name = "主菜"
        for name in ["宮保雞丁", "干燒魚", "回鍋肉", "麻婆豆腐", "家常豆腐", "黃燜鴨", "夫妻肺片", "鹽水鴨", "鍋巴肉片"] {
            let menuItem = MenuItem()
            menuItem.name = name
            mainCoursesMenu.add(item:menuItem)
        }
        self.menu.add(item:mainCoursesMenu)

組合對(duì)象的實(shí)現(xiàn)

MenuComponent 協(xié)議表示組菜單、菜單項(xiàng),統(tǒng)一它們的操作

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 實(shí)現(xiàn),這里以 count 方法為代表:

func count() -> Int {
        var count = 1 //自己為第一項(xiàng)
        if (self.isExpand) {
            for item in self.child {
               count += item.count()
            }
        }
        return count
    }

這里可以看出,主要是利用了遞歸對(duì)組合對(duì)象進(jìn)行了遍歷。

完整代碼請(qǐng)參閱SichuanFood,閱讀代碼中有任何問題,歡迎通過各種方式“騷擾”樓主。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容