掌握建造者模式,让你的代码更易于维护和扩展

举报
dancer 发表于 2024/03/19 19:03:47 2024/03/19
【摘要】 建造者模式是一种对象构建设计模式,它将复杂对象的构建过程分解为多个简单步骤,使对象创建更灵活、易于管理。本文介绍了建造者模式的基本概念、优势,通过案例详细剖析了其设计过程和实现方式。建造者模式适用于需要创建复杂对象且希望构建过程具有灵活性的场景。使用时需注意,建造者模式可能会增加代码的复杂性,应谨慎选择。本文为读者提供了一个快速了解建造者模式的概览,有助于理解并掌握这一设计模式。

logo2.png

💪🏻 制定明确可量化的目标,坚持默默的做事。


 建造者模式(Builder Pattern)是一种创建型设计模式,旨在将一个复杂对象的创建过程与其表示分离,使得同样的构建过程可以创建不同的表示形式。

        

主要角色:

  1. 产品(Product):表示正在构建的复杂对象。它由多个部分组成,这些部分可能具有不同的特性和属性。

  2. 抽象建造者(Abstract Builder):定义了构建产品所需的各个步骤抽象接口。每个步骤都有具体的实现方法,用于构建产品的不同部分。通常,抽象建造者还提供一个获取最终生成产品的方法。

  3. 具体建造者(Concrete Builder):实现了抽象建造者接口,负责实际构建产品的各个部分。具体建造者通常具有一个内部变量来保存当前产品的状态。构建过程中,具体建造者负责跟踪和更新产品的状态。

  4. 指挥者(Director):负责按特定顺序调用具体建造者的方法,以完成产品的构建过程。指挥者知道构建者应该如何构建产品,但不关心具体的构建细节。它通过接收具体建造者对象,按照预定的顺序调用构建步骤,最终获取完成的产品。

实现步骤:

  1. 定义产品类(Product):首先,定义一个需要被构建的复杂对象的类,这个类通常包含多个属性。

  2. 创建抽象建造者接口(Builder):建造者接口定义了用于构建产品的方法,这些方法包括设置产品的属性等。

  3. 创建具体建造者类(ConcreteBuilder):针对产品类实现抽象建造者接口,实现产品的各个部件的具体构造过程,以及提供获取最终产品的方法。

  4. 创建指挥者类(Director):指挥者类负责使用建造者接口来构建产品。它不直接构建产品,而是通过调用建造者接口中的方法来构建产品。

  5. 使用建造者模式:客户端代码通过指挥者类来构建产品,指挥者将构建过程委托给具体的建造者类,最终得到构建好的产品。

优点:

  1. 分离构建过程和最终表示:建造者模式可以将产品的构建过程与表示分离,使得相同的构建过程可以得到不同的表示。
  2. 易于扩展:通过建造者模式,可以很容易地扩展和改变产品的内部表示,使得我们可以更灵活地修改构建过程。
  3. 隐藏细节:客户端不需要关心产品的构建细节,只需关心最终的产品对象的使用即可。
  4. 构建复杂对象:对于构建具有复杂内部结构的对象来说,建造者模式可以使得构建过程更清晰、更易于维护。

缺点:

  1. 增加代码量:引入了建造者模式会增加代码量,特别是当产品的属性比较多,需要通过建造者类逐一设置时,会显得比较繁琐。
  2. 可能会导致产生多余的建造者对象:如果产品的构建过程比较简单,引入建造者模式可能会显得有些多余。

     客户端(Client):创建指挥者对象并配置具体建造者,然后通过指挥者获取最终构建完成的产品。客户端可以根据需要选择不同的具体建造者或使用自定义的建造者。客户端只需要关心如何使用最终构建完成的产品。

     建造者模式允许按照一系列步骤来构建对象,而不需要暴露复杂对象的构建过程。这种分步骤的构建方式使得构建过程更加灵活和可扩展,并且可以轻松地创建不同组合的对象。此外,建造者模式还可以避免创建过多的构造函数或重叠参数的问题。

     建造者模式可以有效地解耦复杂对象的构建过程,提高灵活性和可维护性,同时还可以支持创建不同表示形式的产品。

