设计模式——0_2 生成器(Builder)

2024-01-01 23:18:02

手工时代的中国工匠相信愿力无边,不管是做佛像,还是打家具。即使只是打造一个金丝楠木柜子,可能都不是一个工匠一生就能做完的。往往是爷爷做出粗坯,父亲做完粗工,孙子再精雕细琢,穷尽三代才打造出一件精湛的柜子。陆续建造了一千六百年的莫高窟,那是多少代无名工匠,用尽了自己的体温去焐热了菩萨的慈悲。

——于丹《人间有味是清欢》

定义

将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示

图纸

UML

不止有 Builder 和 ConcreteBuilder 是生成器模式的构成,Director(导演) 也是生成器模式不可或缺的一部分,因为这部分是生成器用于保证自己交付给 client 的产品是一个完整对象的关键

而且 client 会同时和 Director 和 Builder 两个部分打交道


一个例子:产出一部玩具车

这次的例子会有点长,先做个深呼吸

准备好了?那我们开始了:

假设我们要用代码来表示 一家玩具厂的玩具车产线,这种玩具车具有如下要求:

玩具车

这种玩具车还分三种型号,就像这样:

型号说明

ToyCar

毋庸置疑,我们需要创建出一个 玩具车 的实体类ToyCar,就像这样:

ToyCar

方案一,直接写静态工厂

你当然可以在 ToyCar 里直接新增三种不同的构造方法来实现这个效果,就像这样:

public class ToyCar{
    
	……
    
    public static ToyCar createModelA(){
        ToyCar car = new ToyCar();
        
        car.底盘 = new 底盘();
        car.车轮 = new α车轮();
        car.遥控器 = new 甲型号遥控器();
        car.车顶灯 = null;
        car.喇叭 = new 音乐喇叭();
        
        return car;
    }
    
    public static ToyCar createModelB(){
        ToyCar car = new ToyCar();
        
        car.底盘 = new 底盘();
        car.车轮 = new β车轮();
        car.遥控器 = new 乙型号遥控器();
        car.车顶灯 = new 彩色车灯();
        car.喇叭 = null;
        
        return car;
    }
    
    public static ToyCar createModelC(){
        ToyCar car = new ToyCar();
        
        car.底盘 = new 底盘();
        car.车轮 = new β车轮();
        car.遥控器 = new 甲型号遥控器();
        car.车顶灯 = new 彩色车灯();
        car.喇叭 = new 音乐喇叭();
        
        return car;
    }
    
    ……
}

如果这家玩具厂 永远都不开新品产线并且每种类型的玩具车的构成都不发生改变,那么这个方案非常完美。虽然他在静态工厂里面暴露了玩具车的具体构成(这让 ToyCar各个组件 之间形成了紧耦合),但是如果不变动的话,问题并不大

可是设计模式就是考虑变化的情况,如果没有变化,那全都用硬编码不就好了?


很显然,如果我们加入了新的型号,我们需要新增新的静态工厂方法。最重要的是,这些工厂方法所产出的对象之间并没有什么本质不同,他们只是状态迥异的 “同个对象” 而已

这和工厂方法的理念是背道而驰的,我们采用工厂方法的时候,通常是每个工厂方法所产出的产品是同个产品根类的不同方向拓展

另两种实现方式:

  1. 创建一个工厂类用于存放上面的工厂方法
  2. 定义一个接收所有组件作为参数的构造方法

这两种方法的本质和静态工厂方法实现是一样的,只不过这两种方法解除了 ToyCar 和具体组件类之间的紧耦合

而前者把这部分硬编码放到了工厂类中,后者把硬编码放到了调用构造方法的上下文中


方案二:对组件用抽象工厂怎么样?

这种方案的意思是说,这样写:

玩具车和抽象工厂

public interface ToyCarComponentFactory{  
    
    //创建一个底盘
	Chassis createChassis();
    
    //创建一个轮胎
    Tyre createTyre();
    
    //创建一个遥控器
    RemoteControl createRemoteControl();
    
    //创建一个车顶灯
    RoofLigh createRoofLight();
    
    //创建一个喇叭
    Trumpet createTrumpet();
}

//型号A的组件工厂
public class ModelAComponentFactory implements ToyCarComponentFactory{
    
