23种设计模式-建造者模式

  1. 变化是永恒的

在模板方法模式中,用的是汽车模型举例,在这里还是用汽车模型,但是需求有变动,制作奔驰、宝马的模型,增加了一个新需求:汽车的启动、停止、喇叭声音、引擎声音都由客户自己控制,他想要什么顺序就是什么顺序。
分析一下需求,奔驰、宝马都是一个产品,他们有共有的属性,需求关心的是单个模型的运行过程:奔驰模型A是先有引擎声音,然后在响喇叭;奔驰模型B是先启动起来,然后在有引擎声音。那么为了满足需求,要什么顺序就立马能产生什么顺序的模型,这些模型都有run()方法,但是具体到没一个模型中run方法中的执行顺序并不相同,需要根据客户的需求变化顺序。先找一个简单的切入点——产品类,每个车模型都是一个产品,类图11-1:


11-1

类图比较简单,在carModel中定义一个setSequence方法,来设置运行的执行顺序,代码如下:

//汽车抽象类
public abstract class CarModel {
    private ArrayList<String> sequence;
    protected abstract void start();
    protected abstract void stop();
    protected abstract void alarm();
    protected abstract void engineBoom();
    final public void run(){
        if(sequence == null && sequence.size()<=0){
            start();
            alarm();
            engineBoom();
            stop();
        }else{
            for(String action : sequence){
                if("start".equals(action)){
                    start();
                }else if("alarm".equals(action)){
                    alarm();
                }else if("engineBoom".equals(action)){
                    engineBoom();
                }else if("stop".equals(action)){
                    stop();
                }
            }
        }
    }

    final public void setSequence(ArrayList<String> sequence){
        this.sequence = sequence;
    }
}
//宝马模型
public class BMWModel extends CarModel {
    @Override
    protected void start() {
        System.out.println("宝马start。。。");
    }

    @Override
    protected void stop() {
        System.out.println("宝马stop。。。");
    }

    @Override
    protected void alarm() {
        System.out.println("宝马alarm。。。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("宝马engineBoom。。。");
    }
}
//奔驰模型
public class BenzModel extends CarModel {
    @Override
    protected void start() {
        System.out.println("奔驰start。。。");
    }

    @Override
    protected void stop() {
        System.out.println("奔驰stop。。。");
    }

    @Override
    protected void alarm() {
        System.out.println("奔驰alarm。。。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("奔驰engineBoom。。。");
    }
}
public class Client {
    public static void main(String[] args) {
        ArrayList<String> sequence = new ArrayList<>();
        sequence.add("engineBoom");
        sequence.add("start");
        sequence.add("stop");
        BenzModel benz = new BenzModel();
        benz.setSequence(sequence);
        benz.run();
    }
}

CarModel的设计原理是这样的,setSequence方法是允许客户自己设置一个顺序。对于一个具体的模型永远都是固定的,但是对N个模型就是动态的了,run方法根据sequence中设置的顺序来决定具体方法的只想顺序。具体模型代码就不写了。
但是想想需求,汽车的动作执行顺序是能够随意调整的,我们只满足了一个需求,还有下一个需求,第二个宝马模型,只要启动、停止,其他的都不要;第三个模型,先喇叭,然后启动,然后停止。。。我们不可能一个一个的来写场景类满足需求,那么我们为每种模型定义一个建造者,需要啥顺序告诉建造者,由建造者来建造,类图11-2:


11-2

增加了一个CarBuilder抽象类,由它来组装各个车模,要什么类型什么顺序的车辆模型,都由相关子类完成,代码如下:

public abstract class CarBuilder {
    public abstract void setSequence(ArrayList<String> sequence);
    public abstract CarModel getCarModel();
}
public class BenzBuilder extends CarBuilder {
    private BenzModel benz = new BenzModel();
    @Override
    public void setSequence(ArrayList<String> sequence) {
        this.benz.setSequence(sequence);
    }

    @Override
    public CarModel getCarModel() {
        return benz;
    }
}
public class BMWBuilder extends CarBuilder {
    private BMWModel bmw = new BMWModel();
    @Override
    public void setSequence(ArrayList<String> sequence) {
        this.bmw.setSequence(sequence);
    }

    @Override
    public CarModel getCarModel() {
        return bmw;
    }
}
public class Client {
    public static void main(String[] args) {
        ArrayList<String> sequence = new ArrayList<>();
        sequence.add("engineBoom");
        sequence.add("start");
        sequence.add("stop");
        BenzBuilder builder = new BenzBuilder();
        builder.setSequence(sequence);
        BenzModel benz = (BenzModel) builder.getCarModel();
        benz.run();
    }
}

书上说,这个代码比直接访问产品类要简单了很多(并没有感觉,还得多写几行代码,直接访问产品类不也是直接setSequence吗?)。在我们做项目时,经常会有一个共识:需求是无底洞,是无理性的,不可能告诉你不加需求就不加,这四个过程(start,stop,alarm,engineboom)按排列组合有很多种,我们不可能预知他们要什么顺序,然后这里在封装一下,类图11-3:

11-3

我们增加了一个Director类,负责按照指定的顺序生产模型(就是把setSequenece的步骤写出来,需要生成那个顺序的模型直接生成就好了)Director类代码如下:

public class Director {
    private ArrayList<String> sequence = new ArrayList<String>();
    private BenzBuilder benzBuilder = new BenzBuilder();
    private BMWBuilder bmwBuilder = new BMWBuilder();
    
