最近帮朋友做了一款应用的功能模块,本来是属于原生APP的范畴,但它比较特殊,这是一款面向“厨房设计”的APP,主要面向的国家是加拿大,所以和国内的厨房有不小的差距,但给家里装修过的同学应该都有所了解,厨房设计是非常重要的一块儿,那么这款应用,用户在选择具体的厨房以后,可以通过APP对厨房进行设计,即通过APP来组装,调整你的厨房配置,比如:
1.有几面墙,每面墙的宽度是多少
2.在墙上是否有窗户,窗户在哪面墙上,窗户的宽度是多少,离墙角的距离
3.厨房里是否有岛,英文单词叫Island,大家应该都见到过,只是可能不太知道叫什么,如图:
中间这就是岛,就像我们的餐桌一样,但他通常是可以放水盆(Basin)的,这在国外特别常见,这个岛是在厨房的中间的,还有一种叫半岛,即有一边是靠着某一堵墙的,但本质上都一样,和厨房的空间设计有一定的关系。
这里再给出关于岛的顶视图(纯科普下,和技术没关系哈哈):
继续说:
4.水盆的位置放在哪里,是窗口下,某一面墙,还是放在岛上,水盆是厨房必备的
5.炉头在哪里,这个炉头就是厨房的炉灶,知道炉头的位置,也就能定位炉灶了,他可以放在墙上,也可以放在岛上,但上面的水盆和炉头不能都放在岛上,这里是细节的部分
6.是否有冰箱,冰箱在哪面墙边,冰箱的尺寸是多少
7.当冰箱大于一定的尺寸后,是否要安装储物柜(pantry),如果需要,储物柜的尺寸是多少
细节上就是这么多,用户在做出选择以后,会进入到一个3D的预览视图中,通过3D来演示用户自己“设计”的立体效果图,并可以进行360度的切换,直观方便,一目了解(据说那边人工设计图纸非常的昂贵,而且通勤又不方便,所以通过APP的形式就可以节省人力和财力了),那么这部分就需要使用3D渲染引擎来做,选择了U3D,在进入3D预览图之间的一些UI流程,也是通过UGUI来实现。
那么通过上面的需求,如何去设计他的结构呢?
厨房里可以包含窗户,水盆,炉灶,冰箱,岛等等
我最先想到的是组合模式Composite Parttern),即“体现部分和整体”的树形“层次”结构,犹如公司的组织架构,但组合模式的核心是让部分和整体具有一致性,即对用户来说,单一的对象和组合的对象是一样的,从代码层面来说,他们均继承自于同一个抽象类,实现Add,Remove这些抽象方法,并且相互之间,可以任意的组合,通常有一个根结点Root,然后下面是组合组件,组合组件可以添加其它多个组件,还有称为叶子结点(Leaf),即他是最下层的,他不可以再添加其它的组件,只能被添加,但叶子结点也保留了Add,Remove这些抽象接口的使用,对外部保持一致性。
所以开始设计了KitchenBase,抽象基类,包括了上面的抽象方法Add,Remove等等,然后定义Wall,Window,Basin,Kitchen Range,Fridge,Pantry这些厨房里会包括的组件类,均继承自KitchenBase,但在实际开发中慢慢发现使用“组合模式”并不合理,
原因如下:
1.当前需求并没有多层次的情况,厨房里包含的这些组件均是平行的关系(不考虑具体每个组件内部的细节,比如冰箱里的细节)
2.强调部分与整体,并且互相之间,可以任意的组合,任次的层次,而上面这些平行关系的组件,谁组合谁并不合理,冰箱下
包含水盆,炉灶下包含窗口,岛包含窗户和墙等等,显然是不合理的,所以这本质上还是一个包含has-a的关系,Kitchen厨房里面,可以添加的N个部件,这些部件之间可以派生自一个父类,但他们之间组合显然不具备明确的合理性。
那么,Kitchen类,他有自己的一些属性,比如加拿大的厨房被总结城了4大分类,I型(单面墙),II型(双面墙),L型,和U型,这4种类型,每一种主类型还有多个子类型,即有多个不同的变化,比如说I型就分I型和I型+岛的,L型也分L型和L型+岛,还有L型+半岛,U型就更多,多达10几种,而冰箱,水盆,墙体,窗户这些和Kitchen本身并不具备继承关系,只是一种包含关系。
那么我便将原有组合模式的设计,重构成了现在这种简单”包含的”关系“。
最佳的组件模式案例
目前我个人认为,组件模式最佳的案例是UI组件,比如Text,Image,Label,ScrollView,他们平行,可以独立的存在,也可以互相的组件成一个复杂的组件,Text可以包含Image,Image也可以包含Text,Label也可以包含Image和Text等等,任意的组件,部分和整体具有一致性,即我们可以通过相同的“行为”去操作他们。
现在,厨房设计的逻辑关系已经清晰了,去除了不合适的组件模式,所以在下面的需求中,让我想到了另外一种设计模式的应用:
当用户去选择一个类型的厨房以后,我们需要引导用户,引导他们进行一步一步的操作设置,比如1,2,3,4,5步,每一步的设置由系统设定好,用户根据提示填选各种参即可,那么,不同厨房的类型肯定是会拥有不同的组件,比如有的带岛,有的没岛,窗口是可以选择的,冰箱也是可以选择的,你不选择冰箱也就没有储物柜pantry什么事儿,这种结构也是包含的关系,而且是“动态的”,用户选择不要窗户,就可以直接从Kitchen类中删除了,流程就是,我们只需要去添加指定的组件,然后遍这些组件,显示对应的UI,并进行相应的逻辑控制,更新数据等等。
这其实是一个“构建的”过程,根据不同的类型来构建这个复杂的对象,这让我想到了
(“建造者模式”Builder Pattern),通过建造者模来一步一步的构建复杂的对象,特点是灵活,
尤其是针对那些参数比较多的,组件比较多,根据需求一步一步的构建,最典型的一个案例就是
Android中AlertDialog的设计:
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
}
});
使用起来很灵活,对话框通常是参数式的,但组件形式也是同理,那么,自然的,我创建一个KitchenBuilder来构建不同类型厨房所需要的不同的组件。
下面是其中的代码片断,大致的效果(部分代码还未重构):
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?";
}
调用如下:
KitchenDesign template = new KitchenBuilder(KITCHEN_TYPES.I,KITCHEN_SUB_TYPES.I_1)
.AddGuidePage()
.AddWall("OA")
.AddWindow()
.AddBasin()
.AddKitchenRange()
.AddFridge()
.AddPantry()
.Build();
写技术博客真的特别耗费时间,没人看你也得写,毕竟记录自己掌握的知识,以备日后复习查阅才更重要,而且发现自己有些强迫症,正在做的东西,总想去系统的了解它们,但通常系统的了解需要花费蛮多的精力,这样会搞得你很辛苦,很劳累!但如果说把他们记录下来,未来的某一天再去系统的学习,经验打脸,要么不会再去研究了,要么就是隔了非常久的时间,所以现在想做就去做吧,但要做好优先主次,轻重缓急,这不,重构这一块,非要现在搞,而且还要把博客写出来,又不想明天来做,就延误了今天的计划,还有些功能没有调试实现呢,怎么办,熬夜补吧!