    //创建一个底盘
	public Chassis createChassis(){
        return 底盘对象;
    }
    
    //创建一个轮胎
    public Tyre createTyre(){
        return α轮胎对象;
    }
    
    //创建一个遥控器
    public RemoteControl createRemoteControl(){
        return 甲型号遥控器;
    }
    
    //创建一个车顶灯
    public RoofLigh createRoofLight(){
        return null;
    }
    
    //创建一个喇叭
    public Trumpet createTrumpet(){
        return 音乐喇叭对象;
    }
}

//型号B的组件工厂
public class ModelBComponentFactory implements ToyCarComponentFactory{
    
    //创建一个底盘
	public Chassis createChassis(){
        return 底盘对象;
    }
    
    //创建一个轮胎
    public Tyre createTyre(){
        return β轮胎对象;
    }
    
    //创建一个遥控器
    public RemoteControl createRemoteControl(){
        return 乙型号遥控器对象;
    }
    
    //创建一个车顶灯
    public RoofLigh createRoofLight(){
        return 彩色车灯对象;
    }
    
    //创建一个喇叭
    public Trumpet createTrumpet(){
        return null;
    }
}

//型号C的组件工厂
public class ModelCComponentFactory implements ToyCarComponentFactory{
    
    //创建一个底盘
	public Chassis createChassis(){
        return 底盘对象;
    }
    
    //创建一个轮胎
    public Tyre createTyre(){
        return β轮胎对象;
    }
    
    //创建一个遥控器
    public RemoteControl createRemoteControl(){
        return 甲型号遥控器对象;
    }
    
    //创建一个车顶灯
    public RoofLigh createRoofLight(){
        return 彩色车灯对象;
    }
    
    //创建一个喇叭
    public Trumpet createTrumpet(){
        return 音乐喇叭对象;
    }
}

这种写法的问题同样很大,抽象工厂确实帮我们解决了 解耦和重复修改 的问题,但是他将 ToyCar 对象的 组装权 交给了 client,谁知道 client 会组装出一个什么样的玩具车?也许会是一个没有底盘光有喇叭的小号;又或者是只有底盘没有轮胎的手电筒。

ToyCar 是一个由多个组件构成的比较复杂的对象,你必须要保证你交付给 client 的时候不会是一个构造到一半的对象。所以,抽象工厂也被pass了


方案三:生成器

现在我们推翻前面的设计,把 Line(生产线) 也用一个类来表示,然后在 生产线 中定义一个 Buidler(生成器) ,让这个Builder来生成 ToyCar,就像这样:

玩具车和生成器

//玩具车生产线
public class ToyCarLine{
   
    private ToyCarBuilder builder;//玩具车生成器
    
    public ToyCarLine(ToyCarBuilder builder){
        this.builder = builder;
    }
    
    public void changeToyCarBuilder(ToyCarBuilder builder){
        this.builder = builder;
    }
    
    public ToyCar create(){
        builder.reset();
        builder.buildChassis();
        builder.buildTyre();
        builder.buildRemoteControl();
        builder.buildRoofLight();
        builder.buildTrumpet();
        return builder.getResult();
    }
}

//玩具车生成器
public abstract class ToyCarBuilder{
    
    protected ToyCar result;
    
    public void reset(){
        result = new ToyCar();
    }
    
    public abstract void buildChassis();
    public abstract void buildTyre();
    public abstract void buildRemoteControl();
    public abstract void buildRoofLight();
    public abstract void buildTrumpet();
    
    public ToyCar getResult(){
        return result;
    }
}

public class ModelABuilder extends ToyCarBuilder{
    
    public void buildChassis(){
        result.底盘 = new 底盘();
    }
    
    public void buildTyre(){
        result.轮胎 = new α轮胎();
    }
    
    public void buildRemoteControl(){
        result.遥控器 = new 甲类型遥控器();
    }
    
    public void buildRoofLight(){
        result.车顶灯 = null;
    }
    
    public void buildTrumpet(){
        result.喇叭 = new 音乐喇叭();
    }
}

public class ModelBBuilder extends ToyCarBuilder{
    
    public void buildChassis(){
        result.底盘 = 底盘;
    }
    