一、案例

     在工厂模式中,我们抽象了工厂,客户端通过创建的工厂来实例化对应的产品。假设产A、产品B和 产品C 是流水线产品,流水线步骤一样(假设固定组装手机顺序:主板 -> 电池 -> 外壳 -> 调试 -> 包装),这时,工厂模式无法满足这种场景。

     用建造者模式抽象流水线步骤实现。

 1.1 示例代码

     产品类:

class Product {
    // 产品的属性
    // ...
}

     抽象建造者接口:

interface Builder {
    void buildPart1();
    void buildPart2();
    Product getResult();
}

     具体建造者类:

class ConcreteBuilder implements Builder {

    private Product product = new Product();
    
    public void buildPart1() {
        // 构建产品的第一个部分
        // ...
    }
    public void buildPart2() {
        // 构建产品的第二个部分
        // ...
    }
    public Product getResult() {
        return this.product;
    }
}

     指挥者:

class Director {
    private Builder builder;
    
    public Director(Builder builder) {
        this.builder = builder;
    }
    
    public Product construct() {
        this.builder.buildPart1();
        this.builder.buildPart2();
        return this.builder.getResult();
    }
}

     客户端:

public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
    }
}

         

 1.2 用建造者模式重写示例

      产品类:(两个产品:小米和苹果)

@Data
public class XiaomiPhoneProduct {

    private String name = "小米";
}
@Data
public class ApplePhoneProduct {

    private String name = "苹果";
}

     抽象建造者接口:

public interface Builder {
    /**
     * 组装主板
     */
    void assembleMainboard();
    /**
     * 组装电池
     */
    void assembleBattery();
    /**
     * 组装外壳
     */
    void assembleOuter();
    /**
     * 调试
     */
    void assembleDebugging();
    /**
     * 包装
     */
    void assemblePack();

}

     小米建造者类:

public class XiaoMiBuilder implements Builder {

    private XiaomiPhoneProduct xm = new XiaomiPhoneProduct();

    @Override
    public void assembleMainboard() {
        System.out.println("组装" + xm.getName() + "产品的 主板。");
    }

    @Override
    public void assembleBattery() {
        System.out.println("组装" + xm.getName() + "产品的 电池。");
    }

    @Override
    public void assembleOuter() {
        System.out.println("组装" + xm.getName() + "产品的 外壳。");
    }

    @Override
    public void assembleDebugging() {
        System.out.println("调试" + xm.getName() + " 产品。");
    }

    @Override
    public void assemblePack() {
        System.out.println("包装" + xm.getName() + " 产品。");
    }

    public XiaomiPhoneProduct getResult() {
        System.out.println("获取" + xm.getName() + "产品");
        return xm;
    }
}

     苹果建造者类:

public class AppleBuilder implements Builder {

    private ApplePhoneProduct apple = new ApplePhoneProduct();

    @Override
    public void assembleMainboard() {
        System.out.println("组装" + apple.getName() + "产品的 主板。");
    }

    @Override
    public void assembleBattery() {
        System.out.println("组装" + apple.getName() + "产品的 电池。");
    }

    @Override
    public void assembleOuter() {
        System.out.println("组装" + apple.getName() + "产品的 外壳。");
    }

    @Override
    public void assembleDebugging() {
        System.out.println("调试" + apple.getName() + " 产品。");
    }

    @Override
    public void assemblePack() {
        System.out.println("包装" + apple.getName() + " 产品。");
    }

    public ApplePhoneProduct getResult() {
        System.out.println("获取" + apple.getName() + "产品");
        return apple;
    }
}

     指挥者:

public class Director {

    private Builder builder;

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

    public void construct() {
        // 组装主板
        this.builder.assembleMainboard();
        // 组装电池
        this.builder.assembleBattery();
        // 组装电池
        this.builder.assembleOuter();
        // 调试
        this.builder.assembleDebugging();
        // 包装
        this.builder.assemblePack();
    }
}

     客户端:

