分享&探討組合模式和建造者模式

最近幫朋友做了一款應(yīng)用的功能模塊,本來是屬于原生APP的范疇,但它比較特殊,這是一款面向“廚房設(shè)計”的APP,主要面向的國家是加拿大,所以和國內(nèi)的廚房有不小的差距,但給家里裝修過的同學(xué)應(yīng)該都有所了解,廚房設(shè)計是非常重要的一塊兒,那么這款應(yīng)用,用戶在選擇具體的廚房以后,可以通過APP對廚房進行設(shè)計,即通過APP來組裝,調(diào)整你的廚房配置,比如:

1.有幾面墻,每面墻的寬度是多少
2.在墻上是否有窗戶,窗戶在哪面墻上,窗戶的寬度是多少,離墻角的距離
3.廚房里是否有島,英文單詞叫Island,大家應(yīng)該都見到過,只是可能不太知道叫什么,如圖:
image.png
中間這就是島,就像我們的餐桌一樣,但他通常是可以放水盆(Basin)的,這在國外特別常見,這個島是在廚房的中間的,還有一種叫半島,即有一邊是靠著某一堵墻的,但本質(zhì)上都一樣,和廚房的空間設(shè)計有一定的關(guān)系。

這里再給出關(guān)于島的頂視圖(純科普下,和技術(shù)沒關(guān)系哈哈):

image.png
image.png

繼續(xù)說:

4.水盆的位置放在哪里,是窗口下,某一面墻,還是放在島上,水盆是廚房必備的
5.爐頭在哪里,這個爐頭就是廚房的爐灶,知道爐頭的位置,也就能定位爐灶了,他可以放在墻上,也可以放在島上,但上面的水盆和爐頭不能都放在島上,這里是細節(jié)的部分
6.是否有冰箱,冰箱在哪面墻邊,冰箱的尺寸是多少
7.當冰箱大于一定的尺寸后,是否要安裝儲物柜(pantry),如果需要,儲物柜的尺寸是多少

細節(jié)上就是這么多,用戶在做出選擇以后,會進入到一個3D的預(yù)覽視圖中,通過3D來演示用戶自己“設(shè)計”的立體效果圖,并可以進行360度的切換,直觀方便,一目了解(據(jù)說那邊人工設(shè)計圖紙非常的昂貴,而且通勤又不方便,所以通過APP的形式就可以節(jié)省人力和財力了),那么這部分就需要使用3D渲染引擎來做,選擇了U3D,在進入3D預(yù)覽圖之間的一些UI流程,也是通過UGUI來實現(xiàn)。

那么通過上面的需求,如何去設(shè)計他的結(jié)構(gòu)呢?

廚房里可以包含窗戶,水盆,爐灶,冰箱,島等等
我最先想到的是組合模式Composite Parttern),即“體現(xiàn)部分和整體”的樹形“層次”結(jié)構(gòu),猶如公司的組織架構(gòu),但組合模式的核心是讓部分和整體具有一致性,即對用戶來說,單一的對象和組合的對象是一樣的,從代碼層面來說,他們均繼承自于同一個抽象類,實現(xiàn)Add,Remove這些抽象方法,并且相互之間,可以任意的組合,通常有一個根結(jié)點Root,然后下面是組合組件,組合組件可以添加其它多個組件,還有稱為葉子結(jié)點(Leaf),即他是最下層的,他不可以再添加其它的組件,只能被添加,但葉子結(jié)點也保留了Add,Remove這些抽象接口的使用,對外部保持一致性。

所以開始設(shè)計了KitchenBase,抽象基類,包括了上面的抽象方法Add,Remove等等,然后定義Wall,Window,Basin,Kitchen Range,Fridge,Pantry這些廚房里會包括的組件類,均繼承自KitchenBase,但在實際開發(fā)中慢慢發(fā)現(xiàn)使用“組合模式”并不合理,
原因如下:

1.當前需求并沒有多層次的情況,廚房里包含的這些組件均是平行的關(guān)系(不考慮具體每個組件內(nèi)部的細節(jié),比如冰箱里的細節(jié))
2.強調(diào)部分與整體,并且互相之間,可以任意的組合,任次的層次,而上面這些平行關(guān)系的組件,誰組合誰并不合理,冰箱下

包含水盆,爐灶下包含窗口,島包含窗戶和墻等等,顯然是不合理的,所以這本質(zhì)上還是一個包含has-a的關(guān)系,Kitchen廚房里面,可以添加的N個部件,這些部件之間可以派生自一個父類,但他們之間組合顯然不具備明確的合理性。