    public void buildTyre(){
        result.轮胎 = new β轮胎();
    }
    
    public void buildRemoteControl(){
        result.遥控器 = new 乙类型遥控器();
    }
    
    public void buildRoofLight(){
        result.车顶灯 = new 彩色车灯();
    }
    
    public void buildTrumpet(){
        result.喇叭 = null;
    }
}

public class ModelCBuilder extends ToyCarBuilder{
    
    public void buildChassis(){
        result.底盘 = 底盘;
    }
    
    public void buildTyre(){
        result.轮胎 = new β轮胎();
    }
    
    public void buildRemoteControl(){
        result.遥控器 = new 甲类型遥控器();
    }
    
    public void buildRoofLight(){
        result.车顶灯 = new 彩色车灯();
    }
    
    public void buildTrumpet(){
        result.喇叭 = new 音乐喇叭;
    }
}

我们通过 ToyCarLine 实现了对 ToyCar 产出时的控制,每个构筑步骤一定会被调用,无论他有没有对成品做操作

当玩具车要开新的产线的时候,我只需要新增新的 ToyCarBuilder 子类就可以了,ToyCarLine 和 client 中不需要任何修改

如果玩具车的 “生产线” 生产到一半想要切换成品的类型,则直接切换 ToyCarLinebuilder 引用就可以实现


也就是说,在生成器模式下,我们是这样生产一部 ToyCar 的:

流水线式的产出对象


对照生成器的UML的话,则有:

  • ToyCarLine <-> Director
  • ToyCarBuilder <-> Builder
  • ModelABuilder… <-> ConcreteBuilder
  • ToyCar <-> Product

这是一个标准的生成器实现

看起来和抽象工厂很像是吧,但是他们有两个本质区别

  1. 抽象工厂无法控制 ToyCar 构筑的流程,而生成者由于有 Director 的存在,可以保证从他这里产出的对象都是到手可用的,没有状态问题的

  2. 抽象工厂所产出的内容,通常都是在一个大的框架下的不同组件;

而生成器的关注点是给一个成品内添加不同的组件

写在最后的碎碎念

生成器和生产线

从我第一次接触 生成器模式 开始,就觉得他特别像工厂里的流水线

流水线上每个人负责不同的工位(Builder里的每个方法都负责不同的部分),但都对相同的一个部品进行加工(都对result进行加工)。

这就和 DirectorProduct 做的事是一模一样,Builder 会定义加工产品的这条流水线一共有多少个工位,至于每个工位具体做什么事情,由实现 Builder 的具体生成器决定,但是 Builder 要保证所有工位加工完之后就可以拿到一个成品


生成器和空方法

玩具车 的例子中,有两个方法是给产品里的属性赋空值,这是为了配合前面使用工厂模式的例子所以才这样写的(因为工厂方法必须要有返回值,那怕是null)

事实上由于生成器是一种加工的模式,所以他是允许某个工位对产品不做任何操作的

所以 ModelABuilder中的buildRoofLightModelBBuilder中的buildTrumpet 是可以什么都不写的,这样也不会影响 Director 对生成器的调用


生成器和策略模式

Director 中通过对 Builder 的切换,最终实现了相同的行为在不同状态下有不同的产品产出。这时候如果你将 Builder 类簇看成是一个策略类簇的话就会发现,其实 Director 是一个策略模式实现

这并不矛盾,没有人规定一种设计模式里面只能用当前这种设计模式里面的内容,抽象工厂里还有工厂方法,工厂方法的例子里我还用到享元和单例呢

设计模式就像是武侠小说里的武功秘籍一样,他记载的只是招式,招式和招式之间也会存在相生相克和相互关联的时候

所以我们要像张无忌那样,先记住这些招式,再把这些招式全忘掉

当你发现你在忘掉所有招式的情况下,还能把他们融入自己的设计中,那应该就是大成了

就像上文的例子那样,并不是因为老师说用这个更好,所以我用这个。而是因为我们发现这样写好像更合理,更易于拓展,自然而然的写出了这样的代码。而恰巧这种手法和前人已经总结过的生成器模式一致,仅此而已

请不要总把他当作解决某种问题的方法,让他成为你思考问题的方式

文章来源:https://blog.csdn.net/weixin_39241202/article/details/135282153
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。