1-5设计模式之建造者模式(Builder)

1.5.1 模式意图:

当面临一个复杂对象的创建时,通常由各个子对象按照一定的顺序组合而成;随着需求的不断变化,复杂对象对应的各个子对象也随之变化,但组合顺序相对稳定。我们需要将一个复杂的构建与其表示相分离,使得同样的构建过程或顺序可以创建不同的复杂对象。使用建造者模式即可达到这样的目的。

1.5.2 模式概念:

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

1.5.3 模式元素:

  • 抽象建造者(AbstractBuilder)
  • 具体建造者(HouseBuilder)
  • 指挥者(Director)
  • 产品角色(House、Cement、Brick等)

1.5.4 代码示例:

写一个建造房子的例子:首先你要准备水泥,钢筋,砖块,然后盖一层、二层、三层直到最高层,通电,通水等等多道“工序”才能完成成品。其中每一道工序都是对产品的全新创建、但是我们只关心最后的终极产品—【房子】。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Custom.Log;

# region BaseClass
/// 
/// 水泥
/// 
public class Cement { }
/// 
/// 砖块
/// 
public class Brick { }
/// 
/// 钢筋
/// 
public class Rebar { }
/// 
/// 楼层
/// 
public class Floor { }

/// 
/// 水
/// 
public class Water { }
/// 
/// 电
/// 
public class Electricity { }
/// 
/// 房屋
/// 
public class House { }
#endregion
public abstract class AbstractBuilder
{
    public abstract void Build_Cement();
    public abstract void Build_Brick();
    public abstract void Build_Rebar();
    public abstract void Build_Floor();
    public abstract void Build_Water();
    public abstract void Build_Electricity();
    public abstract House Build_House();
}

public class HouseBuilder : AbstractBuilder
{
    public override void Build_Brick()
    {
        this.Log("建造砖块");
    }
    public override void Build_Cement()
    {
        this.Log("建造水泥");
    }
    public override void Build_Electricity()
    {
        this.Log("建造电力");
    }
    public override void Build_Floor()
    {
        this.Log("建造楼层");
    }
    public override void Build_Rebar()
    {
        this.Log("建造钢筋");
    }
    public override void Builde_Water()
    {
        this.Log("建造水");
    }
    public override House Build_House()
    {
        this.Log("房屋建造完成");
        return new House();
    }
}

public class Director
{
    private AbstractBuilder abstractBuilder = null;

    public Director(AbstractBuilder tempAbstractBuilder)
    {
        abstractBuilder = tempAbstractBuilder;
    }

    public House GetResult()
    {
        abstractBuilder.Build_Brick();//建造砖块
        abstractBuilder.Build_Cement();//建造水泥
        abstractBuilder.Build_Rebar();//建造钢筋
        abstractBuilder.Build_Floor();//建造楼层
        abstractBuilder.Build_Electricity();//电力完成
        abstractBuilder.Build_Water();//水源完成
        House temp =  abstractBuilder.Builde_House();//成品房屋完成
        this.Log($"完全品{temp}完成");
        return temp;
    }
}

调用

public class BuilderComponent : MonoBehaviour
{
    void Start()
    {
        AbstractBuilder abstractBuilder = new HouseBuilder();
        Director director = new Director(abstractBuilder);
        House house = director.GetResult();
    }
}

打印信息

1.5.5 写法对比:

1.5.6 模式分析:

下面笔者和大家具体分析一下建造者模式,其实这个和抽象工厂模式特别的相似,都是创建产品,但建造者模式只是把原有工厂的创建函数交给了指挥者(Director)来执行。从写法上讲,我认为【AbstractFactoryPlus】【Builder】更贴切,就是让多行的创建代码演变成现在的Director director = new Director(abstractBuilder);House house = director.GetResult();,从结构上看是把原本的创建过程再次的封装和转移,如果想修改创建过程可以直接在Director类中修改。最后通过Director组装,在组装的过程中可以不断的向一个容器内添加,获取最终的产品。

从职责划分上看:
【抽象工厂】主要是没有元素或者说一个元素对应一个产品,以这种方式产出多个产品。
【建造者模式】是多个元素建造一个产品。目的是关心一个复杂产品的构建过程(再次封装和转移)。

指挥者(Director)主要是用于创建一些复杂的对象,这些对象内部的构建顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。不过真的需要建造顺序更改时,我们也可以重新定义一个新的Director,只需要把原有的Director类替换掉就可以,上层的改动也相对较小,绝对的开闭原则是不存在的,相对的开闭才是我们应该掌握的。

【扩展说明】

或许有人会问,为什么不用接口代替对应的AbstractBuilder呢?
笔者是这样理解的,接口是一个独立的功能,不是一个系统。以手机为例:它含有上网、听音乐、看电影、储存、拍照、收发信息等功能,每一个功能都可以是一个接口。且这种接口具有通用性。

问什么说是通用性?
例如上网功能接口电脑也可以继承,拍照这种功能接口专业摄像机也可以继承这就是通用性。这种微小独立功能的划分也符合接口隔离原则

但是抽象类呢,它更像一个特殊的缩小系统,就像手机是众多电子产品中的一种,但手机的品牌很多,基于这种缩小系统基本一致性的特点,不同的品牌,我们就可以根据手机抽象类实现不同的品牌实例。而且在抽象基类里面可以实现一些字段和函数,接口则不能。

接口是能够,抽象是含有。

【举例说明】

List这种就是集各种通用功能于一身的

这种文件操作的抽象类就很局限了

1.5.7 应用场景:

  • 生成的对象具有复杂的内部结构。
  • 生成的对象内部属性本身相互依赖。

1.5.8 小结:

  • 笔者认为,如果构建过程相对简单,2-5行代码可以解决的问题用工厂模式就可以了,如果十分复杂的创建就可以考虑下建造者模式。

  • 建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品的组装顺序和细节,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者(示例中的BuilderHouse)就可以了。


更多设计模式详见:设计模式全家桶

发表评论