Skip to content

Latest commit

 

History

History
380 lines (296 loc) · 13.3 KB

建造者模式.md

File metadata and controls

380 lines (296 loc) · 13.3 KB

建造者模式

变化是永恒的

这一次要根据需求,在run()方法中间执行不同顺序的任务。

类图比较简单,在CarModel中定义了一个setSequence方法,车辆模型的几个动作要如何排布,在ArrayList中定义。然后run()方法根据sequence定义的顺序完成指定的顺序动作。

车辆模型的抽象类

public abstract class CarModel {
    // 这个参数是各个基本方法执行的顺序
    private ArrayList<String> sequence = new ArrayList<String>();
    // 模型启动
    protected abstract void start();
    // 能发动,还要能停下来,才是真本事
    protected abstract void stop();
    // 发出声音
    protected abstract void alarm();
    // 引擎轰隆声
    protected abstract void engineBoom();
    // 模型会跑
    final public void run() {
        for (int i = 0; i < this.sequence.size(); i++) {
            String actionName = this.sequence.get(i);
            if (actionName.equalsIgnoreCase("start")) {
                this.start();
            } else if (actionName.equalsIgnoreCase("stop")) {
                this.stop();
            } else if (actionName.equalsIgnoreCase("alarm")) {
                this.alarm();
            } else if (actionName.equalsIgnoreCase("engine boom")) {
                this.engineBoom();
            }
        }
    }
    // 把传递过来的值传递到类内
    final public void setSequence(ArrayList sequence) {
        this.sequence = sequence;
    }
}

CarModel的设计原理,setSequence方法允许客户自己设置一个顺序。对于一个具体的模型永远是固定的,但是对N多个模型就是动态的。在子类中实现父类的基本方法,run()方法读取sequence,然后遍历sequence中的字符串,哪个字符串在先,就先执行哪个方法

奔驰车模型

public class BenzModel extends CarModel {
    protected void alarm() {
        System.out.println("奔驰车的喇叭声音是这个样子的...");
    }
    protected void engineBoom() {
        System.out.println("奔驰车的引擎是这个声音的...");
    }
    protected void start() {
        System.out.println("奔驰车跑起来是这个样子的...");
    }
    protected void stop() {
        System.out.println("奔驰车应该这样停车");
    }
}

模拟XX公司要求,跑奔驰模型代码

public class Client {
    public static void main(String[] args) {
        BenzModel benz = new BenzModel();
        ArrayList<String> sequence = new ArrayList<String>();
        sequence.add("engine boom");
        sequence.add("start");
        sequence.add("stop");
        // 把这个顺序赋予奔驰车
        benz.setSequence(sequence);
        benz.run();
    }
}

这样组装了一辆奔驰车,满足了XX公司的要求。但想想我们的需求,汽车动作执行顺序是要能够随意调整的。我们有许多需求需要满足,一个一个写场景类肯定不可能。我们要为每种模型产品定义一个建造者,要什么顺序直接告诉建造者,由建造者创建。于是有了新类图

增加了一个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();
    public void setSequence(ArrayList<String> sequence) {
        this.benz.setSequence(sequence);
    }
    public CarModel getCarModel() {
        return this.benz;
    }
}

宝马车组装者

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

修改后的场景类

public class Client {
    public static void main(String[] args) {
        ArrayList<String> sequence = new ArrayList<String>();
        sequence.add("engine boom");
        sequence.add("start");
        sequence.add("stop");
        // 要一个奔驰车
        BenzBuilder benzBuilder = new BenzBuilder();
        // 把顺序给这个builder类,制造出一个这样的车出来
        benzBuilder.setSequence(sequence);
        // 制造出一个奔驰车
        BenzModel benz = (BenzModel) benzBuilder.getCarModel();
        benz.run();
        // 按照相同顺序的宝马车
        BMWBuilder bmwBuilder = new BMWBuilder();
        bmwBuilder.setSequence(sequence);
        BMWModel bmw = (BMWModel)bmwBuilder.getCarModel();
        bmw.run();
    }
}

现在有了同样顺序的宝马和奔驰车,比刚开始直接访问产品类简单了很多。

但是现在不同过程排列组合非常多,要什么顺序的车模就必须生成什么顺序的车模。我们不可能预知他们要什么顺序的模型。因此找一个类来指挥各个事件的先后顺序,然后为每种顺序指定一个代码,说一种立刻就生产一种

类图复杂了,还是比较简单,增加了一个Director类,负责按照指定的顺序生产模型

  • getABenzModel方法

    组建出A型号的奔驰车,过程为启动、停止

  • getBBenzModel方法

    组建出B型号的奔驰车,过程为发动引擎,启动,停车

  • getCBMWModel方法

    组建出C型号的宝马车,其过程为先喇叭叫一下,启动,停车

  • getDBMWModel方法

    组建出D型号的宝马车,其过程为启动

Director类代码

