当前位置: 首页 > article >正文

建造者设计模式

👉 请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大!

在这里插入图片描述

什么是建造者设计模式

建造者设计模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的一种手段。
建造者设计模式是创建者模式之一。建造者模式往往用来和工厂设计模式做类比,因为这两种设计模式在设计思路上有很相似的地方。
不过需要注意的是,搞清楚这两者的区别非常重要。当理解了两种设计模式对应的问题场景,以及分别用来解决什么样的问题以后,才算真正掌握了建造者设计模式。

建造者设计模式怎么用?

设想这样一个业务场景。
你需要设计一个类,这个类需要表示某个食品的外包装,外包装上需要显示这个食品所含有的成分。而每个食品含有的成分不同,并且有些成分是必然会印刷到包装上的,而有些成分则是某些食品所独有的。你应该怎么做?

分析上述问题,由于食品品种不同,成分不同,所以食品包装类中含有必须,非必须的成分字段。面对这样的问题,最容易想到的就是重叠构造器模式。

package builder;

/**
 * 建造者设计模式
 * 包装食品标签类(不可变)
 * 标签中拥有2个必选参数,3个可选参数
 */
public class NutritionFacts {
    // 必选
    private final int servingSize;
    private final int servings;

    // 可选
    private final int calories;
    private final int fat;
    private final int sodium;


    /**
     * 只有calories可选参数的构造器
     *
     * @param servingSize
     * @param servings
     * @param calories
     */
    public NutritionFacts(int servingSize, int servings, int calories) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = 0;
        this.sodium = 0;
    }


    /**
     * 构造器
     * 无sodium条件的构造器
     *
     * @param servingSize
     * @param servings
     * @param calories
     * @param fat
     */
    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = 0;
    }

    /**
     * 构造器
     * 满足所有条件的构造器
     *
     * @param servingSize
     * @param servings
     * @param calories
     * @param fat
     * @param sodium
     */
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
    }

    /**
     * 客户端测试
     *
     * @param args
     */
    public static void main(String[] args) {
        // 非常难以阅读的代码.完全无法知道参数是什么,而且容易传错参数.
        NutritionFacts nf = new NutritionFacts(1, 2, 3, 0, 5);
    }


}

重叠构造器很容易理解。我们为食品包装类设计了复数个构造器,每个构造器,都拥有必须持有的两个字段。而每一个构造器都比上一个构造器多出一个可选字段。这样用户就可以根据需要,调用不同的构造器创建不同的对象了。

重叠构造器能够解决问题,但是却有一个重大的缺陷:代码非常难以阅读,用户也相当难以使用这样的类。
原因很简单,用户在使用这样的类时,需要仔细去阅读 doc(如果有的话),还需要搞清楚到底哪个是可选哪个是必选。最后在创建对象时传参更是要小心翼翼-稍不注意,就会造成参数传递错误,并且是运行时错误,得不到任何检查。
在本例中只拥有 6 个成员变量的类还算勉强可以接受,但假设这个类的成员变量膨胀到 20 个,那么通过这种方式创建这种类的对象的体验实在是太糟糕了。

如何让参数传递既安全,代码又易读呢?
假设我们将大量参数分装为一个 javaBean,创建对象时,通过 getSet 方法来设值是否是一个可行的方法?

package builder;

/**
 * 建造者设计模式
 * 包装食品标签类(可变)
 * 标签中拥有2个必选参数,3个可选参数
 */
public class NutritionFactsBeanMode {

    // 必选
    private final int servingSize;
    private final int servings;

    // 可选
    private int calories;
    private int fat;
    private int sodium;

    /**
     * 构造器
     *
     * @param servingSize
     * @param servings
     */
    public NutritionFactsBeanMode(int servingSize, int servings) {
        this.servingSize = servingSize;
        this.servings = servings;
    }

    public int getServingSize() {

        return servingSize;
    }


    public int getServings() {

        return servings;
    }


    public int getCalories() {

        return calories;
    }

    public void setCalories(int calories) {

        this.calories = calories;
    }

    public int getFat() {

        return fat;
    }

    public void setFat(int fat) {

        this.fat = fat;
    }