public class Client {

    public static void main(String[] args) {
        XiaoMiBuilder xiaoMiBuilder = new XiaoMiBuilder();
        Director director1 = new Director(xiaoMiBuilder);
        // 组装小米手机产品
        director1.construct();
        // 获取小米手机
        XiaomiPhoneProduct result1 = xiaoMiBuilder.getResult();

        AppleBuilder appleBuilder = new AppleBuilder();
        Director director2 = new Director(appleBuilder);
        // 组装苹果手机产品
        director2.construct();
        // 获取苹果手机
        ApplePhoneProduct result2 = appleBuilder.getResult();
    }
}

      测试支行:

组装小米产品的 主板。
组装小米产品的 电池。
组装小米产品的 外壳。
调试小米 产品。
包装小米 产品。
获取小米产品
组装苹果产品的 主板。
组装苹果产品的 电池。
组装苹果产品的 外壳。
调试苹果 产品。
包装苹果 产品。
获取苹果产品

        通过上面的 讲述,应该能很清晰地看出建造者模式的实现方式和它的优势所在了,那就是对同 一个 构建过程,只要配置不同的建造者实现,就会生成不同表现的对象。

        

二、模式讲解

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

建造者模式的本质:分离整体构建算法和部件构造。

 2.1 功能

功能:构建复杂的产品,而且是细化的、分步骤的构建产品,也就是建造者模式重在 一步 一步解决构造复杂对象的问题。不只为此,更为重要的是,这个构建的过程是统一的、固定不变的,变化的部分放到建造者部分了,只要配置不同的建造者,那么同样的构建过程,就能构建出不同的产品来。

        

 2.2 建造者模式的结构和说明

  • Builder:建造者接口,定义创建 一个Product 对象所需的各个部件的操作。
  • ConcreteBuilder:具体的建造者实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。
  • Director:指导者,也被称为导向者,主要用来使用Builder 接口,以一个统一的过程来构建所需要的Product 对象。
  • Product:产品,表示被建造者构建的复杂对象,包含多个部件。

        

 2.3 建造者模式重写代码的结构图

     建造者模式主要用于以下几个方面的应用场景中:

  1. 构建复杂对象:当需要构建的对象具有复杂的内部结构,并且需要通过多个步骤逐步构建时,可以使用建造者模式。它可以将构建过程划分为多个步骤,每个步骤由具体的建造者来实现,从而更加灵活地构建复杂对象。

  2. 需要隐藏对象构建过程的细节:使用建造者模式可以将对象的构建细节隐藏起来,只向客户端暴露一个简单的构建接口。客户端不需要关心对象的具体构建过程,只需通过指挥者类来构建对象。

  3. 构建对象的部分属性可变:如果需要构建的对象具有一些可变的属性,而且构建过程中需要根据特定的需求来设置这些属性,可以使用建造者模式。建造者模式可以通过具体的建造者类,根据不同的需求来设置不同的属性值。

  4. 构建多个相似对象:如果有多个类似的对象需要构建,但是它们的构建过程略有不同,可以使用建造者模式。可以提供多个具体的建造者类来分别构建不同的对象,从而更好地组织和管理对象的构建过程。

     优势:

  1. 简化对象的创建过程:通过建造者模式,可以将复杂对象的创建逻辑分解为多个步骤,使得构建过程更加清晰和可控。每个步骤都有对应的方法,在客户端代码中可以按需调用,不必关心具体的创建细节。

  2. 可以更灵活地构建对象:通过建造者模式,可以使用相同的构建过程来构建不同的产品。通过定义不同的具体建造者,可以创建具有不同属性配置的对象,而不会混淆或混合构建逻辑。

  3. 隐藏产品构建细节:客户端不需要了解产品的具体构建过程,可以直接通过指定建造者的方法来创建产品。这样可以减少客户端对产品内部细节的依赖,同时也可以隐藏产品内部的实现细节。

  4. 提供了更好的封装性:建造者模式将产品的创建过程封装在具体建造者中,客户端只需要调用指定的方法获取产品,而无需了解建造的细节。这种封装性可以减少对产品内部实现的直接访问,提高了代码的封装性和安全性。

  5. 支持逐步构建复杂对象:通过建造者模式,可以分步骤构建复杂对象。在每个步骤中,可以对对象进行逐渐完善和扩展,从而满足不同的需求和变化。

  6. 松散耦合:建造者模式可以用同一个构建算法构建出表现 上完全不同的产品,实现产品构建和产品表现 上的分离。建造者模式正是把产品构建的过程独立出来,使它和具体产品的表现松散耦合,从而使得构建算法可以复用,而具体产品表现也可以灵活地、方便地扩展和切换。

  7. 可以很容 易地改变产 品的内部表示:在建造者模式中,由 于Builder 对象只是提供接口给Director 使用,那么具体的部件创建和装配方式是被Builder 接口隐藏了的,Director 并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换Builder 的具体实现即可,不用管Director,因此变得很容易。

  8. 更好的复用性:建造者模式很好地实现了构建算法和具体产品实现的分离。这样一来,使得构建产品的算法可以复用。同样的道理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用。

     不足:

  1. 类的膨胀:使用建造者模式会引入额外的建造者类,导致类的数量增加。对于简单的对象,引入建造者模式可能会显得过度复杂。

  2. 对象内部结构的暴露:在一些情况下,建造者模式会要求产品对象暴露其内部结构,以便建造者能够逐步构建对象。这可能违反封装原则,并使得产品对象的内部结构对外暴露,降低了对象的安全性。

  3. 对象构建流程的不可逆转:一旦对象的构建过程开始,建造者模式往往不支持将构建过程逆转。一旦开始构建,很难回到前一个步骤进行修改。

  4. 不适合每个场景:建造者模式在对象的属性较多、复杂度较高,且对象的创建过程中涉及较多的选项和步骤时非常有用。但对于简单的对象,使用建造者模式可能会带来过多的额外复杂性。

  5. 学习和实现成本:建造者模式的实现需要定义产品类、抽象建造者、具体建造者等多个类,这可能带来额外的学习和实现成本,特别是对于初学者而言。

        

 2.4 建造者模式的调用顺序示意图

         

 3.5 相关模式

  3.5.1 建造者模式和组合模式

     这两个模式可以组合使用。

     对于复杂的组合结构,可以使用建造者模式来一步一步构建。

         

  3.5.2 建造者模式和模板方法模式

     模板方法模式主要是用来定义算法的骨架,把算法中某些步骤延迟到子类中实现。再想起建造者模式 ,Director 用来定义整体的构建算法,把算法中某些涉及到具体部件对象的创建和装配的功能,委托给具体的Builder 来实现。

     类似:

  • 都是定义一个固定的算法骨架,然后把算法中的某些具体步骤 交给其他类来完成,都能 实现整体算法步骤和某些具体 步骤实现的分离。

     区别:

  • 模式的目的:建造者模式是用来构建复杂对象的,而模板方法是用来定义算法骨架,尤其是一些复杂的业务功能的处理算法的骨架;
  • 模式的实现:建造者模式是采用委托的方法,而模板方法采用的是继承的方式; 
  • 使用的复杂度:建造者模式需要组合 Director 和Builder 对象,然后才能开始构建,要等构建完后才能得最终的对象,而模板方法就没有这么麻烦,直接使用子类对象即可。

        

  3.5.3 建造者模式和工厂方法模式

     这两个模式可以组合使用。

     建造者模式的Builder 实现中,通常需要选择具体的部件实现。一个可行的方案就是实现成为工厂方法,通过工厂方法来获取具体的部件对象,然后再进行部件的装配。

        

  3.5.4 建造者模式与工厂模式

  1. 关注点不同:
  • 工厂模式关注于创建对象的过程,即根据客户端请求创建相应的对象实例;
  • 建造者模式关注于构建复杂对象的过程,即通过一步步构建的方式创建对象。

  2. 设计目的不同:

  • 工厂模式旨在解耦对象的创建和使用,在客户端和具体实现之间建立一个工厂类,通过工厂方法创建所需的对象实例;
  • 建造者模式旨在通过一步步构建来创建一个复杂对象,也可以通过一系列构建步骤来定义不同的表示。

  3. 灵活性不同:

  • 工厂模式具有较高的灵活性,可以根据不同的需求创建不同类型的对象,且可以动态切换具体实现;
  • 建造者模式相对固定,通常使用一个具体的构造类,按照固定步骤构建对象,不太容易切换。

  4. 构建过程不同:

  • 工厂模式的创建过程相对简单,仅涉及对象的实例化,没有复杂的构建过程;
  • 建造者模式涉及多个步骤,每个步骤可以定制和配置,可以支持不同对象的创建。

        

三、建造者模式结合抽象工厂模式

     回顾上一篇抽象工厂模式

     抽象工厂模式和建造者模式可以结合使用,以实现更复杂的对象创建过程。 

 3.1 结合抽象工厂模式代码实现

     抽象工厂模式用于创建相关或依赖的对象族,而建造者模式用于创建复杂对象的构建过程。下面是一个示例,演示如何结合抽象工厂模式和建造者模式:

     首先,定义产品族的抽象工厂接口,用于创建产品族中的不同类型产品。例如,我们可以创建一个汽车工厂接口来生产不同类型的汽车及其相关零部件。

public interface CarFactory {
    Engine createEngine();
    Wheel createWheel();
    // 其他相关方法
}

     然后,定义具体的产品族工厂类,实现抽象工厂接口,并负责创建特定类型的产品。例如,我们可以创建一个奔驰汽车工厂类和一个宝马汽车工厂类。

public class BenzFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new BenzEngine();
    }

    @Override
    public Wheel createWheel() {
        return new BenzWheel();
    }
    // 其他相关方法实现
}

public class BMWFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new BMWEngine();
    }

    @Override
    public Wheel createWheel() {
        return new BMWWheel();
    }
    // 其他相关方法实现
}

     接下来,定义产品的抽象建造者接口,用于创建复杂对象。例如,我们可以创建一个汽车建造者接口来构建汽车对象。

