Factory method (Gamma et al.) Define una interfaz para crear un objeto pero deja a las subclases decidir que clase instanciar Motivación: Consideremos un framework que presenta múltiples documentos al usuario. Aquí aparecen dos abstracciones claves: Aplicación y Documento. Ambas clases son abstractas y los clientes deben crear las subclases respectivas para realizar sus implementaciones dependientes de la aplicación. Dado que la subclase particular de Documento a instanciar es dependiente de la aplicación, la clase Aplicación no puede predecir qué subclase Documento instanciar: “la clase Aplicación sólo sabe cuando un nuevo documento debe ser instanciado pero no cuál”. FactoryMethod provee una solución: encapsula el conocimiento de qué subclase de Documento crear y lo mueve fuera del framework. Metodologías de diseño y programación Factory method Ejemplo: Las subclases de Aplicación redefinen CreateDocument para retornar la subclase de Documento apropiada Metodologías de diseño y programación Factory method Estructura: Aplicabilidad: -Una clase no puede anticipar el tipo de objetos que debe crear -Una clase quiere que sus subclases especifiquen el objeto a crear Metodologías de diseño y programación Factory method Otros usos: Conecta jerarquías paralelas Metodologías de diseño y programación Factory method Implementaciones: - Variaciones: (a) La clase Creator es una clase abstracta y no provee una implementación del factory method que declara, (b) La clase Creator es una clase concreta y provee una implementación default. - Factory methods parametrizados: Un factory method puede crear múltiples productos si recibe como parámetro un valor que identifica el tipo de objeto a crear. class Creator{ public: virtual Product* create(ProductId); } Product* Creator::create(ProductId id){ if( id == ID_1){ return ID_1Product;} if( id == ID_2){ return ID_2Product;} ... return 0; } Metodologías de diseño y programación Factory method Redefiniendo un factory method parametrizado nos permite extender fácilmente la creación a incorporar nuevos productos o cambiar los productos que el Creador genera. class MyCreator: public Creator{ public: virtual Product* create(ProductId);} Product* MyCreator::create(ProductId id){ if( id == ID_1){ return ID_2Product;} if( id == ID_2){ return ID_1Product;} .... if( id == ID_N){ return ID_NProduct;} return Creator::create(id); } Metodologías de diseño y programación Iterator(continuación) AbstractList provee una interfaz común para manipular listas. Un iterador abstracto define una una interfaz de iteración común. El mecanismo de iteración es independiente de las clases “agregadas”concretas. Si se desea escribir código independiente de las subclases concretas, es conveniente tener un método createIterator() para solicitar el iterador a usar. Qué tipo de método es éste? Metodologías de diseño y programación Iterator( cont.) Estructura: Metodologías de diseño y programación Iterator(cont.) Implementaciones: - Quién controla la iteración? Si el cliente la controla se habla de iteradores externos y si el mimo iterador la controla de conocen como iteradores internos. Cuáles son más flexibles? Los externos. - Iteradores internos: cómo parametrizar el iterador con la operación que aplicar a cada elemento? template<class Item> class ListTraverser{ public: ListTraverser(List<Item*> aList); bool Traverse(); protected: virtual bool processItem(const Item); private: ListIterator<Item> _iterator; } Metodologías de diseño y programación Iterator(cont.) template <class Item> ListTraverser<Item>::ListTraverser( List<Item>* aList):_iterator(aList){} template <class Item> bool ListTraverser<Item>::Traverse(){ bool result = false; for( iterator.First(); !iterator.IsDone(); _iterator.Next()){ result = ProcessItem(_iterator.CurrentItem() ); if( result == false ) break; } return result; } Nota: Internamente usa un iterador externo para su recorrido. Metodologías de diseño y programación Iterator (cont.) Ejemplo: Usarlo para imprimir los primeros diez empleados de una listade empleados. class PrintNEmployees: public ListTraverser<Employee*>{ public: PrintNEmployees(List<Employee*>* aList, int n): ListTraverser<Employee*>(aList), _total(n), _count(0){} protected: bool ProcessItem(const Employee*); private: int _total; int _count; }; bool PrintNEmployees::ProcessItem(const Employee* e){ _count++; e->Print(); return _count < _total; } Metodologías de diseño y programación Iterator (cont.) Uso: List<Employess*>* employees; // .... PrintNEmployees pa(employees,10); pa.traverse(); El beneficio es que la lógica entera de la iteración puede ser reusada. Nota: Los iteradores internos pueden ser extendidos para procesar sólo los elementos si satisfacen un cierto test. Para esto junto al método para procesar Item, debe existir uno llamado TestItem(...). Metodologías de diseño y programación Composite Modela objetos en estructuras de árbol para representar jerarquías parte-todo. - Los clientes deben ser capaces de ignorar la diferencia entre objetos compuestos y objetos individuales. Estructura: Metodologías de diseño y programación Composite Implementación: Notar que este patrón incluye una contradicción. - El patrón hace que los clientes se olviden de las diferencias entre clases primitivas y clases compuestas. Para esto todas tienen la misma interfaz. Problema? Este diseño va en contra del principio que dice que cada clase debe tener las operaciones que tienen significado para ella. En este caso, getChild, add, remove solo tienen sentido para las subclases de Composite. - Qué implementación default podemos dar para cada una? + getChild: Si definimos que una hoja(Leaf) como una componente sin hijos, podemos definir una operación default de acceso a los hijos que nunca retorna hijos. + add, remove? No se ve de manera natural Metodologías de diseño y programación Composite - Veamos ventajas/desventajas del diseño en general: + definir estas operaciones en la raiz de la jerarquía (Component) da transparencia pues todo se maneja de manera uniforme, pero es menos segura pues los clientes pueden querer hacer add y remove de algo que no tiene sentido. + definirlas sólo en la clase Composite es más seguro pero se pierde uniformidad. + Conflicto entre transparencia y seguridad. Si optamos por seguridad en algún momento hay que usar cast: (Composite) componente. Esto tambien puede llevar a errores por eso este patrón decidió enfatizar transparencia. Recomendación: optar por seguridad agregando un método getComposite() que retorna null si es hoja y retorna this si es compuesto. El cliente entonces agrega una nueva componente con add solo si corresponde. Metodologías de diseño y programación Composite class Component{ public: // ... virtual Composite* getComposite(){return 0;} }; class Composite: public Component{ public: void add(Component*); // ... virtual Composite* getComposite(){ return this;} }; class Leaf: public Component{ // ... } Metodologías de diseño y programación Composite Uso: Composite* aComposite = new Composite(); Leaf* aLeaf = new Leaf(); Component* acomponent; Composite* test; aComponent = aComposite; if( test = aComponent->getComposite() ){ test->add(new Leaf()); } aComponent = aLeaf; if( test = aComponent->getComposite() ){ test->add(new Leaf()); // No se agrega } Metodologías de diseño y programación