Saltar al contenido

¿Cómo implementar el patrón de construcción en Java 8?

Solución:

los GenericBuilder

La idea de construir objetos mutables (los objetos inmutables se tratan más adelante) es usar referencias de método a los establecedores de la instancia que se debe construir. Esto nos lleva a un constructor genérico que es capaz de construir cada POJO con un constructor predeterminado: un constructor para gobernarlos a todos 😉

La implementación es esta:

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;
    }
}

El constructor se construye con un proveedor que crea nuevas instancias y luego esas instancias son modificadas por las modificaciones especificadas con el with método.

los GenericBuilder se usaría para Person como esto:

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

Propiedades y usos adicionales

Pero hay más sobre ese constructor por descubrir.

Por ejemplo, la implementación anterior borra los modificadores. Esto podría trasladarse a su propio método. Por lo tanto, el constructor mantendría su estado entre modificaciones y sería fácil crear múltiples instancias iguales. O, dependiendo de la naturaleza de un instanceModifier, una lista de diferentes objetos. Por ejemplo, un instanceModifier podría leer su valor de un contador creciente.

Continuando con este pensamiento, podríamos implementar una fork método que devolvería un nuevo clon del GenericBuilder instancia en la que se invoca. Esto es fácilmente posible porque el estado del constructor es solo el instantiator y la lista de instanceModifiers. A partir de ahí, ambos constructores podrían modificarse con algún otro instanceModifiers. Compartirían la misma base y tendrían un estado adicional establecido en instancias compiladas.

El último punto lo considero especialmente útil cuando se necesitan entidades pesadas para pruebas unitarias o incluso de integración en aplicaciones empresariales. No habría ningún dios-objeto para las entidades, sino para los constructores.

los GenericBuilder también puede reemplazar la necesidad de diferentes fábricas de valores de prueba. En mi proyecto actual, se utilizan muchas fábricas para crear instancias de prueba. El código está estrechamente acoplado a diferentes escenarios de prueba y es difícil extraer partes de una fábrica de prueba para reutilizarlas en otra fábrica de prueba en un escenario ligeramente diferente. Con el GenericBuilder, reutilizar esto se vuelve mucho más fácil ya que solo hay una lista específica de instanceModifiers.

Para verificar que las instancias creadas son válidas, el GenericBuilder podría inicializarse con un conjunto de predicados, que se verifican en el build método después de todo instanceModifiers se ejecutan.

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);
    }
}

Creación de objetos inmutables

Para utilizar el esquema anterior para la creación de objetos inmutables, extrae el estado del objeto inmutable en un objeto mutable y usa el instanciador y el constructor para operar en el objeto de estado mutable. Luego, agregue una función que creará una nueva instancia inmutable para el estado mutable. Sin embargo, esto requiere que el objeto inmutable tenga su estado encapsulado de esta manera o se cambie de esa manera (básicamente aplicando el patrón de objeto de parámetro a su constructor).

Esto es de alguna manera diferente a lo que se usó un constructor en tiempos pre-java-8. Allí, el propio constructor era el objeto mutable que creaba una nueva instancia al final. Ahora, tenemos una separación del estado que un constructor mantiene en un objeto mutable y la funcionalidad del constructor en sí.

En esencia

Deje de escribir patrones repetitivos del generador y sea productivo con el GenericBuilder.

Puedes consultar el proyecto lombok

Para tu caso

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

Generaría el código sobre la marcha

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(){...}
    }        
}

Lombok lo hace en la fase de compilación y es transparente para los desarrolladores.

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);
    }
}

Uso

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();

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

Descargo de responsabilidad: soy el autor de la publicación

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *