Patrón Fachada

Podríamos decir que el patrón fachada es uno de los más sencillos de implementar. Por ejemplo, si en la aplicación existe un proceso/botón/servicio que se encarga de hacer llamados a muchas y diferentes clases o servicios, fachada es el patrón a utilizar.

Analicemos el siguiente ejemplo: Nuestra aplicación se encarga de procesar facturas. Una factura contiene los siguientes elementos:

  1. Información del cliente.
  2. Información de la tienda.
  3. Información de la factura.
  4. Detalle de impuestos.
  5. Detalle de descuentos.
  6. Líneas de detalle.

En este caso, tenemos 6 diferentes tipos de información que necesitamos procesar. Probablemente, cada uno de ellos, tiene diferentes validaciones y lógica específica.

Veamos un caso de uso sencillo para almacenar esos detalles:

  1. Verificar que el cliente exista, si no existe crear el cliente.
  2. Asociar la tienda y el cliente con la factura.
  3. Verificar información de la factura como número de consecutivo.
  4. Verificar información de los impuestos.
  5. Verificar información de los descuentos.
  6. Verificar todas las líneas del detalle, como por ejemplo cantidades y montos.
  7. Almacenar la factura.
  8. Asociar los detalles de impuestos con la factura.
  9. Asociar los detalles de descuentos con la factura.
  10. Asociar cada línea con la factura.
  11. Salvar todos los cambios.

¿Se imaginan a un cliente realizando todos esos llamados a diferentes clases sólo para almacenar una factura?

¿Qué sucede si introducimos un paso adicional al proceso anterior? Bueno, nuestro cliente (llámese servicio externo, aplicación, clase, etc.) tendría que modificar el código para soportar ese paso adicional.

Veamos el código del cliente con el diseño actual.

public static void main(String[] args) {
	// create the customer
	Customer customer = new Customer("John Dale", "San Jose");

	// create the store
	Store store = new Store("StoreABC", "506-1234-5678");
		
	// create the invoice
	Invoice invoice = new Invoice("INV-001");
		
	// check invoice number is valid
	invoice.checkInvoiceNumber();
		
	// add customer and store
	invoice.addCustomer(customer);
	invoice.addStore(store);
		
	// store taxes list
	ArrayList<InvoiceTaxDetail> taxes = new ArrayList();
		
	// create tax and add it to taxes
	taxes.add(new InvoiceTaxDetail("Sales Tax", 13.0));
	taxes.add(new InvoiceTaxDetail("Service Tax", 2.0));
		
	// store discounts
	ArrayList<InvoiceDiscountDetail> discounts = new ArrayList();
		
	// create discount and add it to discounts
	discounts.add(new InvoiceDiscountDetail("DiscountA", 10.0));
	discounts.add(new InvoiceDiscountDetail("DiscountB", 5.0));
		
	// store details
	ArrayList<InvoiceDetail> details = new ArrayList();
		
	// create detail and add it to details
	details.add(new InvoiceDetail(1, "ProductA", 3000));
	details.add(new InvoiceDetail(2, "ProductB", 5000));
	details.add(new InvoiceDetail(3, "ProductC", 1000));
		
	// store invoice header
	invoice.saveInvoice();
		
	// add invoice to taxes and save
	for (InvoiceTaxDetail invoiceTaxDetail : taxes) {
		invoiceTaxDetail.addInvoice(invoice);
		invoiceTaxDetail.saveTaxDetail();
	}
		
	// add invoice to discounts and save
	for (InvoiceDiscountDetail invoiceDiscountDetail : discounts) {
		invoiceDiscountDetail.addInvoice(invoice);
		invoiceDiscountDetail.saveDiscountDetail();
	}
		
	// add invoice to details and save
	for (InvoiceDetail invoiceDetail : details) {
		invoiceDetail.addInvoice(invoice);
		invoiceDetail.saveInvoiceDetail();
	}
		
	System.out.println("Process completed!");
}

Es evidente que nuestro cliente tiene demasiadas llamadas a diferentes clases para un proceso que debería ser sencillo.

En otras palabras, este diseño esta mal, muy mal. No sólo porque estamos obligando a que cualquier otra clase que quiera almacenar una factura, tenga que seguir esos tediosos pasos, sino que estarían fuertemente acopladas, es decir, cualquier cambio en el proceso, impacta a todos los clientes.

Este diagrama representa la complejidad de almacenar facturas.

Diagrama de clases que demuestra complejidad para almacenar facturas

Fachada nos ayuda a mejorar el diseño, ya que por medio de él, podemos desacoplar nuestra lógica compleja de nuestros clientes. Es decir, los cambios hechos en nuestra lógica no afectan directamente a quienes consumen el proceso.

Si introducimos una fachada al gráfico anterior, podría verse similar a esto.

Diagrama que muestra como sería implementando el patrón fachada

La clase InvoiceFacade solo tiene un método que es saveInvoice(). El constructor de esta clase, recibe todos los elementos necesarios para almacenar una factura de forma correcta. De este modo, nos aseguramos que todos los detalles mínimos para crear una factura están disponibles.

Además, los clientes que consumen el proceso, van a estar claros al momento de consumirlo, ya que deberán proveer la información necesaria.

/**
 * 
 * @author Carlos Marin
 * Runnable Patterns (runnablepatterns.com)
 * 
 * Class that represents the Facade pattern
 *
 */
public class InvoiceFacade {
	
	/**
	 * Variable to reference customer details
	 */
	private Customer customer;
	
	/**
	 * Variable to reference store details
	 */
	private Store store;
	
	/**
	 * Variable to reference invoice details
	 */
	private Invoice invoice;
	