public interface CarBuilder {
    void buildEngine();
    void buildWheel();
    // 其他构建方法
    Car getCar();
}

     然后,定义具体的产品建造者类,实现抽象建造者接口,并负责实际构建复杂对象的各个部分。例如,我们可以创建一个汽车建造者类来构建汽车对象。

public class CarBuilderImpl implements CarBuilder {
    private Car car;

    public CarBuilderImpl() {
        this.car = new Car();
    }

    @Override
    public void buildEngine() {
        // 构建引擎部分的具体逻辑
        // car.setEngine(...);
    }

    @Override
    public void buildWheel() {
        // 构建轮子部分的具体逻辑
        // car.setWheel(...);
    }

    // 其他构建方法的具体实现

    @Override
    public Car getCar() {
        return car;
    }
}

     最后,在客户端中使用抽象工厂模式和建造者模式来创建复杂对象。

public class Client {
    public static void main(String[] args) {
        // 创建奔驰汽车工厂
        CarFactory benzFactory = new BenzFactory();

        // 创建汽车建造者
        CarBuilder builder = new CarBuilderImpl();

        // 设置建造者为奔驰工厂
        builder.setCarFactory(benzFactory);

        // 建造汽车并获取
        Car car = builder.buildCar();

        // 使用创建好的汽车
        car.drive();
    }
}

     在这个例子中,我们通过抽象工厂模式创建了一个奔驰汽车工厂和一个宝马汽车工厂,并通过建造者模式创建了一个汽车对象。通过结合抽象工厂模式和建造者模式,我们可以实现更复杂的对象构建过程,并根据具体情况创建不同类型的对象。

        

 3.2 练习

     回顾工厂方法模式,请编程实现建造者模式结合工厂方法模式。

        

PS:感谢您耐心看到了这里,再麻烦动动发财的手,点个赞点个赞点个赞吧!(关注收藏也是免费的哦)

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。