设计模式学习笔记(八):建造者模式

1 概述

1.1 引言

建造者模式是较为复杂的建立型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的建立过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只须要知道所需的建造者类型便可。建造者模式关注一步一步地建立一个复杂对象,不一样的具体建造者定义了不一样的建立过程,且具体建造者相互独立,增长新的建造者很是方便,无须修改已有代码,系统具备较好的扩展性。java

1.2 复杂对象

建造者模式中用到了复杂对象这个概念。编程

复杂对象就是指那些包含多个成员变量的对象,这些成员变量也叫部件或者零件,例如汽车包括方向盘,发动机,轮胎等 ,
汽车就是复杂对象,方向盘,发动机以及轮胎就是汽车的部件。微信

1.3 定义

建造者模式:将一个复杂的对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。ide

建造者模式是一种对象建立型模式。函数

1.4 结构图

在这里插入图片描述

1.5 角色

建造者模式包含如下四个角色:post

  • Builder(抽象建造者):为建立一个产品Product对象的各个部件指定抽象接口,在该接口中通常声明两类方法,一类是buildXXX()方法,用于建立复杂对象的各个部分(部件),另外一类是是getResult(),用于返回复杂对象。Builder既能够是抽象类,也能够是接口
  • ConcreteBuilder(具体建造者):实现了Builder接口或者继承了Builder类,实现各个部件的具体构造和装配方法,定义并明确其所建立的复杂对象,也能够提供一个方法返回建立好的复杂对象
  • Product(产品角色):是被构建的复杂对象,包含多个组成部件,具体建造者建立该产品的内部表示并定义其装配过程
  • Director(指挥者):指挥者又叫导演类,复杂安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,能够在其construct()建造方法中调用建造者对象的部件构造以及装配方法,完成复杂对象的建造。客户端通常只须要与导演类进行交互,在客户端肯定具体建造者的类型,并实例化具体建造者对象,而后经过导演类的构造函数或者setter将该对象传入导演类中

2 典型实现

2.1 步骤

  • 定义产品角色:通常为复杂对象
  • 定义抽象建造者:根据产品角色肯定bulidXXX的数量,同时声明相似getResult返回产品角色对象的方法
  • 定义具体建造者:实现抽象建造者中的buildXXX方法
  • 定义导演类:经过构造方法或者setter注入抽象建造者,提供相似construct的方法给外界,调用具体建造者的方法并返回产品角色对象

2.2 产品角色

通常来讲Product是一个复杂对象,典型的实现以下:测试

class Product
{
    private type1 part1;
    private type2 part2;
    private type3 part3;
    //getter + setter ...
}

其中type1type2等指各类不一样的类型,通常来讲会有嵌套类。优化

2.3 抽象建造者

抽象建造者的典型实现以下:ui

abstract class Builder
{
    protected Product product = new Product();
    public abstract void buildPart1();
    public abstract void buildPart2();
    public abstract void buildPart3();
    public Product getResult()
    {
        return product;
    }
}

抽象建造者中声明了一系列buildXXX方法,用于建立Product的各个部件,具体建立过程在ConcreteBuilder中实现,getResult()返回已建立完成的Productthis

2.4 具体建造者

ConcreteBuilder实现了Builder中的buildXXX方法,经过调用Product的setter来实现给产品对象的各部分赋值。

不一样的ConcreteBuilder在实现buildXXX时将有所区别,好比传入Product的setter参数的不一样。

另外在有些ConcreteBuilder中某些buildXXX无须实现(提供一个空实现),这些对客户端来讲无须关心,客户端只须要知道具体建造者的类型便可。

典型实现以下:

class ConcreteBuilder extends Builder
{
    public void buildPart1()
    {
        product.setPart1("part1");
    }
    public void buildPart2()
    {
        product.setPart2("part2");      
    }
    public void buildPart3()
    {
        product.setPart3("part3");
    }
}

2.5 导演类