    public int getSodium() {
        return sodium;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    /**
     * 客户端测试
     *
     * @param args
     */
    public static void main(String[] args) {
        NutritionFactsBeanMode nf = new NutritionFactsBeanMode();
        nf.setFat(1);
        nf.setSodium(2);
        nf.setCalories(3);
    }
}

JavaBean 模式似乎用非常简单的方法解决了问题。但是却引发了一个致命的缺陷。食品包装对象从一个不可变对象成为了一个可变对象。在并发的逻辑中,你就不得不花心思在它的同步处理上面。这真是有点得不偿失的感觉。

那么创建这样的较复杂对象,有没有办法写出又安全,阅读性又强,用户又易用的代码呢?
答案当然是有。结合静态内部类的创建者模式就是用来解决这样的问题的。
改进一下上例的 bean 模式,我们将 bean 改为不可变类,并且不允许外部创建对象。接下来通过创建内部的建造者对象,再调用建造方法来建造外部对象。

package builder;

/**
 * 建造者设计模式
 * 包装食品标签类(不可变)
 * 标签中拥有2个必选参数,3个可选参数
 */
public class NutritionFactsBuilderPattern {
    // 所有的外部类中的字段都是final的,保证线程安全.
    // 必选
    private final int servingSize;
    private final int servings;

    // 可选
    private final int calories;
    private final int fat;
    private final int sodium;

    /**
     * 构造器
     * 根据内部建造者的参数来初始化对象
     *
     * @param builder
     */
    private NutritionFactsBuilderPattern(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.getCalories();
        this.fat = builder.getFat();
        this.sodium = builder.getSodium();
    }

    /**
     * 建造者
     */
    public static class Builder {

        // 必选参数
        private final int servingSize;
        private final int servings;

        // 可选
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;

        public int getCalories() {
            return calories;
        }


        public Builder setCalories(int calories) {
            this.calories = calories;
            return this;
        }

        public int getFat() {
            return fat;
        }

        public Builder setFat(int fat) {
            this.fat = fat;
            return this;
        }

        public int getSodium() {
            return sodium;
        }

        public Builder setSodium(int sodium) {
            this.sodium = sodium;
            return this;
        }


        /**
         * 建造者必选参数构造器
         *
         * @param servingSize
         * @param servings
         */
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        /**
         * 通过内部类创建者的builder方法,调用外部类的构造器.
         * 创建对象.
         * builder类似构造器,能够对参数加约束条件.
         *
         * @return
         */
        public NutritionFactsBuilderPattern builder() {
            // 可以在此对所有的参数做检查.凡是不满足条件的可以在此处就报错.这就是约束条件的check
            return new NutritionFactsBuilderPattern(this);
        }


    }

    /**
     * 客户端测试
     *
     * @param args
     */
    public static void main(String[] args) {
        NutritionFactsBuilderPattern.Builder builder = new NutritionFactsBuilderPattern.Builder(1, 2);
        // 通过builder来创建需要的不可变对象.
        NutritionFactsBuilderPattern obj = builder.setCalories(2).setFat(4).setSodium(5).builder();

    }
}

分析示例代码,NutritionFactsBuilderPattern 是不可变类,保证了线程安全。建造者类的 set 与 get 方法保证了代码的易读性,并且在编译阶段就为用户提供了参数约束性检查。通过必须字段设置为 final 防止用户漏传必须字段。
建造者模式完美解决了我们的问题,还没有任何其他模式所带来的缺陷。所以它是当我们面临类似问题时应该优先选择的解决方案。


http://www.kler.cn/news/368070.html

相关文章:

  • 解决pycharm无法添加conda环境的问题【Conda Environment下没有Existing environment】
  • 停止等待协议、回退N帧协议、选择重传协议
  • 音视频同步版本【基于音频】
  • 开源实时数仓的构建
  • C++——String类讲解
  • 从零学习大模型(五)-----提示学习(Prompt Engineering)
  • 基于知识图谱的苹果病虫害知识图谱问答
  • redis详细教程(2.List教程)
  • 如何快速开发一套基于Java的诊所管理系统?
  • C++设计模式——Factory Method工厂方法模式
  • C#文件内容检索的功能
  • P11232 [CSP-S 2024] 超速检测(民间数据)
  • ES6:let和const命令解读以及变量的解构赋值
  • PostgreSQL(十三)pgcrypto 扩展实现 AES、PGP 加密,并自定义存储过程
  • Flink CDC系列之:学习理解核心概念——Transform
  • Elasticsearch 解析:倒排索引机制/字段类型/语法/常见问题
  • 双击热备和负载均衡的区别
  • 头歌数据库实验 MySQL
  • Redis 哨兵 总结
  • Vue3学习:番茄钟案例的实现及打包遇到的几个问题
  • Python 自动化运维:Python基础知识
  • Vuejs设计与实现 — 渲染器核心:挂载与更新
  • 【C++单调栈 贡献法】907. 子数组的最小值之和|1975
  • 闯关leetcode——171. Excel Sheet Column Number
  • Unity3D 自动化资源打AB包详解
  • Vue项目GET请求正常,POST请求却失效?揭秘Mock服务背后的故事