public class Director {
    private ArrayList<String> sequence = new ArrayList<>();
    private BenzBuilder benzBuilder = new BenzBuilder();
    private BMWBuilder bmwBuilder = new BMWBuilder();
    // A类型奔驰车模型
    public BenzModel getABenzModel() {
        // 一定要注意清理场景
        this.sequence.clear();
        this.sequence.add("start");
        this.sequence.add("stop");
        this.benzBuilder.setSequence(this.sequence);
        return (BenzModel) this.benzBuilder.getCarModel();
    }
    // B类型奔驰车模型
    public BenzModel getBBenzModel() {
        this.sequence.clear();
        this.sequence.add("engine boom");
        this.sequence.add("start");
        this.sequence.add("stop");
        this.benzBuilder.setSequence(this.sequence);
        return (BenzModel) this.benzBuilder.getCarModel();
    }
    // C类型宝马车模型
    public BMWModel getCBMWModel() {
        this.sequence.clear();
        this.sequence.add("alarm");
        this.sequence.add("start");
        this.sequence.add("stop");
        this.benzBuilder.setSequence(this.sequence);
        return (BMWModel) this.bmwBuilder.getCarModel();
    }
    // D类型宝马车模型
    public BMWModel getDBMWModel() {
        this.sequence.clear();
        this.sequence.add("start");
        this.benzBuilder.setSequence(this.sequence);
        return (BMWModel) this.bmwBuilder.getCarModel();
    }
}

程序中有很多this调用。如果要调类中的成员变量或方法,需要在前面加上this关键字,不加也能正常跑起来,但是不清晰。加上this关键字,就是要调用本类中的成员变量和方法,而不是本方法中的一个变量,还有super方法也是一样,是调用父类的成员边浪或方法,那就加上关键字,不要省略

注意:上面每个方法都有一个this.sequence.clear()。系统分析师或技术经理一定要告诉项目成员,ArrayList和HashMap如果定义成员类的成员变量,那你在方法中的调用一定要clear,以防止数据混乱。

导演类来创建奔驰车

public class _1Client {
    public static void main(String[] args) {
        Director director = new Director();
        // 10辆A类型奔驰车
        for (int i = 0; i < 10; i++) {
            director.getABenzModel().run();
        }
        // 10辆B类型奔驰车
        for (int i = 0; i < 10; i++) {
            director.getBBenzModel().run();
        }
        // 10辆C类型宝马车
        for (int i = 0; i < 10; i++) {
            director.getCBMWModel().run();
        }
    }
}

建造者模式定义

建造者模式(Builder Pattern)也叫做生成器模式,定义如下

Seperate the construction of a complex object from its representation so that the same construction process can create different representations.

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

建造者模式通用类图

建造者模式的4个角色

  • Product产品类

    通常是实现了模板方法模式,也就是模板方法和基本方法

  • Builder抽象建造者

    规范产品的组建,一般是由子类实现例子中的CarBuilder就属于抽象建造者

  • ConcreteBuilder具体建造者

    实现抽象类定义的所有方法,并且返回一个组建好的对象

  • Director导演类

    负责安排已有的模块顺序,告诉Builder开始建造

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

产品类

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

抽象建造者

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

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

具体建造者

public class ConcreteProduct extends Builder {
    private Product product = new Product();
    // 设置产品零件
    public void setPart() {
        // 产品类内的逻辑处理
    }
    // 组建一个产品
    public Product buildProduct() {
        return product;
    }
}

导演类

public class Director {
    private Builder builder = new ConcreteProduct();
    // 构建不同的产品
    public Product getAProduct() {
        builder.setPart();
        // 设置不同的零件,产生不同的产品
        return builder.buildProduct();
    }
}

导演类起到封装的作用,避免高层模块深入建造者内部的实现类。建造者模式比较庞大时,导演类可以有多个。

建造者模式的应用

建造者模式的优点

  • 封装性

    使用建造者模式可以使客户端不必知道产品内部组成的细节,我们不需要关心每一个具体的模型内部如何实现,产生的对象类型就是CarModel

  • 建造者独立容易扩展

    BenzBuilder和BMWBuilder是相互独立的,对系统的扩展非常有利

  • 便于控制细节风险

    由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他模块产生任何影响

建造者模式的使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适
  • 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建过程,本身已经违反了设计的最初目标

建造者模式的注意事项

建造者模式关注的是零件类型和装配工艺(顺序),这是它与工厂方法模式最大不同的地方,虽同为创建类模式,但注重点不同。

建造者模式的扩展

我们在汽车模型制造的例子中已经对建造者模式进行了扩展,引入了模板方法模式。建造者模式中还有一个角色,零件。建造者通过零件的组装来创建对象,组装顺序不同对象效能也不同,这才是建造者模式要表达的核心意义。引入模板方法模式是一个非常简单而有效的方法

建造者模式和工厂方法模式非常相似,但要记住:建造者模式最主要的功能是基本方法的调用顺序安排,基本方法已经实现,顺序不同产生的对象也不同;工厂方法重点是创建,创建零件是它的主要职责,组装顺序不在考虑范围内。

最佳实践

在使用建造者模式的时候考虑一下模板方法模式,不独立思考一个模式。