Director类主要有两个做用:

  • 隔离了客户与建立过程
  • 控制产品的建立过程,包括某个buildXXX方法是否被调用,以及调用时的前后次序等等

指挥者针对抽象建造者编程,客户端只须要知道具体建造者的类型,便可经过指挥者调用建造者的相关方法,返回一个完整的产品对象。典型实现以下:

class Director
{
    private Builder builder;
    public Director(Builder builder)
    {
        this.builder = builder;
    }

    public void setBuilder(Builder builder)
    {
        this.builder = builder;     
    }

    public Product construct()
    {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        return builder.getResult();
    }
}

2.6 客户端

建立具体建造者并传入导演类做为构造方法参数,而后调用construct便可获取产品对象:

public static void main(String[] args) 
{
    Director director = new Director(new ConcreteBuilder());
    Product product = director.construct();
    System.out.println(product.getPart1());
}

3 实例

游戏角色的建立:不一样的角色具备差异极大的外部特征,并且要求随着游戏的进行会不断出现新的角色,也就是说扩展性要好。这里例子简化就建立三个角色:英雄,天使与恶魔,使用建造者模式进行设计。

设计以下:

  • 产品对象:Actor
  • 抽象建造者:ActorBuilder
  • 具体建造者:HeroBuilder+AngelBuilder+DevilBuilder
  • 指挥者:ActorController

代码以下:

// 复杂产品
class Actor
{
    private String type;
    private String face;
    private String costume;
    private String hairstyle;
    //getter and setter ...
}

//抽象建造者
abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public Actor createActor()
    {
        return actor;
    }
}

//具体建造者
class HeroBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("英雄"); }
    public void buildFace(){ actor.setFace("英俊"); }
    public void buildCostume(){ actor.setCostume("盔甲"); }
    public void buildHairstyle(){ actor.setHairstyle("飘逸"); }
}

class AngleBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("天使"); }
    public void buildFace(){ actor.setFace("漂亮"); }
    public void buildCostume(){ actor.setCostume("白裙"); }
    public void buildHairstyle(){ actor.setHairstyle("披肩长发"); }
}

class DevilBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("恶魔"); }
    public void buildFace(){ actor.setFace("帅气"); }
    public void buildCostume(){ actor.setCostume("黑衣"); }
    public void buildHairstyle(){ actor.setHairstyle("红色"); }
}

// 指挥者类
class ActorController
{
    public Actor construct(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildHairstyle();
        builder.buildCostume();
        return builder.createActor();
    }
}

测试类:

public class Test
{
    public static void main(String[] args) {
        ActorBuilder builder = new AngleBuilder();
        ActorController controller = new ActorController();
        Actor actor = controller.construct(builder);
        System.out.println(actor.getType());
        System.out.println(actor.getCostume());
        System.out.println(actor.getHairstyle());
        System.out.println(actor.getFace());
    }
}

4 优化

4.1 省略Director

其实Director是能够省略的,直接与Builder合并,在Builder中提供相似Direcotr中的construct()方法,并定义为静态方法,如:

abstract class ActorBuilder
{
    protected static Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public static Actor build(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildHairstyle();
        builder.buildCostume();
        return actor;
    }
}

同时客户端代码修改以下:

Actor actor = ActorBuilder.build(new AngleBuilder());
//Actor actor = ActorBuilder.build(new HeroBuilder());
//Actor actor = ActorBuilder.build(new DevilBuilder());

再简单一点的能够省略createActor中的参数:

abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public Actor build()
    {
        buildType();
        buildFace();
        buildHairstyle();
        buildCostume();
        return actor;
    }
}

同时客户端简化以下:

Actor actor = new AngleBuilder().build();

这两种方式简化了系统结构的同时又不影响灵活性以及可扩展性,可是加剧了抽象建造者的职责,若是build方法较为复杂,待构建的产品组成部分较多,建议仍是将其单独封装在Director中,这样更加符合SRP(单一权责原则)。

4.2 钩子方法

钩子方法是一种能够控制是否调用某个buildXXX的方法,特征以下:

  • 返回类型为boolean
  • 方法名通常为isXXX

