Patrón compuesto
En ingeniería de software, el patrón compuesto es un patrón de diseño de partición. El patrón compuesto describe un grupo de objetos que se tratan de la misma manera que una sola instancia del mismo tipo de objeto. La intención de un compuesto es "componer" objetos en estructuras de árbol para representar jerarquías parte-todo. La implementación del patrón compuesto permite a los clientes tratar los objetos individuales y las composiciones de manera uniforme.
Resumen
El compuesto patrón de diseño es uno de los veintitrés bien conocidos Patrones de diseño de GoF que describen cómo resolver problemas de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar.
¿Qué problemas puede resolver el patrón de diseño compuesto?
- Una jerarquía parcial debe ser representada para que los clientes puedan tratar objetos parciales y completos de forma uniforme.
- Una jerarquía parcial debe ser representada como estructura de árboles.
Al definir (1) objetos Partes
y (2) objetos Total
que actúan como contenedores para objetos Partes
, los clientes deben tratarlos por separado, lo que complica el código del cliente.
¿Qué solución describe el patrón de diseño compuesto?
- Definir un unificado
Component
interfaz para ambas partes (Leaf
) objetos y enteros (Composite
) objetos. - Individual
Leaf
los objetos implementanComponent
interfaz directamente, yComposite
objetar las solicitudes a sus componentes infantiles.
Esto permite a los clientes trabajar a través de la interfaz Component
para tratar los objetos Leaf
y Composite
de manera uniforme:
Los objetos Leaf
realizan una solicitud directamente,
y objetos Compuestos
reenvía la solicitud a sus componentes secundarios de forma recursiva hacia abajo en la estructura de árbol.
Esto hace que las clases de cliente sean más fáciles de implementar, cambiar, probar y reutilizar.
Vea también el diagrama de clases y objetos UML a continuación.
Motivación
Cuando se trata de datos con estructura de árbol, los programadores a menudo tienen que discriminar entre un nodo hoja y una rama. Esto hace que el código sea más complejo y, por lo tanto, más propenso a errores. La solución es una interfaz que permite tratar de manera uniforme objetos complejos y primitivos. En la programación orientada a objetos, un compuesto es un objeto diseñado como una composición de uno o más objetos similares, todos exhibiendo una funcionalidad similar. Esto se conoce como "has-a" relación entre objetos. El concepto clave es que puede manipular una sola instancia del objeto tal como manipularía un grupo de ellos. Las operaciones que puede realizar en todos los objetos compuestos a menudo tienen una relación de mínimo común denominador. Por ejemplo, si define un sistema para representar formas agrupadas en una pantalla, sería útil definir el cambio de tamaño de un grupo de formas para que tenga el mismo efecto (en cierto sentido) que cambiar el tamaño de una sola forma.
Cuándo usar
Compuesto debe usarse cuando los clientes ignoran la diferencia entre las composiciones de objetos y los objetos individuales. Si los programadores descubren que están usando varios objetos de la misma manera y, a menudo, tienen un código casi idéntico para manejar cada uno de ellos, entonces el compuesto es una buena opción; es menos complejo en esta situación tratar los primitivos y los compuestos como homogéneos.
Estructura
Diagrama de clases y objetos UML
En el diagrama de clases UML anterior, la clase Client
no hace referencia a las clases Leaf
y Composite
directamente (por separado).
En cambio, Client
se refiere a la interfaz común de Component
y puede tratar Leaf
y Composite
de manera uniforme.
La clase Leaf
no tiene hijos e implementa la interfaz Component
directamente.
La clase Composite
mantiene un contenedor de child
Objetos Component
(child
) y solicitudes de reenvío
a estos child
(para cada hijo en children: child.operation()
).
El diagrama de colaboración de objetos
muestra las interacciones en tiempo de ejecución: En este ejemplo, el objeto Cliente
envía una solicitud al objeto Compuesto
de nivel superior (de tipo Component
) en la estructura de árbol.
La solicitud se reenvía (se realiza en) todos los objetos secundarios Component
(objetos Hoja
y Compuesto
) hacia abajo en la estructura de árbol.
- Definir las operaciones relacionadas con los niños
Hay dos variantes de diseño para definir e implementar operaciones relacionadas con niños
como agregar/eliminar un componente secundario del contenedor (add(child)/remove(child)
) y acceder a un componente secundario (getChild()
):
- Diseño para uniformidad: Las operaciones relacionadas con los niños se definen en la
Component
Interfaz. Esto permite a los clientes tratarLeaf
yComposite
objetos uniformemente. Pero la seguridad del tipo se pierde porque los clientes pueden realizar operaciones relacionadas con los niñosLeaf
objetos. - Diseño para seguridad tipo: Las operaciones relacionadas con los niños se definen únicamente en la
Composite
clase. Los clientes deben tratarLeaf
yComposite
objetos de forma diferente. Pero la seguridad del tipo se gana porque los clientes no realizar operaciones relacionadas con los niñosLeaf
objetos.
El patrón de diseño Composite enfatiza la uniformidad sobre la seguridad del tipo.
Diagrama de clases UML
- Componente
- es la abstracción de todos los componentes, incluyendo los compuestos
- declara la interfaz para objetos en la composición
- (opcional) define una interfaz para acceder al padre de un componente en la estructura recursiva, y lo implementa si es apropiado
- Leaf
- representa objetos de hoja en la composición
- aplica todos los métodos de componentes
- Compuesto
- representa un componente compuesto (componente teniendo hijos)
- implementa métodos para manipular niños
- implementa todos los métodos Componente, generalmente delegando a sus hijos
Variación
Como se describe en Patrones de diseño, el patrón también incluye la inclusión de métodos de manipulación de niños en la interfaz principal del Componente, no solo en la subclase Compuesto. Las descripciones más recientes a veces omiten estos métodos.
Ejemplos
Esta implementación de C++14 se basa en la implementación anterior a C++98 del libro.
#include ■iostream#include Identificado#include Identificador#include - No.#include - No.Tipodef doble Moneda;// declara la interfaz para objetos en la composición.clase Equipo {} // Componentepúblico: // implementa comportamiento predeterminado para la interfaz común a todas las clases, según corresponda. virtual const std::cuerda" getName() {} retorno Nombre; } virtual vacío setName()const std::cuerda" name_) {} Nombre = name_; } virtual Moneda getNetPrice() {} retorno neto Precio; } virtual vacío setNetPrice()Moneda netPrice_) {} neto Precio = netPrice_; } // declara una interfaz para acceder y gestionar sus componentes infantiles. virtual vacío añadir()std::shared_ptr.Equipo■) = 0; virtual vacío Retirar()std::shared_ptr.Equipo■) = 0; virtual ~Equipo() = por defecto;protegida: Equipo() :Nombre()"), neto Precio()0) {} Equipo()const std::cuerda" name_) :Nombre()name_), neto Precio()0) {}privado: std::cuerda Nombre; Moneda neto Precio;};// define comportamiento para componentes que tienen hijos.clase CompositeEquipamiento : público Equipo {} / Compositepúblico: // implementa operaciones relacionadas con niños en la interfaz de Componente. virtual Moneda getNetPrice() Anulación {} Moneda total = Equipo::getNetPrice(); para ()const auto" i:equipo) {} total += i-getNetPrice(); } retorno total; } virtual vacío añadir()std::shared_ptr.Equipo■ equipo_) Anulación {} equipo.push_front()equipo_.#()); } virtual vacío Retirar()std::shared_ptr.Equipo■ equipo_) Anulación {} equipo.Retirar()equipo_.#()); }protegida: CompositeEquipamiento() :equipo() {} CompositeEquipamiento()const std::cuerda" name_) :equipo() {} setName()name_); }privado: // almacena componentes infantiles. std::lista.Equipo* equipo;};// representa objetos de hoja en la composición.clase FloppyDisk : público Equipo {} // Hojapúblico: FloppyDisk()const std::cuerda" name_) {} setName()name_); } // Una hoja no tiene hijos. vacío añadir()std::shared_ptr.Equipo■) Anulación {} tiro std::runtime_error()"FloppyDisk::add"); } vacío Retirar()std::shared_ptr.Equipo■) Anulación {} tiro std::runtime_error()"FloppyDisk::remove"); }};clase Chassis : público CompositeEquipamiento {}público: Chassis()const std::cuerda" name_) {} setName()name_); }};int principal() {} // Los punteros inteligentes evitan las fugas de memoria. std::shared_ptr.FloppyDisk■ fd1 = std::make_shared.FloppyDisk■()"3.5in Floppy"); fd1-setNetPrice()19.99); std::Cout .. fd1-getName() .. ": netPrice=" .. fd1-getNetPrice() .. 'n '; std::shared_ptr.FloppyDisk■ fd2 = std::make_shared.FloppyDisk■()"5.25in Floppy"); fd2-setNetPrice()29.99); std::Cout .. fd2-getName() .. ": netPrice=" .. fd2-getNetPrice() .. 'n '; std::único_ptr.Chassis■ ch = std::make_unique.Chassis■()"PC Chassis"); ch-setNetPrice()39.99); ch-añadir()fd1); ch-añadir()fd2); std::Cout .. ch-getName() .. ": netPrice=" .. ch-getNetPrice() .. 'n '; fd2-añadir()fd1);}
La salida del programa es
3.5dentro Floppy: neto Precio=19.995.25dentro Floppy: neto Precio=29.99PC Chassis: neto Precio=89.97terminada llamado después lanzamiento an ejemplo de 'std::runtime_error' ¿Qué?(): FloppyDisk::añadir
El siguiente ejemplo, escrito en Java, implementa una clase gráfica, que puede ser una elipse o una composición de varias gráficas. Todos los gráficos se pueden imprimir. En forma de Backus-Naur,
Gráficos::= elipse tención GraphicList GraphicList::= vaciado tención GraphicList
Podría ampliarse para implementar otras formas (rectángulo, etc.) y métodos (traducir, etc.).
Java
importación Java.util. Lista;importación Java.util. ArrayList;* "Componente" */interfaz Gráfico {} //Imprime el gráfico. público vacío impresión();}* "Composite" */clase CompositeGraphic implementos Gráfico {} //Colección de gráficos infantiles. privado final Lista.Gráfico■ niñoGrafics = nuevo ArrayList■(); //Agrega el gráfico a la composición. público vacío añadir()Gráfico gráfico) {} niñoGrafics.añadir()gráfico); } //Imprime el gráfico. @Override público vacío impresión() {} para ()Gráfico gráfico : niñoGrafics) {} gráfico.impresión(); //Delegación } }}* "Leaf" */clase Ellipse implementos Gráfico {} //Imprime el gráfico. @Override público vacío impresión() {} Sistema.Fuera..println()"Ellipse"); }}* Cliente*clase CompositeDemo {} público estática vacío principal()String[] args) {} //Initializar cuatro elipses Ellipse elipse1 = nuevo Ellipse(); Ellipse elipse2 = nuevo Ellipse(); Ellipse elipse3 = nuevo Ellipse(); Ellipse elipse4 = nuevo Ellipse(); //Crea dos compuestos que contienen los elipses CompositeGraphic compositGraphic2 = nuevo CompositeGraphic(); compositGraphic2.añadir()elipse1); compositGraphic2.añadir()elipse2); compositGraphic2.añadir()elipse3); CompositeGraphic compositGraphic3 = nuevo CompositeGraphic(); compositGraphic3.añadir()elipse4); //Crear otros gráficos que contengan dos gráficos CompositeGraphic compositGraphic = nuevo CompositeGraphic(); compositGraphic.añadir()compositGraphic2); compositGraphic.añadir()compositGraphic3); //Imprimir el gráfico completo (Cuatro veces la cadena "Ellipse"). compositGraphic.impresión(); }}
Contenido relacionado
Fulgence Bienvenüe
Transporte en Nauru
Bucle costas