Patró Builder

El patró de disseny Builder és un patró creacional que s’utilitza per a construir objectes complexos pas a pas.

L’objectiu principal del patró Builder és separar la creació d’un objecte complex de la seua representació i permetre diferents formes de construcció segons les necessitats.

Este patro és molt útil quan tenim objectes amb molts atributs opcionals o quan volem oferir una manera flexible i llegible de crear-los.

Components del Patró Builder

El patró Builder consta de quatre components principals:

  • Builder: Defineix una interfície per a crear objectes complexos i proporciona mètodes per a establir els atributs individuals de l’objecte.
  • Concrete Builder: Implementa la interfície Builder i proporciona una implementació concreta per a construir l’objecte complex. També té mètodes addicionals per a recuperar l’objecte construït.
  • Director: Utilitza la interfície del Builder per a construir l’objecte complex. El Director no està involucrat en la construcció directa dels objectes, sinó que s’encarrega de coordinar i orquestrar les anomenades al Builder.
  • Producte: Representa l’objecte complex que s’està construint. El Producte pot ser qualsevol objecte, des d’una simple estructura de dades fins a un objecte amb molts atributs i mètodes.

Implementació en Java

A continuació, es mostra una implementació bàsica del patró Builder:

public interface Builder {
    void buildPartA();
    void buildPartB();
    void buildPartC();
    Product getResult();
}

public class ConcreteBuilder implements Builder {
    private Product product = new Product();

    public void buildPartA() {
        // Construir la part A
    }

    public void buildPartB() {
        // Construir la part B
    }

    public void buildPartC() {
        // Construir la part C
    }

    public Product getResult() {
        return product;
    }
}

