生成器设计模式

生成器模式是一种创建型设计模式,使你能够分步骤创建一个复杂对象。该模式还允许你使用相同的创建代码生成不同形式和不同类型的对象,下面通过几个方面具体的说明。

解决的问题

现在有一个复杂对象,包括很多的成员变量和嵌套对象,如何初始化这个复杂的对象呢?

通常就是将这些初始化代码放入一个构造方法当中,当然也有可能会放在多个不同的构造方法中

但是,如果将所有的参数都放到一个构造方法中,那么就会有很多冗余的参数;而要是使用多个不同的构造方法的话,不同的参数组合会产生数量不可控的构造方法

不管是使用一个构造方法还是使用多个构造方法,都会在一定程度上增加构造这个对象复杂度,并且会导致这个对象很难扩展,下面使用一个具体的案例来解释一下

比如,现在要构建一个House对象,它包含的基本属性有:door,window,roof,wall,floor,light 等,并且它也可以包含一些其他的设施,如:heating,electricity,gas,yard,pool,soft 等等,那如何构造这个对象呢?如何为不同的人构造不同的House呢?

最简单的就是在这个House对象的基础上进行扩展,添加poolsoftheating等等这些对象;最简单的方法就是添加一个超级构造方法将所有的这些属性和嵌套对象都包含进来,但是,这样的话即便构造一个简单House也需要传入很多的构造参数,尽管大部分构造参数根本用不到

当然,还有一种方法就是添加多个构造方法,不同的构造方法构造不同形式的House,但这随着House的属性不断增加,就会导致构造方法的数量不可控了,并且使得代码的扩展性极差

解决方案

生成器模式建议将对象构造的代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象当中;将对象的构造划分为一组步骤,如:buildWall()创建墙面,buildDoor创建门等等;

每次创建对象的时候,你可以通过生成器执行一系列步骤;重点就是,你无需调用所有的步骤,只需要调用创建特定对象的那些步骤即可

当然,对于同一类产品可能会有不同的形式,比如:木制的Door和铁制的door;在这种情况下,就可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤,然后在客户端使用这些生成器创建不同形式的对象;比如:WoodHouseBuilder创建木制的房子,IronHouseBuilder则会给你返回一个铁制的房子

主管

虽然现在解决了超级构造函数的问题,但是客户端还是得知道构建一个House对象的具体步骤;那有没有一种方法直接生成一个你想要的房子呢?

答案是有的,就是通过主管类,比如:你现在想要一个木制的房子,那么主管类会将一系列创建木制房子的步骤封装起来,当你使用主管类调用时,主管类就会给你返回给你一个木制的房子,这样就不需要再去了解构建一个木制房子的具体细节了

这样,主管类屏蔽了构建一个木制类房子的细节,真正的做到了客户端和具体的构建对象之间的解耦,更加的利于程序的扩展了

生成器模式结构图和代码
generator.png

代码示例:

package main

import "fmt"

type House struct {
    Wall    string
    Door    string
    Window  string
    Heating string
    Yard    *Yard
}

type Yard struct {
    Size int
    Name string
}

func newYard(size int, name string) *Yard {
    return &Yard{
        Size: size,
        Name: name,
    }
}

func (h *House) String() string {
    s := fmt.Sprintf("Door:%s,Wall:%s,Window:%s,Heating:%s",h.Door,h.Wall,h.Window,h.Heating)
    if h.Yard != nil{
        s = fmt.Sprintf("%s,yard size:%d,yard name:%s",s,h.Yard.Size,h.Yard.Name)
    }
    return s
}

HouseBuilder

package main

type HouseBuilder interface {
    BuildWall()
    BuildYard()
    BuildDoor()
    BuildWindow()
    BuildHeating()
    // 如果是同一类型不同形式的对象,则在此处声明获取产品的方法即可
    // 如果是不同类型的对象,则需要在具体的 builder 中添加获取该类型产品的方法
    GetHouse() *House
}

// woodBuilder use build wood house
type woodBuilder struct {
    wall    string
    door    string
    window  string
    heating string
    yard    *Yard
}

func newWoodBuilder() HouseBuilder {
    return &woodBuilder{}
}

func (w *woodBuilder) BuildWall() {
    w.wall = "white wall"
}

func (w *woodBuilder) BuildDoor() {
    w.door = "red wood"
}

func (w *woodBuilder) BuildYard() {
    w.yard = newYard(10,"wood yard")
}

func (w *woodBuilder) BuildWindow() {
    w.window = "wood window"
}

func (w *woodBuilder) BuildHeating() {
    w.heating = "simonzi"
}

func (w *woodBuilder) GetHouse() *House {
    return &House{
        Wall:    w.wall,
        Door:    w.door,
        Window:  w.window,
        Heating: w.heating,
        Yard:    w.yard,
    }
}

Director

package main

type director struct {
    builder HouseBuilder
}

func newDirector(builder HouseBuilder) *director {
    return &director{builder: builder}
}

func (d *director) SetBuilder(builder HouseBuilder) {
    d.builder = builder
}

func (d *director) buildWoodHouse() *House {
    d.builder.BuildDoor()
    d.builder.BuildHeating()
    d.builder.BuildWall()
    d.builder.BuildWindow()
    d.builder.BuildYard()
    return d.builder.GetHouse()
}

客户端代码

package main

import "fmt"

func main() {
    houseBuilder := newWoodBuilder()
    director := newDirector(houseBuilder)

    house := director.buildWoodHouse()

    fmt.Printf("%s",house)
}

上面创建House对象是通过指定的HouseBuilder创建的,House的参数是默认的;如果你想要动态参数,那么就将这些参数传入到WoodBuild就可以了,因为在这已经确定了这些参数是创建这个所必须的,所以不会出现参数的冗余问题;

对于扩展性而言,对于新增的字段则只需要再新加一个新的生成器就可以了,这样就不需要更改之前的代码就完成了程序的扩展了

转载: https://xinwazi.com.cn/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E7%94%9F%E6%88%90%E5%99%A8%E6%A8%A1%E5%BC%8F-70c66ed098cd/

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

推荐阅读更多精彩内容