    public BenzModel getABenzModel(){
        this.sequence.clear();
        this.sequence.add("start");
        this.sequence.add("stop");
        this.benzBuilder.setSequence(this.sequence);
        return (BenzModel) this.benzBuilder.getCarModel();
    }
//这里还可以写很多方法,将可能会需要的顺序的模型都写出来
}

顺便说一下,这个程序中用到了很多this,如果要调用类中的成员变量或方法,需要在前面加this,还有调用父类中的成员变量或方法就加上super,这是为了让代码看着更清晰。

注意:这个ArrayList作为成员变量的时候,要考虑线程安全的问题,要防止数据混乱的情况
这个就是建造者模式。(个人感觉有点类似工厂模式,也是负责生产目标产品的一个设计模式

  1. 建造者模式的定义

建造者模式(Builder Pattern)也叫生成器模式,其定义如下:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.(将一个复杂的对象的构建与他的表示分离,是的同样的构建过程可以创建不同的表示。)
建造者模式通用类图11-4:

11-4

在建造者模式中,有如下4个角色:

  • Product产品类
    通常是实现了模板方法模式,也就是有模板方法和基本方法的,例子中的BenzModel和BMWBuilder就是属于产品类。
  • Builder抽象建造者
    规范产品的组件,一般由子类实现。例子中的CarBuilder就属于抽象建造者
  • ConcreteBuilder 具体建造者
    实现抽象类定义的所有方法,并且返回一个组件好的对象。例子中的BenzBuilder和BMWBuilder就属于具体建造者。
  • Director导演类
    负责安排已有模块的顺序,然后告诉Builder开始建造。

建造者模式的通用源代码也比较简单,先看Product类,通常是一个组合或继承(如模板方法模式)产生的类,代码如下:

public class Product {
    public void doSomething(){
        //独立业务处理
    }
}

抽象建造者,其中,setPart方法是零件的配置,什么是零件?其他的对象,获得一个不同的零件,或者不同的装配顺序就可能产生不同的产品,代码如下:

public abstract class Builder {
    //设置产品部分,已获得不同的产品
    public abstract void setPart();
    //建造产品
    public abstract Product buildProduct();
}

具体的建造者,需要注意的是,如果有多个产品类就有几个具体的建造者,而且这多个产品类具有相同接口或抽象类,代码如下:

public class ConcreteBuilder extends Builder  {
    private Product product = new Product();
    @Override
    public void setPart() {
        //产品类内的逻辑处理
    }

    @Override
    public Product buildProduct() {
        return product;
    }
}

导演类,起到封装的作用,避免高层模块深入到建造者内部的实现类,当然,在建造者模块比较庞大时,导演类可以有多个,代码如下:

public class Director {
    private Builder builder = new ConcreteBuilder();
    public Product getAProduct(){
        builder.setPart();
        return builder.buildProduct();
    }
}
  1. 建造者模式的应用

3.1 建造者模式的优点

  • 封装性
    使用建造者模式可以使客户端不必知道产品内部组成的细节,如例子中我们就不需要关心每一个具体的模型内部是如何实现的,产生的对象类型就是CarModel。
  • 建造者独立,容易扩展
    BenzBuilder和BMWBuilder是相互独立的,如果我们要扩展新的汽车模型,只需要添加对应的产品类和建造类,并不影响其他模型,符合开闭原则
  • 便于控制细节风险
    由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他模块产生任何影响,这就是低耦合的表现
    3.2 建造者模式的使用场景
  • 相同的方法,不同的执行顺序,产生不同的事件结果,例子就很明显的代表
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,就可以采用建造者模式,例如HttpClientBuilder用来创建httpClient,可以设置很多相关的配置参数,这些配置参数就是部件或零件
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能(这感觉和第二条差不多
  • 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该中场景只能是一个补偿方法,因为一个对象不容易获得,在设计阶段竟然没有发觉,这已经违反设计的最初目标了
    3.3 建造者模式的注意事项
    建造者模式关注的是零件类型和装配工艺(顺序),这是它与工厂方法模式最大不同的地方,虽然同为创建类模式,但是注重点不同。(刚想说,建造者模式和工厂模式有什么区别来着,这里就提出来了,工厂模式关注点在同一类产品,但是产品的型号或是什么是有区别的;而建造者模式则关注产品内部的方法执行顺序而导致效果不同,建造者模式创建的是相同型号的产品,但是装配工艺(顺序)有所不同
    4.建造者模式的扩展
    已经不用扩展了,因为在汽车模型的例子中进行了扩展,引入了模板方法模式,建造者模式中还有一个角色没有说明,就是零件,建造者怎么去建造一个对象?是零件的组装,组装顺序不同对象效能也不同,这才是建造者模式要表达的核心意义,而怎么才能更好的达到这种效果呢?引入模板方法模式是一个非常简单而有效的办法。
    这个建造者模式和工厂模式非常的相似,但是记住一点:建造者模式最主要的功能是基本方法的调用顺序安排,也就是基本方法已经实现了,通俗的说就是零件的装配,顺序不同产生的对象也不同;而工厂方法的重点是创建,创建零件是他的主要职责,组装顺序则不是它关心的。
    5.最佳实践
    在使用建造者模式的时候考虑一下模板方法模式,别孤立的思考一个模式,僵化地套用一个模式会让你受害无穷
    内容来自《设计模式之禅》
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容