那么,Kitchen類,他有自己的一些屬性,比如加拿大的廚房被總結(jié)城了4大分類,I型(單面墻),II型(雙面墻),L型,和U型,這4種類型,每一種主類型還有多個子類型,即有多個不同的變化,比如說I型就分I型和I型+島的,L型也分L型和L型+島,還有L型+半島,U型就更多,多達10幾種,而冰箱,水盆,墻體,窗戶這些和Kitchen本身并不具備繼承關(guān)系,只是一種包含關(guān)系。

那么我便將原有組合模式的設(shè)計,重構(gòu)成了現(xiàn)在這種簡單”包含的”關(guān)系“。

最佳的組件模式案例

目前我個人認為,組件模式最佳的案例是UI組件,比如Text,Image,Label,ScrollView,他們平行,可以獨立的存在,也可以互相的組件成一個復(fù)雜的組件,Text可以包含Image,Image也可以包含Text,Label也可以包含Image和Text等等,任意的組件,部分和整體具有一致性,即我們可以通過相同的“行為”去操作他們。

現(xiàn)在,廚房設(shè)計的邏輯關(guān)系已經(jīng)清晰了,去除了不合適的組件模式,所以在下面的需求中,讓我想到了另外一種設(shè)計模式的應(yīng)用:

當用戶去選擇一個類型的廚房以后,我們需要引導(dǎo)用戶,引導(dǎo)他們進行一步一步的操作設(shè)置,比如1,2,3,4,5步,每一步的設(shè)置由系統(tǒng)設(shè)定好,用戶根據(jù)提示填選各種參即可,那么,不同廚房的類型肯定是會擁有不同的組件,比如有的帶島,有的沒島,窗口是可以選擇的,冰箱也是可以選擇的,你不選擇冰箱也就沒有儲物柜pantry什么事兒,這種結(jié)構(gòu)也是包含的關(guān)系,而且是“動態(tài)的”,用戶選擇不要窗戶,就可以直接從Kitchen類中刪除了,流程就是,我們只需要去添加指定的組件,然后遍這些組件,顯示對應(yīng)的UI,并進行相應(yīng)的邏輯控制,更新數(shù)據(jù)等等。

這其實是一個“構(gòu)建的”過程,根據(jù)不同的類型來構(gòu)建這個復(fù)雜的對象,這讓我想到了

(“建造者模式”Builder Pattern),通過建造者模來一步一步的構(gòu)建復(fù)雜的對象,特點是靈活,

尤其是針對那些參數(shù)比較多的,組件比較多,根據(jù)需求一步一步的構(gòu)建,最典型的一個案例就是
Android中AlertDialog的設(shè)計:

AlertDialog.Builder builder=new AlertDialog.Builder(context);
        builder.setIcon(R.drawable.icon);
        builder.setTitle("Title");
        builder.setMessage("Message");
        builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //TODO
            }
        });

使用起來很靈活,對話框通常是參數(shù)式的,但組件形式也是同理,那么,自然的,我創(chuàng)建一個KitchenBuilder來構(gòu)建不同類型廚房所需要的不同的組件。

下面是其中的代碼片斷,大致的效果(部分代碼還未重構(gòu)):

using System.Collections;
using System.Collections.Generic;
using kitchen.extension;
using kitchen.puremvc;
using UnityEngine;

/// <summary>
/// create KitchenDesign Builder
/// </summary>
public class KitchenBuilder {

    private KitchenDesign Kitchen;

    public KitchenBuilder (KITCHEN_TYPES mainType, KITCHEN_SUB_TYPES subType) {
        Kitchen = new KitchenDesign ( mainType, subType);
    }

    public KitchenBuilder AddGuidePage () {

        Kitchen.Add (new Guide ().SetData (new GuideStepData () { Owner = KitchenBase.WALL, Text = LABEL_WALL_INTRO, Image = "xxxx.png", Step = false }));
        return this;
    }
    public KitchenBuilder AddWall (string data) {
        Kitchen.Add (new Wall ().SetData (new WallStepData () { WallConfig = data }));
        return this;
    }

    public KitchenBuilder AddIsland () {
        Kitchen.Add (new IsLand ());
return this;
    }
    public KitchenBuilder AddWindow () {
        Kitchen.Add (new Window ().SetData (new WindowStepData { guideData = { Owner = KitchenBase.WINDOW, Text = LABEL_KITCHEN_INTRO, Image = "xxxx.png", Step = true } }));
        return this;
    }
    public KitchenBuilder AddBasin () {
        Kitchen.Add (new Basin ().SetData (new BasinStepData { guideData = { Owner = KitchenBase.BASIN, Text = LABEL_BASIN_INTRO, Image = "xxxx.png", Step = false } }));
        return this;

    }

    public KitchenBuilder AddFridge () {

        Kitchen.Add (new Fridge ().SetData (new FridgeStepData { guideData = { Owner = KitchenBase.FRIDGE, Text = LABEL_FRIDGE_INTRO, Image = "xxxx.png", Step = true } }));
        return this;

    }

    public KitchenBuilder AddKitchenRange () {
        Kitchen.Add (new KitchenRange ().SetData (new KitchenRangeStepData { guideData = { Owner = KitchenBase.KITCHEN_RANGE, Text = LABEL_RANGE_INTRO, Image = "xxxx.png", Step = false } }));
        return this;

    }

    public KitchenBuilder AddPantry () {
        Kitchen.Add (new Pantry ().SetData (new PantryStepData { guideData = { Owner = KitchenBase.PANTRY, Text = LABEL_PANTRY_INTRO, Image = "xxxx.png", Step = true } }));
        return this;

    }

    public KitchenDesign Build () {
        return Kitchen;
    }

    public static string LABEL_WALL_INTRO = "Please set the length of each wall.";
    public static string LABEL_KITCHEN_INTRO = "Is there any window in your kitchen?";
    public static string LABEL_BASIN_INTRO = "Installation Pipe";
    public static string LABEL_RANGE_INTRO = "Location of Stove power outlet";

    public static string LABEL_FRIDGE_INTRO = "Is there a fridge in your kitchen?";

    public static string LABEL_PANTRY_INTRO = "Do you need the pantry?";
}

調(diào)用如下:

KitchenDesign template =  new KitchenBuilder(KITCHEN_TYPES.I,KITCHEN_SUB_TYPES.I_1)
                    .AddGuidePage()
                    .AddWall("OA")
                    .AddWindow()
                    .AddBasin()
                    .AddKitchenRange()
                    .AddFridge()
                    .AddPantry()
                    .Build();

寫技術(shù)博客真的特別耗費時間,沒人看你也得寫,畢竟記錄自己掌握的知識,以備日后復(fù)習(xí)查閱才更重要,而且發(fā)現(xiàn)自己有些強迫癥,正在做的東西,總想去系統(tǒng)的了解它們,但通常系統(tǒng)的了解需要花費蠻多的精力,這樣會搞得你很辛苦,很勞累!但如果說把他們記錄下來,未來的某一天再去系統(tǒng)的學(xué)習(xí),經(jīng)驗打臉,要么不會再去研究了,要么就是隔了非常久的時間,所以現(xiàn)在想做就去做吧,但要做好優(yōu)先主次,輕重緩急,這不,重構(gòu)這一塊,非要現(xiàn)在搞,而且還要把博客寫出來,又不想明天來做,就延誤了今天的計劃,還有些功能沒有調(diào)試實現(xiàn)呢,怎么辦,熬夜補吧!

image.png
偶像鎮(zhèn)樓,給自己續(xù)命2小時,希望明天韓國BBIC 2018奪冠!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 場景問題# 1.1 商品類別樹## 考慮這樣一個實際的應(yīng)用:管理商品類別樹。 在實現(xiàn)跟商品有關(guān)的應(yīng)用系統(tǒng)的時候...
    七寸知架構(gòu)閱讀 6,274評論 10 59
  • 來項目部要十八班武藝接通,還要處事八面玲瓏。
    dahh閱讀 253評論 0 0
  • 我也像很多人一樣,喜歡冥想。悠閑時,忙碌時,悲傷時,恬靜時,把生活里種種好的壞的揉在心里,慢慢地醞釀,醞釀,發(fā)酵,...
    小人物阿雞閱讀 755評論 0 3
  • 上圖就是我家可愛又可恨的小傻狗。 首先奉勸想養(yǎng)狗的單身妹子,養(yǎng)狗如嫁人,務(wù)必要慎重。 操心系列 他剛來到家里的時候...
    晴千千閱讀 349評論 1 3

友情鏈接更多精彩內(nèi)容