当前位置: 首页 > 面试题库 >

如何在Java 8中实现构建器模式?

皇甫通
2023-03-14
问题内容

我经常发现用Java-8之前的设置来实现构建器模式很麻烦。总是有很多几乎重复的代码。建造者本身可以被视为样板。

实际上,有一些代码重复检测器,它们几乎会将使用java-8之前版本的工具构建的构建器的每种方法都视为所有其他方法的副本。

因此,请考虑以下类以及它是Java-8之前的构建器:

public class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class PersonBuilder {

    private static class PersonState {
        public String name;
        public int age;
    }

    private PersonState  state = new PersonState();

    public PersonBuilder withName(String name) {
        state.name = name;
        return this;
    }

    public PersonBuilder withAge(int age) {
        state.age = age;
        return this;
    }

    public Person build() {
        Person person = new Person();
        person.setAge(state.age);
        person.setName(state.name);
        state = new PersonState();
        return person;
    }
}

如何使用java-8工具实现构建器模式?


问题答案:

构建 可变对象
(稍后将讨论不可变对象)的想法是使用方法引用对应该构建的实例的设置方法进行设置。这使我们得到了一个通用的构建器,该构建器能够使用默认的构建器构建每个POJO-
一个构建器将它们全部统治;-)

实现是这样的:

public class GenericBuilder<T> {

    private final Supplier<T> instantiator;

    private List<Consumer<T>> instanceModifiers = new ArrayList<>();

    public GenericBuilder(Supplier<T> instantiator) {
        this.instantiator = instantiator;
    }

    public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {
        return new GenericBuilder<T>(instantiator);
    }

    public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {
        Consumer<T> c = instance -> consumer.accept(instance, value);
        instanceModifiers.add(c);
        return this;
    }

    public T build() {
        T value = instantiator.get();
        instanceModifiers.forEach(modifier -> modifier.accept(value));
        instanceModifiers.clear();
        return value;
    }
}

构建器由创建新实例的供应商构造,然后通过with方法指定的修改来修改这些实例。

GenericBuilder将用于Person如下:

Person value = GenericBuilder.of(Person::new)
            .with(Person::setName, "Otto").with(Person::setAge, 5).build();

属性和进一步用途

但是,还有更多关于该构建器的发现。

例如,以上实现清除了修饰符。可以将其移动到自己的方法中。因此,构建器将在修改之间保持其状态,并且很容易创建多个相等的实例。或者,根据的性质instanceModifier,列出不同的对象。例如,instanceModifier可以从递增的计数器中读取其值。

继续这种想法,我们可以实现一个fork方法,该方法将返回GenericBuilder调用它的实例的新克隆。这很容易实现,因为构建器的状态仅为instantiator和的列表instanceModifiers。从那以后,两个建造者都可以与其他建造者一起改变instanceModifiers。它们将共享相同的基础,并在已构建的实例上设置一些其他状态。

当需要在企业应用程序中进行大型实体进行单元甚至集成测试时,我认为最后一点特别有用。对于实体,没有上帝的对象,但是对于建造者。

GenericBuilder还可以取代不同的测试值工厂的需要。在我当前的项目中,有许多工厂用于创建测试实例。该代码与不同的测试场景紧密耦合,并且很难提取测试工厂的一部分以在稍有不同的场景中在另一个测试工厂中重用。使用时GenericBuilder,由于只存在一个特定的列表,因此重新使用它变得容易得多instanceModifiers

为了验证创建的实例是否有效,GenericBuilder可以使用一组谓词来初始化,这些谓词将build在所有instanceModifiers方法运行后在方法中进行验证。

public T build() {
    T value = instantiator.get();
    instanceModifiers.forEach(modifier -> modifier.accept(value));
    verifyPredicates(value);
    instanceModifiers.clear();
    return value;
}

private void verifyPredicates(T value) {
    List<Predicate<T>> violated = predicates.stream()
            .filter(e -> !e.test(value)).collect(Collectors.toList());
    if (!violated.isEmpty()) {
        throw new IllegalStateException(value.toString()
                + " violates predicates " + violated);
    }
}

不变的对象创建

要使用上述方案创建 不可变对象
,请将不可变对象的状态提取到可变对象中,并使用实例化器和生成器对可变状态对象进行操作。然后,添加一个函数,该函数将为可变状态创建一个新的不可变实例。但是,这要求不可变对象要么像这样封装其状态,要么以这种方式更改(基本上将参数对象模式应用于其构造函数)。

这与Java-8之前的版本中使用的生成器有所不同。在那里,构建器本身就是可变对象,它最终创建了一个新实例。现在,我们将构建器保留在可变对象中的状态与构建器功能本身分开。

本质上,
停止编写样板构建器模式,并使用可以提高生产率GenericBuilder



 类似资料:
  • 本文向大家介绍设计模式构建器模式/Java 实现,包括了设计模式构建器模式/Java 实现的使用技巧和注意事项,需要的朋友参考一下 示例 通过Builder模式,您可以以易于阅读的方式创建具有许多可选变量的类的实例。 考虑以下代码: 如果所有参数都是必需的,那么一切都很好。如果有更多的变量和/或其中一些是可选的怎么办?您不想使用必需参数和可选参数的每种可能的组合来创建大量的构造函数,因为它变得难以

  • 给定一个文件,我们可以使用将其转换为字符串流,例如。, 我们能否以类似的方式从标准输入构建行流?

  • 我更喜欢第一种方式,因为我认为在构建器中重复它们是多余的。第一种方法是否存在一些缺点? 提前谢谢,抱歉我的英语不好。

  • 问题内容: 我正在尝试使用Jenkins构建多模块Maven项目。 当我使用 与* Jenkins 相同的环境 (variables / settings.xml / user)在 命令行 上构建相同的项目时,测试失败会导致构建立即失败: * 在 詹金斯 建造时的位置: 生成状态表明所有模块和父级都是 成功的 ,实际上,这些应该是失败的。 我如何像詹姆斯在命令行中那样让詹金斯在maven构建中强制