例如修改ActorBuilder以下:

abstract class ActorBuilder
{
    protected Actor actor = new Actor();

    public abstract void buildType();
    public abstract void buildFace();
    public abstract void buildCostume();
    public abstract void buildHairstyle();

    public boolean isBareheaded()
    {
        return false;
    }

    public Actor createActor()
    {
        return actor;
    }
}

并修改DevilBuilder,覆盖默认方法:

class DevilBuilder extends ActorBuilder
{
    public void buildType(){ actor.setType("恶魔"); }
    public void buildFace(){ actor.setFace("帅气"); }
    public void buildCostume(){ actor.setCostume("黑衣"); }
    public void buildHairstyle(){ actor.setHairstyle("红色"); }
    public boolean isBareheaded(){ return true; }
}

最后修改ActorController

class ActorController
{
    public Actor construct(ActorBuilder builder)
    {
        builder.buildType();
        builder.buildFace();
        builder.buildCostume();
        if(builder.isBareheaded())
            builder.buildHairstyle();
        return builder.createActor();
    }
}

相比起以前的ActorController多了一次判断,测试以下:

public static void main(String[] args) {
    ActorController controller = new ActorController();
    Actor actor = controller.construct(new AngleBuilder());
    System.out.println(actor.getType());
    System.out.println(actor.getCostume());
    System.out.println(actor.getHairstyle());
    System.out.println(actor.getFace());
    System.out.println();

    actor = controller.construct(new DevilBuilder());
    System.out.println(actor.getType());
    System.out.println(actor.getCostume());
    System.out.println(actor.getHairstyle());
    System.out.println(actor.getFace());
}

输出以下:

在这里插入图片描述

4.3 返回Builder

在实际应用中Director较少出现,一般只有Builder以及Product,并且Builder是做为Product的内部类,提供一系列set方法,这些set方法返回一个Builder方便后续调用,最后以一个build()结尾,好比OkHttp中的Request/OkHttpClient

OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5000,TimeUnit.MILLISECONDS)
.readTimeout(10,TimeUnit.SECONDS)
.build();

Request request = new Request.Builder()
.url("https://xxx")
.post(requestBody)
.build();

5 主要优势

  • 封装细节:建造者模式中客户端不须要知道产品内部组成细节,将产品自己与产品的建立过程解耦,使得相同的建立过程能够建立不一样的产品对象
  • 扩展性好:每个具体建造者都相对独立,而与其余建造者无关,伊尼茨能够很方便地替换具体建造者或增长新的具体建造者,用户使用不一样的具体建造者便可获得不一样的产品对象。因为指挥者针对抽象建造者编程,增长新的具体建造者无须修改原有类库的代码,扩展方便,符合开闭原则
  • 控制建立过程:将复杂产品的建立步骤分解在不一样的方法中,使得建立过程更加清晰,更加精细地控制建立过程

6 主要缺点

  • 范围受限:建造者模式所建立的产品通常具备较多的共同点,其组成部分类似,若是产品之间的差别性很大,例如不少组成部分不相同,就不适合使用建造者模式,所以使用范围收到必定限制
  • 建造者多:若是产品内部结构复杂多变,可能会须要定义不少具体建造者类来实现这种变化,增大系统的理解难度与运行成本

7 适用场景

  • 须要生成的产品对象有复杂的内部结构,这些产品对象一般包含多个成员变量
  • 须要生成的产品对象的属性相互依赖,须要指定其生成顺序
  • 对象的建立过程独立于建立该对象的类。在建造者模式中经过引入指挥者类,将建立过程封装在指挥者类中,而再也不建造者类或者客户类中
  • 隔离复杂对象的建立与使用,并使得相同的建立过程能够建立不一样的产品

8 总结

在这里插入图片描述

若是以为文章好看,欢迎点赞。

同时欢迎关注微信公众号:氷泠之路。

在这里插入图片描述

相关文章
相关标签/搜索