	/**
	 * Variable to reference tax details
	 */
	private ArrayList<InvoiceTaxDetail> taxes;
	
	/**
	 * Variable to reference discount details
	 */
	private ArrayList<InvoiceDiscountDetail> discounts;
	
	/**
	 * Variable to reference invoice details
	 */
	private ArrayList<InvoiceDetail> details;

	/**
	 * Overloaded constructor
	 * @param _customer Customer to store
	 * @param _store Store to save
	 * @param _invoice Invoice to store
	 * @param _taxes Taxes list applied to invoice
	 * @param _discounts Discount list applied to invoice
	 * @param _details Invoice details
	 */
	public InvoiceFacade(Customer _customer, Store _store, Invoice _invoice, ArrayList<InvoiceTaxDetail> _taxes,
			ArrayList<InvoiceDiscountDetail> _discounts, ArrayList<InvoiceDetail> _details) {
		this.customer = _customer;
		this.store = _store;
		this.invoice = _invoice;
		this.taxes = _taxes;
		this.discounts = _discounts;
		this.details = _details;
	}
	
	/**
	 * Handle Invoice save process and steps
	 */
	public void saveInvoice() {
		
		// check invoice number is valid
		invoice.checkInvoiceNumber();
				
		// add customer and store
		invoice.addCustomer(customer);
		invoice.addStore(store);
		
		// store invoice header
		invoice.saveInvoice();
				
		// add invoice to taxes and save
		for (InvoiceTaxDetail invoiceTaxDetail : taxes) {
			invoiceTaxDetail.checkTaxDetail();
			invoiceTaxDetail.addInvoice(invoice);
			invoiceTaxDetail.saveTaxDetail();
		}
				
		// add invoice to discounts and save
		for (InvoiceDiscountDetail invoiceDiscountDetail : discounts) {
			invoiceDiscountDetail.checkDiscountDetail();
			invoiceDiscountDetail.addInvoice(invoice);
			invoiceDiscountDetail.saveDiscountDetail();
		}
				
		// add invoice to details and save
		for (InvoiceDetail invoiceDetail : details) {
			invoiceDetail.checkInvoiceDetail();
			invoiceDetail.addInvoice(invoice);
			invoiceDetail.saveInvoiceDetail();
		}
	}
}

Head First Design Patterns define este patrón de la siguiente manera:
Patrón Fachada provee una interfaz unificada para un conjunto de interfaces en un sub sistema. Fachada define una interfaz de alto nivel que hace al sub sistema más fácil de utilizar.

Este es el diagrama ilustrativo de este patrón, ya que puede variar según la implementación.

Diagrama de clases sugerido para el patrón fachada

El resultado de ejecutar el código anterior es el siguiente.

Resultado de ejecutar el código de ejemplo

Este patrón también introduce un principio de diseño llamado Mínimo Conocimiento. De nuevo, Head First Design Patterns lo define así:

Mínimo Conocimiento significa “hablar” sólo con sus conocidos inmediatos.

Es decir, trate de evitar el llamado de métodos o clases de manera concatenada. Por ejemplo, la definición de las siguientes clases.

/**
* Address class to store address details
*/
class Address {
    private String address;

    public String getAddress() {
        return this.address;
    }
}

/**
* Student class to store Student details. Least Knowledge class is Address
*/
class Student {
    private Address address;

    public Address getAddressObj() {
        return this.address;
    }
}

/**
* Mentor assigned to the Student. Least Knowledge class is Student
*/
class Mentor {
    private Student student;

    public String getStudentAddress() {
        // this breaks the desing principle!
        return this.student.getAddressObj().getAddress();
    }
}

Como podemos observar en el método getStudentAddress(), estamos concatenando varios métodos, desde una clase que no es el conocido inmediato. Este principio de diseño ofrece la siguiente guía:

Dado un método X en cualquier clase, este método debería invocar únicamente:

  • Métodos definidos en la misma clase.
  • Métodos de objetos recibidos por parámetro.
  • Métodos de cualquier objeto creado dentro del método X.
  • Métodos de objetos definidos por composición (pertenecen a los atributos de la clase).

Siguiendo esta guía nuestras clases de ejemplo cambian de la siguiente forma.

/**
* Address class to store address details
*/
class Address {
    private String address;

    public String getAddress() {
        return this.address;
    }
}

/**
* Student class to store Student details. Least Knowledge class is Address
*/
class Student {
    private Address address;

    public Address getAddressObj() {
        return this.address;
    }

    // this method help us align the design principle since it is 
    // accessing an object by composition. We will use this to 
    // get the address text
    public String getAddressDetails() {
        return this.address.getDetails();
    }
}

/**
* Mentor assigned to the Student. Least Knowledge class is Student only
*/
class Mentor {
    private Student student;

    public String getStudentAddress() {
        // now we are applying the principle!
        return this.student.getAddressDetails();
    }
}

Este principio es fundamental para implementar correctamente el patrón Fachada, ya que, necesitamos facilitar el uso de procesos complejos. Por ejemplo, si uno de nuestros procesos de almacenar factura se torna muy complejo, podríamos insertar otra fachada para facilitar el consumo de ese paso específico, haciendo a todo nuestro sistema desacoplado.

El código de ejemplo del patrón fachada lo pueden descargar aquí.

En el próximo post vamos a analizar un patrón peculiar, que se le saca mayor provecho en .Net, llamado Patrón Dispose.

Recordá suscribirte aquí para recibir las últimas actualizaciones todas las semanas.

Referencias:
Libro Head First Design Patterns. Versión digital.

Leave a Reply

Your email address will not be published. Required fields are marked *