Lectores como tú ayudan a apoyar a MUO. Cuando realiza una compra utilizando enlaces en nuestro sitio, podemos ganar una comisión de afiliado. Leer más.

Uno de los principios más importantes en el desarrollo de software es el principio de diseño abierto-cerrado. Este principio de diseño enfatiza que las clases deben estar abiertas para la extensión, pero cerradas para la modificación. El patrón de diseño del decorador encarna el principio de diseño abierto-cerrado.

Con el patrón de diseño decorador, puede extender fácilmente una clase dándole un nuevo comportamiento sin alterar su código existente. El patrón decorador hace esto dinámicamente en tiempo de ejecución, usando composición. Este patrón de diseño se conoce como una alternativa flexible al uso de la herencia para extender el comportamiento.

¿Cómo funciona el patrón de diseño de decorador?

Aunque el patrón decorador es una alternativa a herencia de clase, incorpora algunos aspectos de la herencia en su diseño. Un aspecto clave del patrón decorador es que todas sus clases están relacionadas, ya sea directa o indirectamente.

instagram viewer

Un patrón de diseño de decorador típico tiene la siguiente estructura:

En el diagrama de clases anterior, puede ver que el patrón decorador tiene cuatro clases principales.

Componente: esta es una clase abstracta (o interfaz), que sirve como supertipo para el patrón decorador.

Componente de hormigón: estos son los objetos que puedes decorar con diferentes comportamientos en tiempo de ejecución. Heredan de la interfaz del componente e implementan sus funciones abstractas.

Decorador: esta clase es abstracta y tiene el mismo supertipo que el objeto que decorará. En el diagrama de clases, verá dos relaciones entre las clases de componente y decorador. La primera relación es de herencia; cada decorador es un componente. La segunda relación es de composición; cada decorador tiene un (o envuelve un) componente.

Decorador de Concreto: estos son los decoradores individuales que le dan a un componente un comportamiento específico. Debe tener en cuenta que cada decorador concreto tiene una variable de instancia que contiene una referencia a un componente.

Implementando el patrón de diseño Decorator en Java

Una aplicación de pedido de pizza de muestra puede demostrar adecuadamente cómo usar el patrón Decorator para desarrollar aplicaciones. Esta aplicación de pizza de muestra permite a los clientes ordenar pizzas con múltiples ingredientes. La primera clase del patrón decorador es la interfaz de pizza:

públicointerfazPizza{
públicoabstracto Cadena descripción();
públicoabstractodoblecosto();
}

La interfaz Pizza es la clase de componente. Entonces, puedes crear una o más clases concretas a partir de él. La empresa de pizzas elabora dos tipos principales de pizzas, en función de su masa. Un tipo de pizza tiene masa de levadura:

públicoclaseLevaduraCortezaPizzaimplementosPizza{
@Anular
público Cadena descripción(){
devolver"Masa de pizza hecha con levadura";
}

@Anular
públicodoblecosto(){
devolver18.00;
}
}

La YeastCrustPizza es la primera pizza clase Java de la interfaz de Pizza. El otro tipo de pizza disponible es el pan plano:

públicoclasePan PlanoCortezaPizzaimplementosPizza{
@Anular
público Cadena descripción(){
devolver"Masa de pizza hecha con pan plano";
}

@Anular
públicodoblecosto(){
devolver15.00;
}
}

La clase FlatbreadCrustPizza es el segundo componente concreto y, al igual que la clase YeastCrustPizza, implementa todas las funciones abstractas de la interfaz Pizza.

los decoradores

La clase de decorador siempre es abstracta, por lo que no puede crear una nueva instancia directamente desde ella. Pero es necesario establecer una relación entre los diferentes decoradores y los componentes que decorarán.

públicoabstractoclaseToppingDecoradorimplementosPizza{
público Cadena descripción(){
devolver"Cobertura desconocida";
}
}

La clase ToppingDecorator representa la clase decoradora en esta aplicación de ejemplo. Ahora, la empresa de pizzas puede crear muchos ingredientes diferentes (o decoradores) utilizando la clase ToppingDecorator. Digamos que una pizza puede tener tres tipos diferentes de ingredientes, a saber, queso, pepperoni y champiñones.

cobertura de queso

públicoclaseQuesoextiendeToppingDecorador{
privado pizzería pizzería;

públicoQueso(pizza pizza){
este.pizza = pizza;
}

@Anular
público Cadena descripción(){
devolver pizza.descripción() + ", cobertura de queso";
}

@Anular
públicodoblecosto(){
devolverpizza.costo() + 2.50;
}
}

Cobertura de pepperoni

públicoclasePepperoniextiendeToppingDecorador{
privado pizzería pizzería;

públicoPepperoni(pizza pizza){
este.pizza = pizza;
}

@Anular
público Cadena descripción(){
devolver pizza.descripción() + ", cobertura de pepperoni";
}

@Anular
públicodoblecosto(){
devolverpizza.costo() + 3.50;
}
}

cobertura de champiñones

públicoclaseChampiñónextiendeToppingDecorador{
privado pizzería pizzería;

públicoChampiñón(pizza pizza){
este.pizza = pizza;
}

@Anular
público Cadena descripción(){
devolver pizza.descripción() + ", cobertura de champiñones";
}

@Anular
públicodoblecosto(){
devolverpizza.costo() + 4.50;
}
}

Ahora tiene una aplicación simple implementada utilizando el patrón de diseño decorador. Si un cliente ordenara una pizza con masa de levadura con queso y pepperoni, el código de prueba para ese escenario sería el siguiente:

públicoclasePrincipal{
públicoestáticovacíoprincipal(Cadena[] argumentos){
pizzapizza1 = nuevo Pizza con masa de levadura();
pizza1 = nuevo Pepperoni (pizza1);
pizza1 = nuevo Queso (pizza1);
Sistema.salida.println (pizza1.descripción() + " $" + pizza1.costo());
}
}

Ejecutar este código producirá el siguiente resultado en la consola:

Como puede ver, la salida indica el tipo de pizza junto con su costo total. La pizza comenzó como una pizza con masa de levadura por $18,00, pero con el patrón de decorador, la aplicación pudo agregar nuevas características y su costo apropiado a la pizza. Así, dotando a la pizza de un nuevo comportamiento sin alterar el código existente (la pizza con masa de levadura).

Con el patrón decorador, también puede aplicar el mismo comportamiento a un objeto tantas veces como desee. Si un cliente pide una pizza con todo y un poco de queso extra, puede actualizar la clase principal con el siguiente código para reflejar esto:

pizzapizza2 = nuevo Pizza con masa de levadura();
pizza2 = nuevo Peperoni (pizza2);
pizza2 = nuevo queso (pizza2);
pizza2 = nuevo queso (pizza2);
pizza2 = nuevo Champiñón (pizza2);

Sistema.salida.println (pizza2.descripción() + " $" + pizza2.costo());

La aplicación actualizada producirá el siguiente resultado en la consola:

Las ventajas de usar el patrón de diseño Decorator

Las dos principales ventajas de utilizar el patrón de diseño decorador son la seguridad y la flexibilidad. El patrón decorador le permite desarrollar un código más seguro al no interferir con el código seguro preexistente. En su lugar, extiende el código existente a través de la composición. Prevenir de manera efectiva la introducción de nuevos errores o efectos secundarios no deseados.

Debido a la composición, un revelador también tiene mucha flexibilidad cuando usa el patrón decorador. Puede implementar un nuevo decorador en cualquier momento para agregar un nuevo comportamiento, sin alterar el código existente ni interrumpir la aplicación.