Java架构师教你写代码(二) - 使用建造者替代多参数的构造器(上)

简介: Java架构师教你写代码(二) - 使用建造者替代多参数的构造器(上)

静态工厂和构造器的局限:对于大量可选参数情况,难以做到很好的扩展。


比如一个类,表示包装食品上的营养标签。

有些字段是必需的:净含量、毛重和每单位份量的卡路里,

还有 20 个可选字段,如:总脂肪、饱和脂肪、反式脂肪、胆固醇、钠…

大多食品只使用可选字段中的少数,且非零值。


这样的类怎么编写构造器或静态工厂?

SE 通常使用可伸缩构造器模式:只向构造函数提供必需的参数。

提供的第一个构造器只有必需参数,第二个构造器有一个可选参数…以此类推,最后一个构造函数具有所有可选参数。

1 伸缩式构造器模式

// 伸缩式构造器模式 - 伸缩性差
public class NutritionFacts {
    private final int servingSize; // (mL) 必须字段
    private final int servings; // (per container) 必须字段
    private final int calories; // (per serving) 可选
    private final int fat; // (g/serving) 可选
    private final int sodium; // (mg/serving) 可选
    private final int carbohydrate; // (g/serving) 可选
    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }
    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

想创建一个实例,就使用参数列表最短构造器:

NutritionFacts cocaCola =new NutritionFacts(240, 8, 100, 0, 35, 27);

该构造器包含许多额外参数,而且还必须得给它们传递值。

本例中,为 fat 传递了一个0。只有六个参数时,这可能看起来不拉几,但随着参数增加,很快失控。


可伸缩构造器模式可以用,但当有很多参数时,客户端代码很难写,可读性也差 。

阅读者想知道这些值啥意思,必须清点参数。而长序列的相同类型参数也极易导致bug。

如果调用不小心颠倒俩参数,编译器不报错,但程序在运行时会出错。


对于许多可选构造器参数,另一可行方案是

2 JavaBean 模式

调用无参构造器创建对象,然后调用 setter 方法设置所需参数和感兴趣的可选参数。

2.1 实例

4.png

It is easy, if a bit wordy(adj.冗长的), to create instances, and easy to read the resulting(v.产生;adj.作为结果的) code:


2.2 优点

该模式没有可伸缩构造函数模式的缺点。创建实例很容易,虽有点冗长,但可读性较好。

image.png

2.3 缺点

  • 因为构造过程被拆成多个set调用,所以 JavaBean 在并发下构造过程可能处于不一致。无法仅通过校验构造器参数的有效性来保证一致性。在不一致的状态下尝试使用对象可能会导致错误的发生,这比包含bug的代码还难调试。

JavaBean 模式还泯灭了使类不可变的可能性,且需SE费心思确保线程安全。

通过在对象构造完成时手动「冻结」对象,并在冻结之前不允许使用对象,可以减少这些缺陷,但是这种变通方式很笨拙,在实践中很少使用。此外,它可能在运行时导致错误,因为编译器不能确保程序员在使用对象之前调用它的 freeze 方法。


幸好,还有第三种方案,它结合可伸缩构造器模式的安全性和 JavaBean 模式的可读性

3 建造者模式

  1. 不直接生成所需对象,而使用所有必需参数调用构造器(或静态工厂),获得一个 builder 对象
  2. 然后客户端在构建器对象上调用 setter 方法设置每个感兴趣的可选参数
  3. 最后调用一个无参build方法来生成对象,这通常是不可变的。builder通常是它构建的类的静态成员类。

3.1 实例

5.png

NutritionFacts 类不可变,所有默认参数值都在一个位置。builder的 setter 方法返回builder本身,便于链式调用,得到流式 API。形如下:

image.png

特点

这样的代码易于编写,可读性佳。

为简洁,省略有效性检查。为尽快检测到无效参数,可在builder的构造器和方法中校验参数有效性。检查不可变量,包括build方法调用的构造器中的多个参数。为确保这些不可变量免受攻击,从builder复制参数后检查对象字段。如果检查失败,抛 IllegalArgumentException,指示哪些参数无效。


目录
相关文章
|
3天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
91 11
|
7天前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
25天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
50 3
|
1月前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
66 2
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
12天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
62 17
|
22天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
8天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
24天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。

热门文章

最新文章