前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TS 设计模式02 - 建造者模式

TS 设计模式02 - 建造者模式

作者头像
love丁酥酥
修改2020-09-26 14:01:46
6020
修改2020-09-26 14:01:46
举报
文章被收录于专栏:coding for lovecoding for love

1. 简介

工厂模式,为我们将客户端的生产行为封装起来,交给了工厂。它本质上是服务于客户端的,并没有降低产品生产的难度,产品的生产逻辑仍然在自己的类内部实现。 对于一些复杂的产品类(工序多,参数多),我们需要在内部维护其复杂的构建逻辑,是很容易出错的。 举一个简单的例子,生产牛肉汉堡,我们不管是由客户端去生产,还是工厂帮我们生产,建造的逻辑始终写在其 constructor 内部。全部生产步骤可能包含,做面包,做牛肉,放蔬菜,每个步骤可能有不同的参数控制,比如几片面包,几片牛肉或者几片蔬菜。如果我们发现之前的工序不好,需要调整工序,要么在类内部进行修改(违法开闭),要么新增一个类(成本太大,也不好维护),或者说我们要做猪肉汉堡,步骤和工序是一样的,我们新建一个类时由于步骤复杂,可能漏了或写错了。 那怎么办呢,建造者模式就是帮助我们创建一个复杂对象的,它将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。比如我规定汉堡的构建工序是可以稳定地划分为,做面包,做肉,放蔬菜的,至于你具体是面包是做成方形的圆形的,肉要是什么类型,蔬菜是什么类型,这是具体的实现步骤,对不同的汉堡具体的实现不一样。

2. 建造者模式

image.png

这里抽象建造者使用接口也是 okay 的。可以看到这里不管抽象的建造者还是具体的建造者依赖(关联)的都是 Hamburg,这个 Hamburg 其实也可以是一个父类或者抽象类。

代码语言:javascript
复制
class Hamburg {
    name: string;
    // 可选参数也可以使用 set,但是 ts 中直接在这里声明更方便, 当然如果是 private,需要使用 set 来封装
    meatType?: string;
    vegetableType?: string;
    private breadNum?: number;

    constructor(name: string) { // 如果我们不用建造者模式,那么产品类的 constructor 这里将要传入所有参数
        this.name = name; // 必选参数可以放在这里,步骤具体实现可变的就抽出来
    }

    // 利用 ts 的 set 当然也是 okay 的,比如 set num(num){ this.breadNum = num; }
    setBreadNum(num: number) {
        this.breadNum = num;
    }
}

// 原本放在产品类的构建步骤被转移到了建造者类,由具体的建造者实现
abstract class HamburgBuilder {
    abstract buildBread(breadNum: number): void;
    abstract buildMeat(meatType: string): void;
    abstract buildVegetable(vegetableTYpe: string): void;
    abstract createHamburg(): Hamburg;
}

class BeefHamburgBuilder extends HamburgBuilder {
    // 这里如果可以确定 name,就不需要用户再传入了
    private hamburg: Hamburg = new Hamburg('牛肉汉堡');

    buildBread(breadNum: number): void {
        console.log(`制作牛肉汉堡需要的 ${breadNum} 片面包`);
        this.hamburg.setBreadNum(breadNum);
    }

    buildMeat(meatType: string): void {
        console.log(`制作牛肉汉堡需要的 ${meatType}`);
        this.hamburg.meatType = meatType;
    }

    buildVegetable(vegetableType: string): void {
        console.log(`制作牛肉汉堡需要的 ${vegetableType}`);
        this.hamburg.vegetableType = vegetableType;
    }

    createHamburg(): Hamburg {
        return this.hamburg;
    }
}

class PorkHamburgBuilder extends HamburgBuilder {
    private hamburg: Hamburg = new Hamburg('猪肉汉堡');

    buildBread(breadNum: number): void {
        console.log(`制作猪肉汉堡需要的 ${breadNum} 片面包`);
        this.hamburg.setBreadNum(breadNum);
    }

    buildMeat(meatType: string): void {
        console.log(`制作猪肉汉堡需要的 ${meatType}`);
        this.hamburg.meatType = meatType;
    }

    buildVegetable(vegetableType: string): void {
        console.log(`制作猪肉汉堡需要的 ${vegetableType}`);
        this.hamburg.vegetableType = vegetableType;
    }

    createHamburg(): Hamburg {
        return this.hamburg;
    }
}

// 我们用 director 来封装顺序,如果要改变工序,只要新增一个 director 或者新增一个 construct 即可
class HamburgDirector {
    // 顺序1,包含三道工序
    static construct1(builder: HamburgBuilder, breadNum: number, meatType: string, vegetableType: string): Hamburg {
        builder.buildBread(breadNum);
        builder.buildMeat(meatType);
        builder.buildVegetable(vegetableType);
        return builder.createHamburg();
    }

    // 顺序2,包含两道工序
    static construct2(builder: HamburgBuilder, breadNum: number, meatType: string): Hamburg {
        builder.buildMeat(meatType);
        builder.buildBread(breadNum);
        return builder.createHamburg();
    }
}

