Подтвердить что ты не робот

Как реализовать шаблон построителя в Java 8?

Часто мне кажется утомительным реализовать шаблон построителя с настройками pre-java-8. Всегда есть много почти дублированного кода. Сам строитель можно считать шаблоном.

Фактически существуют кодовые дублирующие детекторы, которые рассматривали бы почти каждый метод строителя, сделанный с объектами pre-java-8, как дубликат любого другого метода.

Итак, рассмотрим следующий класс и построитель pre-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?

4b9b3361

Ответ 1

GenericBuilder

Идея создания изменяемых объектов (неизменяемые объекты рассматриваются позже) заключается в использовании ссылок на методы для создателей экземпляра, который должен быть построен. Это приводит нас к родовому строителю, способному создавать каждый 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 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 также может заменить необходимость в разных фабриках тестовых значений. В моем текущем проекте есть много фабрик, используемых для создания тестовых экземпляров. Код тесно связан с различными сценариями тестирования, и трудно извлечь части теста factory для повторного использования в другом тесте factory в несколько ином сценарии. С помощью 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);
    }
}

Создание неподвижного объекта

Чтобы использовать приведенную выше схему для создания неизменяемых объектов, извлеките состояние неизменяемого объекта в изменяемый объект и используйте экземпляр и построитель для работы с изменяемым объектом состояния. Затем добавьте функцию, которая создаст новый неизменяемый экземпляр для изменяемого состояния. Однако это требует, чтобы неизменяемый объект либо имел свое состояние, инкапсулированное таким образом, либо оно было изменено таким образом (в основном применяя шаблон объекта параметра к его конструктору).

Это чем-то отличается от того, что строитель использовался в pre-java-8 раз. Там сам строитель был измененным объектом, который создал новый экземпляр в конце. Теперь у нас есть разделение состояния, которое строитель сохраняет в изменяемом объекте и самой функциональности компоновщика.

В сущности
Прекратите писать шаблоны шаблонов шаблонов и получите производительность с помощью GenericBuilder.

Ответ 2

Вы можете проверить проект lombok

Для вашего случая

@Builder
public class Person {
    private String name;
    private int age;
}

Он будет генерировать код на лету

public class Person {
    private String name;
    private int age;
    public String getName(){...}
    public void setName(String name){...}
    public int getAge(){...}
    public void setAge(int age){...}
    public Person.Builder builder() {...}

    public static class Builder {
         public Builder withName(String name){...}
         public Builder withAge(int age){...}
         public Person build(){...}
    }        
}

Ломбок делает это на этапе компиляции и прозрачен для разработчиков.

Ответ 3

Мы можем использовать функциональный интерфейс пользователя Java 8, чтобы избежать использования нескольких методов getter/setter.

Обратитесь к обновленному коду с интерфейсом пользователя.

import java.util.function.Consumer;

public class Person {

    private String name;

    private int age;

    public Person(Builder Builder) {
        this.name = Builder.name;
        this.age = Builder.age;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }

    public static class Builder {

        public String name;
        public int age;

        public Builder with(Consumer<Builder> function) {
            function.accept(this);
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }

    public static void main(String[] args) {
        Person user = new Person.Builder().with(userData -> {
            userData.name = "test";
            userData.age = 77;
        }).build();
        System.out.println(user);
    }
}

Обратитесь к приведенной ниже ссылке, чтобы узнать подробную информацию с различными примерами.

https://medium.com/beingprofessional/think-functional-advanced-builder-pattern-using-lambda-284714b85ed5

https://dkbalachandar.wordpress.com/2017/08/31/java-8-builder-pattern-with-consumer-interface/

Ответ 4

public class PersonBuilder {
    public String salutation;
    public String firstName;
    public String middleName;
    public String lastName;
    public String suffix;
    public Address address;
    public boolean isFemale;
    public boolean isEmployed;
    public boolean isHomewOwner;

    public PersonBuilder with(
        Consumer<PersonBuilder> builderFunction) {
        builderFunction.accept(this);
        return this;
    }


    public Person createPerson() {
        return new Person(salutation, firstName, middleName,
                lastName, suffix, address, isFemale,
                isEmployed, isHomewOwner);
    }
}

Использование

Person person = new PersonBuilder()
    .with($ -> {
        $.salutation = "Mr.";
        $.firstName = "John";
        $.lastName = "Doe";
        $.isFemale = false;
    })
    .with($ -> $.isHomewOwner = true)
    .with($ -> {
        $.address =
            new PersonBuilder.AddressBuilder()
                .with($_address -> {
                    $_address.city = "Pune";
                    $_address.state = "MH";
                    $_address.pin = "411001";
                }).createAddress();
    })
    .createPerson();

Refer: https://medium.com/beingprofessional/think-functional-advanced-builder-pattern-using-lambda-284714b85ed5

Отказ от ответственности: я являюсь автором сообщения