En ingeniería de software , el patrón compuesto es un patrón de diseño de particionamiento . El patrón compuesto describe un grupo de objetos que se tratan de la misma manera que una única instancia del mismo tipo de objeto. La intención de un compuesto es "componer" objetos en estructuras de árbol para representar jerarquías de partes y todo. La implementación del patrón compuesto permite a los clientes tratar los objetos individuales y las composiciones de manera uniforme. [1]
Es posible que sea necesario limpiar esta sección para cumplir con los estándares de calidad de Wikipedia . El problema específico es que los encabezados de las subsecciones no deben hacer referencia redundante al título del artículo y no deben estar redactados como preguntas . Pero, ¿estos encabezados describen con precisión el contenido de la subsección? Si es así, puede ser adecuado cambiarlos a "Problemas resueltos" y "Solución descrita". ( Mayo de 2024 ) |
El patrón de diseño Composite [2] es uno de los veintitrés patrones de diseño GoF conocidos 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.
Al definir (1) Part
objetos y (2) Whole
objetos que actúan como contenedores de Part
objetos, los clientes deben tratarlos por separado, lo que complica el código del cliente. [3]
Component
interfaz unificada para Leaf
objetos parciales ( ) y Composite
objetos completos ( ).Leaf
implementan la Component
interfaz directamente y Composite
los objetos envían solicitudes a sus componentes secundarios.Esto permite que los clientes trabajen a través de la Component
interfaz para tratar objetos Leaf
y Composite
clases de manera uniforme: Leaf
los objetos realizan una solicitud directamente y Composite
los objetos reenvían la solicitud a sus componentes secundarios de manera 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.
Al trabajar con datos estructurados en árbol, los programadores a menudo tienen que distinguir entre un nodo de 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 permita tratar objetos complejos y primitivos de manera uniforme. 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 ellos con una funcionalidad similar. Esto se conoce como una relación " tiene-un " entre objetos. [4] El concepto clave es que puede manipular una sola instancia del objeto del mismo modo que 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 el cambio de tamaño de una sola forma.
Se debe utilizar el método compuesto cuando los clientes ignoran la diferencia entre composiciones de objetos y objetos individuales. [1] Si los programadores descubren que están utilizando varios objetos de la misma manera y, a menudo, tienen un código casi idéntico para manejar cada uno de ellos, entonces el método compuesto es una buena opción; en esta situación, es menos complejo tratar los primitivos y los compuestos como homogéneos.
En el diagrama de clases UML anterior , la clase no hace referencia a las clases y directamente (por separado). En cambio, hace referencia a la interfaz común y puede tratar a y de manera uniforme.
La clase no tiene objetos secundarios e implementa la interfaz directamente.
La clase mantiene un contenedor de objetos secundarios ( ) y reenvía solicitudes a estos ( ).Client
Leaf
Composite
Client
Component
Leaf
Composite
Leaf
Component
Composite
Component
children
children
for each child in children: child.operation()
El diagrama de colaboración de objetos muestra las interacciones en tiempo de ejecución: en este ejemplo, el Client
objeto envía una solicitud al Composite
objeto de nivel superior (de tipo Component
) en la estructura de árbol. La solicitud se reenvía a (se ejecuta en) todos los objetos secundarios Component
( Leaf
y Composite
objetos ) hacia abajo en la estructura de árbol.
Hay dos variantes de diseño para definir e implementar operaciones relacionadas con los componentes secundarios, como agregar o eliminar un componente secundario del contenedor ( add(child)/remove(child)
) y acceder a un componente secundario ( getChild()
):
Component
interfaz. Esto permite que los clientes traten Leaf
los Composite
objetos de manera uniforme. Sin embargo, se pierde la seguridad de tiposLeaf
porque los clientes pueden realizar operaciones relacionadas con los elementos secundarios en los objetos.Composite
clase. Los clientes deben tratar los objetos Leaf
y Composite
las clases de forma diferente. Sin embargo, la seguridad de tipos se obtiene porque los clientes no pueden realizar operaciones relacionadas con los hijos en Leaf
los objetos.El patrón de diseño compuesto enfatiza la uniformidad sobre la seguridad del tipo .
Como se describe en Patrones de diseño , el patrón también implica incluir los métodos de manipulación de elementos secundarios en la interfaz principal del componente, no solo en la subclase Composite. Las descripciones más recientes a veces omiten estos métodos. [7]
Esta implementación de C++14 se basa en la implementación anterior a C++98 del libro.
#include <iostream> #include <cadena> #include <lista> #include <memoria> #include <stdexcept> typedef doble Moneda ; // declara la interfaz para los objetos en la composición. class Equipment { // Component public : // implementa el comportamiento predeterminado para la interfaz común a todas las clases, según corresponda. virtual const std :: string & getName () { return name ; } virtual void setName ( const std :: string & name_ ) { name = name_ ; } virtual Currency getNetPrice () { return netPrice ; } virtual void setNetPrice ( Currency netPrice_ ) { netPrice = netPrice_ ; } // declara una interfaz para acceder y administrar sus componentes secundarios. virtual void add ( std :: shared_ptr < Equipment > ) = 0 ; virtual void remove ( std :: shared_ptr < Equipment > ) = 0 ; virtual ~ Equipment () = default ; protegido : Equipo () : nombre ( "" ), precio_neto ( 0 ) {} Equipo ( const std :: string & nombre_ ) : nombre ( nombre_ ), precio_neto ( 0 ) {} privado : std :: string nombre ; Moneda precio_neto ; }; // define el comportamiento de los componentes que tienen hijos. class CompositeEquipment : public Equipment { // Composite public : // implementa operaciones relacionadas con los hijos en la interfaz Component. virtual Currency getNetPrice () override { Currency total = Equipment :: getNetPrice (); for ( const auto & i : equipment ) { total += i -> getNetPrice (); } return total ; } virtual void add ( std :: shared_ptr < Equipment > equipment_ ) override { equipment . push_front ( equipment_ . get ()); } virtual void remove ( std :: shared_ptr < Equipment > equipment_ ) override { equipment . remove ( equipment_ . get ()); } protected : CompositeEquipment () : equipment () {} CompositeEquipment ( const std :: string & name_ ) : equipment () { setName ( name_ ); } private : // almacena los componentes hijos. std :: list < Equipment *> equipment ; }; // representa los objetos de hoja en la composición. class FloppyDisk : public Equipment { // Hoja public : FloppyDisk ( const std :: string & name_ ) { setName ( name_ ); } // Una hoja no tiene hijos. void add ( std :: shared_ptr < Equipment > ) override { throw std :: runtime_error ( "FloppyDisk::add" ); } void remove ( std :: shared_ptr < Equipment > ) override { throw std :: runtime_error ( "FloppyDisk::remove" ); } }; clase Chasis : público CompositeEquipment { público : Chasis ( const std :: string & nombre_ ) { setName ( nombre_ ); } }; int main () { // Los punteros inteligentes evitan fugas de memoria. std :: shared_ptr < FloppyDisk > fd1 = std :: make_shared < FloppyDisk > ( "Disquete de 3,5 pulgadas" ); fd1 -> setNetPrice ( 19,99 ); std :: cout << fd1 -> getName () << ": netPrice=" << fd1 -> getNetPrice () << '\n' ; std :: shared_ptr < FloppyDisk > fd2 = std :: make_shared < FloppyDisk > ( "Disquete de 5,25 pulgadas" ); fd2 -> setNetPrice ( 29,99 ); std :: cout << fd2 -> getName () << ": netPrice=" << fd2 -> getNetPrice () << '\n' ; std :: unique_ptr < Chasis > ch = std :: make_unique < Chasis > ( "Chasis de PC" ); ch - > setNetPrice ( 39.99 ); ch -> add ( fd1 ); ch -> add ( fd2 ); std :: cout << ch -> getName () << ": netPrice=" << ch -> getNetPrice () << '\n' ; fd2 -> agregar ( fd1 ); }
La salida del programa es
3.5 en disquete : netPrice = 19.99 5.25 en disquete : netPrice = 29.99 Chasis de PC : netPrice = 89.97 La terminación se llama después de lanzar una instancia de ' std :: runtime_error ' what () : FloppyDisk :: add
{{cite book}}
: CS1 maint: varios nombres: lista de autores ( enlace )