const beefHamburgBuilder = new BeefHamburgBuilder();
const porkHamburgBuilder = new PorkHamburgBuilder();

HamburgDirector.construct1(beefHamburgBuilder, 2, 'beef', 'carrot');
HamburgDirector.construct2(porkHamburgBuilder, 3, 'pork');

image.png

3. 改进构造调用

目前导演类在调用建造者进行建造时,建造步骤如果一多会显得不清晰。我们可以使用链式调用方法来进行优化。

代码语言:javascript
复制
class Hamburg {
    name: string;
    // 可选参数也可以使用 set,但是 ts 中直接在这里声明更方便, 当然如果是 private,需要使用 set 来封装
    meatType?: string;
    vegetableType?: string;
    private breadNum?: number;

    constructor(name: string) { // 如果我们不用建造者模式,那么产品类的 constructor 这里将要传入所有参数
        this.name = name; // 必选参数可以放在这里,步骤具体实现可变的就抽出来
    }

    // 利用 ts 的 set 当然也是 okay 的,比如 set num(num){ this.breadNum = num; }
    setBreadNum(num: number) {
        this.breadNum = num;
    }
}

// 原本放在产品类的构建步骤被转移到了建造者类,由具体的建造者实现
abstract class HamburgBuilder {
    abstract buildBread(breadNum: number): HamburgBuilder;
    abstract buildMeat(meatType: string): HamburgBuilder;
    abstract buildVegetable(vegetableTYpe: string): HamburgBuilder;
    abstract createHamburg(): Hamburg;
}

class BeefHamburgBuilder extends HamburgBuilder {
    // 这里如果可以确定 name,就不需要用户再传入了
    private hamburg: Hamburg = new Hamburg('牛肉汉堡');

    buildBread(breadNum: number): HamburgBuilder {
        console.log(`制作牛肉汉堡需要的 ${breadNum} 片面包`);
        this.hamburg.setBreadNum(breadNum);
        return this;
    }

    buildMeat(meatType: string): HamburgBuilder {
        console.log(`制作牛肉汉堡需要的 ${meatType}`);
        this.hamburg.meatType = meatType;
        return this;
    }

    buildVegetable(vegetableType: string): HamburgBuilder {
        console.log(`制作牛肉汉堡需要的 ${vegetableType}`);
        this.hamburg.vegetableType = vegetableType;
        return this;
    }

    createHamburg(): Hamburg {
        return this.hamburg;
    }
}

class PorkHamburgBuilder extends HamburgBuilder {
    private hamburg: Hamburg = new Hamburg('猪肉汉堡');

    buildBread(breadNum: number): HamburgBuilder {
        console.log(`制作猪肉汉堡需要的 ${breadNum} 片面包`);
        this.hamburg.setBreadNum(breadNum);
        return this;
    }

    buildMeat(meatType: string): HamburgBuilder {
        console.log(`制作猪肉汉堡需要的 ${meatType}`);
        this.hamburg.meatType = meatType;
        return this;
    }

    buildVegetable(vegetableType: string): HamburgBuilder {
        console.log(`制作猪肉汉堡需要的 ${vegetableType}`);
        this.hamburg.vegetableType = vegetableType;
        return this;
    }

    createHamburg(): Hamburg {
        return this.hamburg;
    }
}

// 我们用 director 来封装顺序,如果要改变工序,只要新增一个 director 或者新增一个 construct 即可
class HamburgDirector {
    // 顺序1,包含三道工序
    static construct1(builder: HamburgBuilder, breadNum: number, meatType: string, vegetableType: string): Hamburg {
        return builder.buildBread(breadNum)
            .buildMeat(meatType)
            .buildVegetable(vegetableType)
            .createHamburg();
    }

    // 顺序2,包含两道工序
    static construct2(builder: HamburgBuilder, breadNum: number, meatType: string): Hamburg {
        return builder.buildMeat(meatType)
            .buildBread(breadNum)
            .createHamburg();
    }
}

const beefHamburgBuilder = new BeefHamburgBuilder();
const porkHamburgBuilder = new PorkHamburgBuilder();

HamburgDirector.construct1(beefHamburgBuilder, 2, 'beef', 'carrot');
HamburgDirector.construct2(porkHamburgBuilder, 3, 'pork');

4. 小结

建造者模式的核心其实就是将具体的建造过程提取出来,进行封装。构建步骤封装在建造者,构建顺序封装在导演类。你可以一个导演类对应于一个建造者,也可以对应对个建造者,甚至你如果不用导演类,由客户端来选择构建顺序也是 okay 的。

参考

一篇文章就彻底弄懂建造者模式(Builder Pattern)

建造者模式 | 菜鸟教程

[book - 大话设计模式]

[book - 设计模式之禅]

建造者模式的简单例子

秒懂设计模式之建造者模式(Builder pattern)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 简介
  • 2. 建造者模式
  • 3. 改进构造调用
  • 4. 小结
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档