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


    1 概述

    1.1 引言

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

    1.2 复杂对象

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

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

    1.3 定义

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

    建造者模式是一种对象创建型模式。

    1.4 结构图

    在这里插入图片描述

    1.5 角色

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

    • 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 抽象建造者

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

    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()返回已创建完成的Product

    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 总结

    在这里插入图片描述

    如果觉得文章好看,欢迎点赞。

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

    在这里插入图片描述

  • 相关阅读:
    德信创业系2014版
    [学习笔记]矩形面积并
    第六章 深入分析ClassLoader工作机制
    第五章 深入class文件结构
    设计模式
    第四章 Javac编译原理
    第三章 深入分析Java Web中的中文编码问题
    第2章 深入分析java I/O的工作机制(下)
    linnx常用命令学习
    jenkins学习(1)
  • 原文地址:https://www.cnblogs.com/6b7b5fc3/p/13335706.html
Copyright © 2020-2023  润新知