public class Director {
    public void construct(Builder builder) {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

public class Product {
    // Atributs del producte
}

public class Client {
    public static void main(String[] args) {
        Director director = new Director();
        Builder builder = new ConcreteBuilder();
        director.construct(builder);
        Product product = builder.getResult();
    }
}

En aquesta estructura:

  • ConcreteBuilder és responsable de construir les parts individuals de l’objecte complex.
  • La interfície Builder proporciona els mètodes necessaris per a construir l’objecte.
  • El Director coordina el procés de construcció.
  • El Producte és l’objecte complex que s’està construint.

Exemple d’ús del Patró Builder

Suposem que volem utilitzar una classe Persona amb els següents atributs:

  • nombre (String)
  • apellido (String)
  • edad (int)
  • email (String, opcional)

Implementació tradicional

Una manera clàssica de definir la classe Persona seria:

public class Persona {
    private String nombre;
    private String apellido;
    private int edad;
    private String email;

    public Persona(String nombre, String apellido, int edad, String email) {
        this.nombre = nombre;
        this.apellido = apellido;
        this.edad = edad;
        this.email = email;
    }

    // Getters i setters
}

No obstant això, si la classe Persona tinguera molts més atributs, el constructor es tornaria difícil d’usar i propens a errors.

Implementació amb el Patró Builder

Podem utilitzar el patró Builder per a crear objectes Persona de manera més senzilla i segura. Per a aconseguir-ho, crearem una classe interna Builder dins de la classe Persona, que tindrà els mateixos atributs que la classe Persona, però sense el constructor convencional:

public class Persona {
    private String nombre;
    private String apellido;
    private int edad;
    private String email;

    private Persona(Builder builder) {
        this.nombre = builder.nombre;
        this.apellido = builder.apellido;
        this.edad = builder.edad;
        this.email = builder.email;
    }

    public static class Builder {
        private String nombre;
        private String apellido;
        private int edad;
        private String email;

        public Builder(String nombre, String apellido, int edad) {
            this.nombre = nombre;
            this.apellido = apellido;
            this.edad = edad;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

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

    // Getters i setters
}

En aquest codi:

  • La classe Persona rep un objecte Builder com a paràmetre en el constructor privat.
  • La classe Builder conté els mateixos atributs que Persona i proporciona mètodes per a establir-los.
  • Els mètodes Builder retornen this, la qual cosa permet encadenar cridades.
  • El mètode build() crea l’objecte Persona i el retorna.

Creació d’un objecte Persona amb el Patró Builder

Per a crear una instància de Persona utilitzant aquest patró, fem el següent:

Persona persona = new Persona.Builder("Jaume", "Aragó", 33)
    .email("j.aragovalls@edu.gva.es")
    .build();

En este cas:

  • El constructor del Builder s’utilitza per a definir els atributs obligatoris (nombre, apellido i edad).
  • El mètode email() s’utilitza per a establir l’atribut opcional.
  • La crida al mètode build() retorna l’objecte Persona completament construït.

Utilitzant el patró Builder, podem crear objectes complexos amb molts atributs d’una forma més senzilla i segura, evitant possibles errors en la construcció de l’objecte. A més a més, el patró Builder ens permet afegir més atributs o funcionalitats a la classe Persona sense haver de canviar la manera en que es construeixen els objectes existents.

Exemple avançat d’ús del Patró Builder

Suposem que estem desenvolupant un sistema per a gestionar vehicles. Cada vehicle pot tindre diferents components opcionals, com ara motor, transmissió, aire condicionat o sistema de navegació.

El patró Builder ens permetrà crear diferents tipus de vehicles de manera flexible, sense necessitat d’un constructor amb molts paràmetres.


Definició de la classe Vehicle

Primer, definim la classe Vehicle, que contindrà diversos atributs opcionals.

public class Vehicle {
    private String marca;
    private String model;
    private String motor;
    private String transmissio;
    private boolean aireCondicionat;
    private boolean sistemaNavegacio;

    private Vehicle(Builder builder) {
        this.marca = builder.marca;
        this.model = builder.model;
        this.motor = builder.motor;
        this.transmissio = builder.transmissio;
        this.aireCondicionat = builder.aireCondicionat;
        this.sistemaNavegacio = builder.sistemaNavegacio;
    }

    public static class Builder {
        private String marca;
        private String model;
        private String motor = "Motor estàndard"; // Valor per defecte
        private String transmissio = "Manual"; // Valor per defecte
        private boolean aireCondicionat = false;
        private boolean sistemaNavegacio = false;

        public Builder(String marca, String model) {
            this.marca = marca;
            this.model = model;
        }

        public Builder motor(String motor) {
            this.motor = motor;
            return this;
        }

        public Builder transmissio(String transmissio) {
            this.transmissio = transmissio;
            return this;
        }

        public Builder aireCondicionat(boolean aireCondicionat) {
            this.aireCondicionat = aireCondicionat;
            return this;
        }

        public Builder sistemaNavegacio(boolean sistemaNavegacio) {
            this.sistemaNavegacio = sistemaNavegacio;
            return this;
        }

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

    @Override
    public String toString() {
        return "Vehicle{" +
                "marca='" + marca + '\'' +
                ", model='" + model + '\'' +
                ", motor='" + motor + '\'' +
                ", transmissio='" + transmissio + '\'' +
                ", aireCondicionat=" + aireCondicionat +
                ", sistemaNavegacio=" + sistemaNavegacio +
                '}';
    }
}

Creació de Vehicles amb el Patró Builder

Ara podem construir diferents tipus de vehicles amb diferents configuracions:

public class Client {
    public static void main(String[] args) {
        // Creació d'un vehicle bàsic
        Vehicle cotxeBasic = new Vehicle.Builder("Toyota", "Corolla").build();

        // Creació d'un vehicle amb aire condicionat
        Vehicle cotxeAmbAire = new Vehicle.Builder("Honda", "Civic")
                .aireCondicionat(true)
                .build();

        // Creació d'un vehicle amb transmissió automàtica i aire condicionat
        Vehicle cotxeAutomàtic = new Vehicle.Builder("Ford", "Focus")
                .transmissio("Automàtica")
                .aireCondicionat(true)
                .build();

        // Creació d'un vehicle completament equipat
        Vehicle cotxePremium = new Vehicle.Builder("BMW", "Serie 3")
                .motor("Motor turbo 2.0")
                .transmissio("Automàtica")
                .aireCondicionat(true)
                .sistemaNavegacio(true)
                .build();

        // Mostrem els vehicles creats
        System.out.println(cotxeBasic);
        System.out.println(cotxeAmbAire);
        System.out.println(cotxeAutomàtic);
        System.out.println(cotxePremium);
    }
}

  1. Cotxe bàsic: Es crea amb els valors per defecte (motor estàndard, transmissió manual).
  2. Cotxe amb aire condicionat: Afegim l’opció d’aire condicionat sense modificar la resta de característiques.
  3. Cotxe automàtic: Definim que la transmissió és automàtica i incloem aire condicionat.
  4. Cotxe premium: Configurem tots els atributs disponibles (motor turbo, transmissió automàtica, aire condicionat, sistema de navegació).


Patró DTO

El Patró de Transferència de Dades o DTO (Data Transfer Object) és un patró de disseny utilitzat per a transferir dades entre capes o components d’una aplicació amb l’objectiu de millorar l’eficiència i la flexibilitat en la comunicació entre diferents parts del sistema.

L’ús de DTOs és especialment útil en aplicacions que segueixen una arquitectura en capes, com per exemple en aplicacions Spring Boot amb capes de servei i controladors REST.

Els DTOs ajuden a evitar l’exposició directa de les entitats del domini, que normalment corresponen a entitats JPA en una aplicació amb Spring Data. Això millora la seguretat i permet controlar millor la informació que es transfereix entre les capes de l’aplicació.


Components del Patró DTO

El patró DTO consta de dos components principals:

  • DTO: És l’objecte de transferència de dades que conté un conjunt d’atributs que es transferiran entre les capes o components. A diferència del patró Builder, el DTO no té una estructura jeràrquica clara, ja que simplement emmagatzema dades i no té mètodes específics.
  • Classe Assembladora (Assembler o Mapper): És la classe encarregada de convertir entre una entitat del domini i un DTO, i viceversa. Esta transformació és necessària per adaptar les dades entre les capes amb diferents models de domini i evitar exposar directament les entitats de la base de dades.

Què és una entitat del domini?

En el context de Spring Boot, una entitat del domini sol ser una entitat JPA, que representa una taula de la base de dades i està anotada amb @Entity. Per exemple:

@Entity
@Table(name = "persones")
public class Persona {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nombre;
    private String apellido;
    private int edad;
    private String email;

    // Constructors, getters i setters
}

Aquesta classe Persona és una entitat del domini perquè forma part del model de dades que es guarda en la base de dades. Per a transferir informació d’esta entitat sense exposar directament l’objecte JPA, utilitzem un DTO.


Definició del DTO

Un DTO és una classe que conté només les dades necessàries per a la comunicació entre capes. Normalment té constructors, getters i setters:

public class PersonaDTO {
    private String nombre;
    private String apellido;
    private int edad;
    private String email;

    public PersonaDTO() {}

    public PersonaDTO(String nombre, String apellido, int edad, String email) {
        this.nombre = nombre;
        this.apellido = apellido;
        this.edad = edad;
        this.email = email;
    }

    // Getters i setters
}

Classe Assembladora per a la conversió entre Entitat i DTO

Una Classe Assembladora s’encarrega de convertir entre l’entitat Persona i el PersonaDTO. Açò permet mantenir separades les dades de la base de dades i les dades que es transfereixen en l’API REST.

public class PersonaDTOAssemblador {

    // Converteix una entitat Persona a un DTO
    public static PersonaDTO convertirAPersonaDTO(Persona persona) {
        if (persona == null) return null;

        return new PersonaDTO(
            persona.getNombre(),
            persona.getApellido(),
            persona.getEdad(),
            persona.getEmail()
        );
    }

    // Converteix un DTO a una entitat Persona
    public static Persona convertirAPersona(PersonaDTO personaDTO) {
        if (personaDTO == null) return null;

        Persona persona = new Persona();
        persona.setNombre(personaDTO.getNombre());
        persona.setApellido(personaDTO.getApellido());
        persona.setEdad(personaDTO.getEdad());
        persona.setEmail(personaDTO.getEmail());

        return persona;
    }
}

Per què utilitzar una Classe Assembladora?

  • Evita exposar entitats JPA en l’API REST, protegint el model de dades.
  • Manté el codi net i separat per responsabilitats (no barregem conversió i lògica de negoci en els serveis).
  • Facilita modificacions futures, ja que podem afegir més camps en el DTO sense afectar l’entitat de la base de dades.

  • Exemple d’ús del Patró DTO
public class Client {
    public static void main(String[] args) {
        // Creació d'una instància de Persona utilitzant el Patró Builder
        Persona persona = new Persona();
        persona.setNombre("Jaume");
        persona.setApellido("Aragó");
        persona.setEdad(33);
        persona.setEmail("j.aragovalls@edu.gva.es");

        // Conversió de Persona a PersonaDTO
        PersonaDTO personaDTO = PersonaDTOAssemblador.convertirAPersonaDTO(persona);

        // Impressió de les dades del DTO
        System.out.println("DTO: " + personaDTO.getNombre() + " " + personaDTO.getApellido() + ", edat: " + personaDTO.getEdad());
    }
}

Ús del Patró DTO en el context de Spring

En el framework Spring Boot, el patró DTO s’utilitza per a gestionar la transferència de dades entre els controladors (Controllers) i els serveis (Services). Això permet separar el model de domini intern de l’aplicació dels objectes que es comuniquen amb l’interfície d’usuari o altres capes.

Exemple d’un REST Controller amb DTO en Spring Boot

@RestController
@RequestMapping("/persones")
public class PersonaController {

    @Autowired
    private PersonaService personaService;

    @PostMapping
    public ResponseEntity<PersonaDTO> crearPersona(@RequestBody @Valid PersonaDTO personaDTO) {
        // Convertim el DTO a entitat
        Persona persona = PersonaDTOAssemblador.convertirAPersona(personaDTO);

        // Guardaríem l'objecte en la base de dades (simulació)
        Persona personaGuardada = personaService.guardarPersona(persona);

        // Convertim l'entitat guardada a DTO per a la resposta
        PersonaDTO respostaDTO = PersonaDTOAssemblador.convertirAPersonaDTO(personaGuardada);

        return ResponseEntity.ok(respostaDTO);
    }
}

Validació de DTOs en Spring

Els DTOs també poden ser anotats amb validacions de Spring per a assegurar que les dades tenen el format correcte abans de ser processades:

public class PersonaDTO {
    @NotBlank
    private String nombre;

    @NotBlank
    private String apellido;

    @Min(0)
    private int edad;

    @Email
    private String email;
}

Açò assegura que:

  • Els camps requerits no siguen buits (@NotBlank).
  • L’edat siga positiva (@Min(0)).
  • L’email tinga un format vàlid (@Email).

Beneficis del patro DTO

  1. Evita exposar entitats JPA en API REST, millorant seguretat i control de dades.
  2. Separa responsabilitats, mantenint el codi més modular i fàcil de mantindre.
  3. Facilita la validació de dades en els DTOs abans d’arribar a la base de dades.
  4. Millora el rendiment, enviant només les dades necessàries en cada cas.

Aquest patró és molt important en aplicacions Spring Boot per garantir un codi més net, segur i escalable.