最近幫朋友做了一款應(yīng)用的功能模塊,本來是屬于原生APP的范疇,但它比較特殊,這是一款面向“廚房設(shè)計”的APP,主要面向的國家是加拿大,所以和國內(nèi)的廚房有不小的差距,但給家里裝修過的同學(xué)應(yīng)該都有所了解,廚房設(shè)計是非常重要的一塊兒,那么這款應(yīng)用,用戶在選擇具體的廚房以后,可以通過APP對廚房進行設(shè)計,即通過APP來組裝,調(diào)整你的廚房配置,比如:
1.有幾面墻,每面墻的寬度是多少
2.在墻上是否有窗戶,窗戶在哪面墻上,窗戶的寬度是多少,離墻角的距離
3.廚房里是否有島,英文單詞叫Island,大家應(yīng)該都見到過,只是可能不太知道叫什么,如圖:

中間這就是島,就像我們的餐桌一樣,但他通常是可以放水盆(Basin)的,這在國外特別常見,這個島是在廚房的中間的,還有一種叫半島,即有一邊是靠著某一堵墻的,但本質(zhì)上都一樣,和廚房的空間設(shè)計有一定的關(guān)系。
這里再給出關(guān)于島的頂視圖(純科普下,和技術(shù)沒關(guān)系哈哈):


繼續(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)呢,怎么辦,熬夜補吧!
