分享&探讨组合模式和建造者模式

最近帮朋友做了一款应用的功能模块,本来是属于原生APP的范畴,但它比较特殊,这是一款面向“厨房设计”的APP,主要面向的国家是加拿大,所以和国内的厨房有不小的差距,但给家里装修过的同学应该都有所了解,厨房设计是非常重要的一块儿,那么这款应用,用户在选择具体的厨房以后,可以通过APP对厨房进行设计,即通过APP来组装,调整你的厨房配置,比如:

1.有几面墙,每面墙的宽度是多少
2.在墙上是否有窗户,窗户在哪面墙上,窗户的宽度是多少,离墙角的距离
3.厨房里是否有岛,英文单词叫Island,大家应该都见到过,只是可能不太知道叫什么,如图:
image.png
中间这就是岛,就像我们的餐桌一样,但他通常是可以放水盆(Basin)的,这在国外特别常见,这个岛是在厨房的中间的,还有一种叫半岛,即有一边是靠着某一堵墙的,但本质上都一样,和厨房的空间设计有一定的关系。

这里再给出关于岛的顶视图(纯科普下,和技术没关系哈哈):

image.png
image.png

继续说:

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();

写技术博客真的特别耗费时间,没人看你也得写,毕竟记录自己掌握的知识,以备日后复习查阅才更重要,而且发现自己有些强迫症,正在做的东西,总想去系统的了解它们,但通常系统的了解需要花费蛮多的精力,这样会搞得你很辛苦,很劳累!但如果说把他们记录下来,未来的某一天再去系统的学习,经验打脸,要么不会再去研究了,要么就是隔了非常久的时间,所以现在想做就去做吧,但要做好优先主次,轻重缓急,这不,重构这一块,非要现在搞,而且还要把博客写出来,又不想明天来做,就延误了今天的计划,还有些功能没有调试实现呢,怎么办,熬夜补吧!

image.png
偶像镇楼,给自己续命2小时,希望明天韩国BBIC 2018夺冠!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 商品类别树## 考虑这样一个实际的应用:管理商品类别树。 在实现跟商品有关的应用系统的时候...
    七寸知架构阅读 5,974评论 10 59
  • 来项目部要十八班武艺接通,还要处事八面玲珑。
    dahh阅读 230评论 0 0
  • 我也像很多人一样,喜欢冥想。悠闲时,忙碌时,悲伤时,恬静时,把生活里种种好的坏的揉在心里,慢慢地酝酿,酝酿,发酵,...
    小人物阿鸡阅读 614评论 0 3
  • 上图就是我家可爱又可恨的小傻狗。 首先奉劝想养狗的单身妹子,养狗如嫁人,务必要慎重。 操心系列 他刚来到家里的时候...
    晴千千阅读 313评论 1 3