
Patrones de diseño en MQL5 (Parte 2): Patrones estructurales
Introducción
Le presentamos un nuevo artículo dedicado a un aspecto importante del desarrollo de software: los patrones de diseño. En el artículo anterior, hablamos de los patrones de creación. Si no lo ha leído, le recomiendo empezar por ahí: Patrones de diseño en MQL5 (Parte I): Patrones de creación. El artículo anterior supone una introducción al tema de los patrones de diseño y habla en general de su utilidad en el desarrollo de software.
¿Quién debería estudiar los patrones de diseño? Cualquiera que desee desarrollar sus habilidades de programación y llevarlas al siguiente nivel. Estos patrones ofrecen un plan listo para resolver problemas específicos. Con ellos no tendrá que reinventar la rueda. Si utiliza estos patrones, obtendrá soluciones muy prácticas y comprobables.
En el presente artículo analizaremos los patrones de diseño estructurales. Asimismo, veremos cómo pueden resultar útiles a la hora de escribir programas y cómo ayudan a formar estructuras más amplias. Para ello, usaremos las clases existentes. Por supuesto, el objetivo principal de este artículo es mostrar cómo utilizar dichos patrones en la programación MQL5, cómo utilizarlos para desarrollar aplicaciones comerciales eficaces y cómo usarlos en el terminal MetaTrader 5.
Analizaremos los patrones de tipo estructural en las secciones siguientes:
- Patrones estructurales
- Adaptador (Adapter)
- Puente (Bridge)
- Compositor (Composite)
- Decorador (Decorator)
- Fachada (Facade)
- Peso mosca (Flyweight)
- Proxy
- Conclusión
Esperamos que el artículo le resulte útil y le ayude a mejorar sus habilidades de desarrollo y programación. Una vez más, permítanme recordarles que el tema de los patrones de diseño está estrechamente relacionado con la programación orientada a objetos, cuyos principios resultan bastante importantes para entender esta serie de artículos. Así que si necesita una introducción a la programación orientada a objetos, le recomiendo que lea uno de mis artículos anteriores dedicado ello: "Programación orientada a objetos (POO) en MQL5".
Patrones estructurales
En esta parte, nos familiarizaremos con los patrones de diseño estructurales, sus tipos y estructuras. Los patrones estructurales se refieren a un método de estructuración de clases y objetos que servirán como componentes para crear estructuras mayores. Estos patrones se usan para componer interfaces e implementaciones, utilizando fundamentalmente el concepto de herencia. El concepto de herencia significa que tendremos una clase que posee o combina las propiedades de sus clases padre. Este patrón resultará especialmente útil cuando haya que organizar el trabajo conjunto de varias clases desarrolladas de forma independiente.
Tipos de patrones estructurales:
- Adaptador - permite obtener la interfaz que esperan los clientes transformando la interfaz de la clase.
- Puente - permite separar una abstracción y su implementación para que puedan cambiar independientemente.
- Compositor - combina objetos en una estructura de árbol para representar una jerarquía de lo particular a lo global. Además, el compositor permite a los clientes tratar de la misma manera los objetos individuales y sus composiciones.
- Decorador - se puede utilizar para adjuntar dinámicamente un comportamiento adicional a los objetos actuales, y como alternativa flexible a la práctica de crear subclases para ampliar la funcionalidad.
- Fachada - ofrece una interfaz unificada para un conjunto de interfaces de un subsistema usando la definición de un único punto de interacción con el subsistema.
- Peso mosca - se utiliza para reducir costes al trabajar con un gran número de objetos pequeños.
- Sustituto (Proxy) - ofrece un objeto que controla el acceso a otro objeto interceptando todas las llamadas.
Al analizar estos patrones, intentaremos responder a las siguientes preguntas para cada uno de ellos:
- ¿Qué hace el patrón?
- ¿Qué problema resuelve el patrón?
- ¿Cómo usarlo en MQL5?
Adaptador (Adapter)
Trataremos los tipos de patrones de diseño estructurales desde el primero, el adaptador. La palabra clave para entender este modelo es adaptabilidad. En pocas palabras, si tenemos una interfaz que se puede usar en determinadas condiciones, y luego hay actualizaciones de esas condiciones, deberemos hacer actualizaciones de la interfaz para que el código pueda adaptarse y funcionar eficazmente en esas nuevas condiciones. Eso es exactamente lo que hace un patrón: convierte la interfaz de nuestra clase en otra que el cliente puede usar. Así, el patrón adaptador permite que las clases funcionen juntas si las interfaces son incompatibles. El patrón también se llama envolvente (wrapper) porque crea una clase de envoltorio con la interfaz necesaria.
¿Qué hace el patrón?
Podemos utilizar un patrón cuando la interfaz diseñada no cumple los requisitos de la interfaz de la aplicación para un tema concreto. El patrón convierte una interfaz de esta clase en otra para permitir que las clases funcionen juntas.
Los siguientes esquemas muestran la estructura del patrón Adaptador:
Como podemos ver en los esquemas, gracias al soporte de herencia múltiple, tenemos un adaptador de clase y un adaptador de objeto. También existe un objetivo que identifica una nueva interfaz específica del área temática que utiliza el cliente. Además, tenemos un cliente que participa con objetos adaptables a la interfaz de destino, un objeto adaptable que representa una interfaz heredada existente que debe hacerse adaptable, y un adaptador que hace que la interfaz del objeto adaptable sea adaptable a la interfaz de destino.
¿Qué problema resuelve el patrón?
- El uso de una clase existente con una interfaz que no coincide con la interfaz requerida.
- La creación de clases reutilizables que puedan funcionar con clases no relacionadas, independientemente de si esas clases tienen interfaces compatibles o incompatibles.
- La adaptación de la interfaz de una clase padre cuando se desea utilizar muchas subclases existentes.
¿Cómo usarlo en MQL5?
Veamos ahora como podemos usar este patrón (AdapterClass y ObjectClass) en MQL5. Vamos a desglosar el código paso a paso:
Utilizaremos una función de espacio de nombres y declararemos un área (AdapterClass) en la que definimos las funciones, variables y clases.
namespace AdapterClass
Utilizaremos la función de interfaz para declarar Target. Haciéndolo, definiremos una funcionalidad específica que puede ser implementada posteriormente por la clase.
interface Target { void Request(); };
Utilizaremos la función class para definir una interfaz de la clase Adaptee, que definirá una interfaz existente que se adaptará utilizando un único miembro público (SpecificRequest()).
class Adaptee { public: void SpecificRequest(); };
Luego mostraremos un mensaje cuando la solicitud sea ejecutada por la interfaz de la clase Adaptee
void Adaptee::SpecificRequest(void) { Print("A specific request is executing by the Adaptee"); }
Después declararemos una clase Adapter que adaptará la interfaz Adaptee del objeto Adaptee a las interfaces de objetivo Target que se heredarán del objeto Target y Adaptee como herencia múltiple.
class Adapter; class AdapterAsTarget:public Target { public: Adapter* asAdaptee; void Request(); }; void AdapterAsTarget::Request() { printf("The Adapter requested Operation"); asAdaptee.SpecificRequest(); } class Adapter:public Adaptee { public: AdapterAsTarget* asTarget; Adapter(); ~Adapter(); }; void Adapter::Adapter(void) { asTarget=new AdapterAsTarget; asTarget.asAdaptee=&this; } void Adapter::~Adapter(void) { delete asTarget; }
Declaración de la clase de cliente Client
class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; }
Inicio del cliente
void Client::Run()
{
Adapter adapter;
Target* target=adapter.asTarget;
target.Request();
}
A continuación le mostramos el código MQL5 completo de la clase Adapter en un solo bloque.
namespace AdapterClass { interface Target { void Request(); }; class Adaptee { public: void SpecificRequest(); }; void Adaptee::SpecificRequest(void) { Print("A specific request is executing by the Adaptee"); } class Adapter; class AdapterAsTarget:public Target { public: Adapter* asAdaptee; void Request(); }; void AdapterAsTarget::Request() { printf("The Adapter requested Operation"); asAdaptee.SpecificRequest(); } class Adapter:public Adaptee { public: AdapterAsTarget* asTarget; Adapter(); ~Adapter(); }; void Adapter::Adapter(void) { asTarget=new AdapterAsTarget; asTarget.asAdaptee=&this; } void Adapter::~Adapter(void) { delete asTarget; } class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; } void Client::Run() { Adapter adapter; Target* target=adapter.asTarget; target.Request(); } }
Más abajo podrá ver cómo utilizar el adaptador de objetos en MQL5:
Utilizaremos la función namespace, y declararemos el área en la que definiremos las funciones, variables y clases de AdapterObject.
namespace AdapterObject
Luego usaremos la interfaz para definir Target (el objeto objetivo de la interfaz).
interface Target { void Request(); };
Ahora crearemos una clase Adaptee para definir la interfaz existente que se va a adaptar.
class Adaptee { public: void SpecificRequest(); }; void Adaptee::SpecificRequest(void) { Print("The specific Request"); } class Adapter:public Target { public: void Request(); protected: Adaptee adaptee; }; void Adapter::Request(void) { Print("The request of Operation requested"); adaptee.SpecificRequest(); }
Declaración de la clase de cliente Client
class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; }
Inicio del cliente cuando los clientes inician operaciones en un ejemplar de adaptador
void Client::Run() { Target* target=new Adapter; target.Request(); delete target; }
El código completo será el siguiente:
namespace AdapterObject { interface Target { void Request(); }; class Adaptee { public: void SpecificRequest(); }; void Adaptee::SpecificRequest(void) { Print("The specific Request"); } class Adapter:public Target { public: void Request(); protected: Adaptee adaptee; }; void Adapter::Request(void) { Print("The request of Operation requested"); adaptee.SpecificRequest(); } class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; } void Client::Run() { Target* target=new Adapter; target.Request(); delete target; } }
Puente (Bridge)
En esta parte, analizaremos otro patrón de diseño estructural, el Puente (Bridge). La idea principal de uso de este patrón consiste en separar la abstracción de sus implementaciones para evitar futuros conflictos que puedan surgir si se dan actualizaciones o cambios en alguna de ellas. También se denomina Handle o Body.
¿Qué hace el patrón?
El patrón Puente se usa cuando hay una abstracción que tiene muchas implementaciones posibles. En lugar de utilizar la herencia normal, que siempre vincula una implementación a una abstracción, podemos utilizar este patrón y separar la abstracción de sus implementaciones para evitar el problema en caso de cambios o actualizaciones. Esta separación puede ayudar a crear un código limpio que pueda reutilizarse, ampliarse y probarse cómodamente.
La estructura del patrón Puente se muestra en el siguiente esquema:
El esquema Puente mostrado anteriormente tiene los siguientes elementos:
- Abstraction - abstracción, define una interfaz de abstracción y mantiene una referencia al objeto implementador.
- RefinedAbstraction - extiende la interfaz de abstracción.
- Implementor - implementación, identifica la interfaz de las clases de implementación.
- ConcreteImplementor - implementa una interfaz de desarrollador e identifica una implementación específica de esta.
¿Qué problema resuelve el patrón?
El patrón puente puede utilizarse siempre que lo necesitemos:
- Alejarnos del vínculo constante entre la abstracción y su implementación (separarlas utilizando un patrón).
- Combinar diferentes abstracciones e implementaciones y ampliar cada una de ellas de manera independiente y sin conflictos.
- Evitar el impacto en los clientes al cambiar la implementación de la abstracción.
- Ocultar por completo la implementación de la abstracción a los clientes.
¿Cómo usarlo en MQL5?
En esta parte, veremos cómo utilizar este patrón en MQL5 y crear aplicaciones más eficientes con su ayuda en términos de escritura de código. A continuación podemos ver como implementar la estructura del patrón Bridge en el código MQL5:
En primer lugar, crearemos un área de declaración para definir las variables, funciones y clases del patrón.
namespace Bridge
Después, crearemos la interfaz Implementor; aquí utilizaremos la palabra clave interface para definir la funcionalidad implementada por la clase.
interface Implementor { void OperationImp(); };
En el siguiente fragmento, crearemos una clase de abstracción con miembros abiertos y protegidos y almacenaremos una referencia (reference) al objeto desarrollador.
class Abstraction { public: virtual void Operation(); Abstraction(Implementor*); Abstraction(); ~Abstraction(); protected: Implementor* implementor; }; void Abstraction::Abstraction(void) {} void Abstraction::Abstraction(Implementor*i):implementor(i) {} void Abstraction::~Abstraction() { delete implementor; } void Abstraction::Operation() { implementor.OperationImp(); }
A continuación crearemos la clase RefinedAbstraction; en este ejemplo será un participante
class RefinedAbstraction:public Abstraction { public: RefinedAbstraction(Implementor*); void Operation(); }; void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {} void RefinedAbstraction::Operation() { Abstraction::Operation(); }
Crearemos las clases ConcreteImplementorA y ConcreteImplementorB
class ConcreteImplementorA:public Implementor { public: void OperationImp(); }; void ConcreteImplementorA::OperationImp(void) { Print("The implementor A"); } class ConcreteImplementorB:public Implementor { public: void OperationImp(); }; void ConcreteImplementorB::OperationImp(void) { Print("The implementor B"); }
Luego crearemos la clase de cliente Client
class Client { public: string Output(); void Run(); }; string Client::Output(void) { return __FUNCTION__; }
Inicio del cliente
void Client::Run(void) { Abstraction* abstraction; abstraction=new RefinedAbstraction(new ConcreteImplementorA); abstraction.Operation(); delete abstraction; abstraction=new RefinedAbstraction(new ConcreteImplementorB); abstraction.Operation(); delete abstraction; }
A continuación le mostramos el código de la estructura del patrón Bridge en su totalidad en un solo bloque.
namespace Bridge { interface Implementor { void OperationImp(); }; class Abstraction { public: virtual void Operation(); Abstraction(Implementor*); Abstraction(); ~Abstraction(); protected: Implementor* implementor; }; void Abstraction::Abstraction(void) {} void Abstraction::Abstraction(Implementor*i):implementor(i) {} void Abstraction::~Abstraction() { delete implementor; } void Abstraction::Operation() { implementor.OperationImp(); } class RefinedAbstraction:public Abstraction { public: RefinedAbstraction(Implementor*); void Operation(); }; void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {} void RefinedAbstraction::Operation() { Abstraction::Operation(); } class ConcreteImplementorA:public Implementor { public: void OperationImp(); }; void ConcreteImplementorA::OperationImp(void) { Print("The implementor A"); } class ConcreteImplementorB:public Implementor { public: void OperationImp(); }; void ConcreteImplementorB::OperationImp(void) { Print("The implementor B"); } class Client { public: string Output(); void Run(); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Abstraction* abstraction; abstraction=new RefinedAbstraction(new ConcreteImplementorA); abstraction.Operation(); delete abstraction; abstraction=new RefinedAbstraction(new ConcreteImplementorB); abstraction.Operation(); delete abstraction; } }
Compositor (Composite)
El Compositor (Composite) es otro tipo de patrón de diseño estructural. Este patrón permite combinar objetos en una estructura similar a la de un árbol y, de esta forma, proporcionar un procesamiento uniforme por parte del cliente de objetos y composiciones individuales.
¿Qué hace el patrón?
El patrón Compositor/Composite se utiliza cuando necesitamos componer objetos en estructuras de árbol. Por ello, el árbol será el elemento principal de este patrón. Dicho esto, si tenemos un componente que se representa como una estructura de árbol, tendremos dos elementos de ese componente en el patrón: la hoja Leaf, que en este caso solo realizará operaciones, y el componente del propio compositor Composite, que realizará un conjunto de operaciones como añadir, eliminar y llamar a un elemento hijo.
A continuación le mostramos un esquema de la estructura del patrón de diseño "Compositor":
Vamos a considerar los elementos presentes en el esquema anterior:
- Component - componente; declara una interfaz de objeto e implementa el comportamiento de interfaz por defecto para las clases, y posibilita el acceso y la gestión de los componentes de interfaz declarados para él.
- Leaf - hoja; representa los objetos de hoja en la composición, y esta hoja no tiene hijos, define el comportamiento de los objetos que pueden considerarse primitivos en la composición.
- Compositor - el compositor identifica el comportamiento de los componentes con los elementos hijos, almacena estos elementos hijos de los componentes e implementa las operaciones de los elementos hijos en la interfaz de los componentes.
- Cliente - a través de la interfaz del componente, el cliente manipula los objetos.
¿Qué problema resuelve el patrón?
Este patrón Composite puede utilizarse siempre que lo necesitemos:
- Representación de los objetos en una jerarquía de lo particular a lo global.
- Garantizar el mismo procesamiento del cliente para todos los objetos de la composición.
¿Cómo usarlo en MQL5?
Ahora veremos cómo se puede utilizar el patrón de compositor en MQL5. Vamos a desglosar el código paso a paso:
Luego crearemos un área Composite donde declararemos todas las funciones, variables y clases. Crearemos el área utilizando la palabra clave namespace.
namespace Composite
Crearemos la clase Component con miembros públicos y protegidos y acceso al elemento padre del componente.
class Component { public: virtual void Operation(void)=0; virtual void Add(Component*)=0; virtual void Remove(Component*)=0; virtual Component* GetChild(int)=0; Component(void); Component(string); protected: string name; }; Component::Component(void) {} Component::Component(string a_name):name(a_name) {}
A continuación en el fragmento, hemos decidido mostrar un error de usuario que se produce al añadir a una hoja y eliminar de la misma, o al crear una clase Leaf
#define ERR_INVALID_OPERATION_EXCEPTION 1 class Leaf:public Component { public: void Operation(void); void Add(Component*); void Remove(Component*); Component* GetChild(int); Leaf(string); }; void Leaf::Leaf(string a_name):Component(a_name) {} void Leaf::Operation(void) { Print(name); } void Leaf::Add(Component*) { SetUserError(ERR_INVALID_OPERATION_EXCEPTION); } void Leaf::Remove(Component*) { SetUserError(ERR_INVALID_OPERATION_EXCEPTION); } Component* Leaf::GetChild(int) { SetUserError(ERR_INVALID_OPERATION_EXCEPTION); return NULL; }
Creación de una clase Composite como participante. Trabajo, adición, eliminación de componentes y GetChild(int)
class Composite:public Component { public: void Operation(void); void Add(Component*); void Remove(Component*); Component* GetChild(int); Composite(string); ~Composite(void); protected: Component* nodes[]; }; Composite::Composite(string a_name):Component(a_name) {} Composite::~Composite(void) { int total=ArraySize(nodes); for(int i=0; i if (CheckPointer(i_node)==1) { delete i_node; } } } void Composite::Operation(void) { Print(name); int total=ArraySize(nodes); for(int i=0; i void Composite::Add(Component *src) { int size=ArraySize(nodes); ArrayResize(nodes,size+1); nodes[size]=src; } void Composite::Remove(Component *src) { int find=-1; int total=ArraySize(nodes); for(int i=0; i if (nodes[i]==src) { find=i; break; } } if(find>-1) { ArrayRemove(nodes,find,1); } } Component* Composite::GetChild(int i) { return nodes[i]; }
Creación de la clase Client como participante
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) {return __FUNCTION__;}
Inicio del cliente
void Client::Run(void) { Component* root=new Composite("root"); Component* branch1=new Composite("The branch 1"); Component* branch2=new Composite("The branch 2"); Component* leaf1=new Leaf("The leaf 1"); Component* leaf2=new Leaf("The leaf 2"); root.Add(branch1); root.Add(branch2); branch1.Add(leaf1); branch1.Add(leaf2); branch2.Add(leaf2); branch2.Add(new Leaf("The leaf 3")); Print("The tree"); root.Operation(); root.Remove(branch1); Print("Removing one branch"); root.Operation(); delete root; delete branch1; }
Decorador (Decorator)
El patrón de diseño estructural Decorator permite generar estructuras más grandes para objetos creados o existentes. Este patrón puede usarse para añadir características, comportamientos o capacidades adicionales a un objeto en un método dinámico en el tiempo de ejecución. Así, el patrón ofrece una alternativa flexible a la creación de subclases. La clase de este patrón también se conoce como Wrapper.
¿Qué hace el patrón?
Por ello, el patrón permite añadir funciones a cualquier objeto individual sin hacerlo en toda la clase. Esto se hace usando un envoltorio en lugar de utilizar el método de creación de subclases.
A continuación le mostramos un esquema de la estructura del patrón de diseño "Decorador":
El esquema muestra que en este patrón intervienen los siguientes elementos:
- Component - define la interfaz de los objetos, y también que poseen funciones adicionales de forma dinámica.
- ConcreteComponent - determina qué objeto puede imponer responsabilidades adicionales.
- Decorator - permite almacenar una referencia a un objeto de componente y definir una interfaz que se corresponda con la interfaz del componente.
- ConcreteDecorator - es responsable de añadir funciones al componente.
¿Qué problema resuelve el patrón?
El patrón Decorador puede utilizarse siempre que necesitemos:
- Añadir de forma dinámica y transparente responsabilidades adicionales a objetos individuales sin que ello afecte a otros objetos.
- Quitar responsabilidades a los objetos.
- Cuando utilizar el método de creación de subclases resulta poco práctico para la extensión.
¿Cómo usarlo en MQL5?
Para implementar el patrón Decorator en código MQL5 y luego utilizarlo para escribir programas, realizaremos los siguientes pasos:
Una vez más, empezaremos creando el área de declaración de nuestro Decorador, dentro de la cual declararemos todo lo que necesitemos.
namespace Decorator
Crearemos una clase Component con un miembro público para definir la interfaz de los objetos.
class Component { public: virtual void Operation(void)=0; };
Luego crearemos la clase Decorator como participante
class Decorator:public Component { public: Component* component; void Operation(void); }; void Decorator::Operation(void) { if(CheckPointer(component)>0) { component.Operation(); } }
Y crearemos la clase ConcreteComponent como participante
class ConcreteComponent:public Component { public: void Operation(void); }; void ConcreteComponent::Operation(void) { Print("The concrete operation"); }
Creación de ConcreteDecoratorA y ConcreteDecoratorB
class ConcreteDecoratorA:public Decorator { protected: string added_state; public: ConcreteDecoratorA(void); void Operation(void); }; ConcreteDecoratorA::ConcreteDecoratorA(void): added_state("The added state()") { } void ConcreteDecoratorA::Operation(void) { Decorator::Operation(); Print(added_state); } class ConcreteDecoratorB:public Decorator { public: void AddedBehavior(void); void Operation(void); }; void ConcreteDecoratorB::AddedBehavior(void) { Print("The added behavior()"); } void ConcreteDecoratorB::Operation(void) { Decorator::Operation(); AddedBehavior(); }
Luego crearemos la clase de cliente Client
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; }
Inicio del cliente
void Client::Run(void) { Component* component=new ConcreteComponent(); Decorator* decorator_a=new ConcreteDecoratorA(); Decorator* decorator_b=new ConcreteDecoratorB(); decorator_a.component=component; decorator_b.component=decorator_a; decorator_b.Operation(); delete component; delete decorator_a; delete decorator_b; }
A continuación hemos reunido todo el código del patrón en su totalidad en un solo bloque
namespace Decorator { class Component { public: virtual void Operation(void)=0; }; class Decorator:public Component { public: Component* component; void Operation(void); }; void Decorator::Operation(void) { if(CheckPointer(component)>0) { component.Operation(); } } class ConcreteComponent:public Component { public: void Operation(void); }; void ConcreteComponent::Operation(void) { Print("The concrete operation"); } class ConcreteDecoratorA:public Decorator { protected: string added_state; public: ConcreteDecoratorA(void); void Operation(void); }; ConcreteDecoratorA::ConcreteDecoratorA(void): added_state("The added state()") { } void ConcreteDecoratorA::Operation(void) { Decorator::Operation(); Print(added_state); } class ConcreteDecoratorB:public Decorator { public: void AddedBehavior(void); void Operation(void); }; void ConcreteDecoratorB::AddedBehavior(void) { Print("The added behavior()"); } void ConcreteDecoratorB::Operation(void) { Decorator::Operation(); AddedBehavior(); } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Component* component=new ConcreteComponent(); Decorator* decorator_a=new ConcreteDecoratorA(); Decorator* decorator_b=new ConcreteDecoratorB(); decorator_a.component=component; decorator_b.component=decorator_a; decorator_b.Operation(); delete component; delete decorator_a; delete decorator_b; } }
Fachada (Facade)
Facade es otro patrón estructural que puede utilizarse en el desarrollo de software para crear otras estructuras mayores. Identifica una interfaz de nivel superior que facilita y agiliza el uso de los subsistemas.
¿Qué hace el patrón?
El uso del patrón Facade puede considerarse una forma de ocultar al Cliente la complejidad del subsistema, ya que proporciona una interfaz unificada para un conjunto de interfaces del subsistema. Así, el cliente interactuará con esta interfaz unificada para obtener lo que solicita, y sin embargo esta misma interfaz interactuará con el subsistema para retornar lo que el cliente ha solicitado.
Echemos un vistazo al esquema que muestra la estructura del funcionamiento del patrón de diseño Facade:
Como podemos ver en el esquema, la estructura del patrón consta de los siguientes elementos:
- Facade - sabe qué subsistema puede solicitar, y delega las solicitudes de los clientes a los objetos del subsistema correspondientes.
- Clases de subsistema - realizan las funciones de un subsistema; cuando reciben una petición de Fachada la procesan, no tienen referencias a Fachada.
¿Qué problema resuelve el patrón?
El patrón de Fachada puede utilizarse cuando necesitamos:
- Simplificar la complejidad del subsistema ofreciendo una interfaz sencilla.
- Separar el subsistema de los clientes y de otros subsistemas para cambiar las dependencias existentes entre los clientes y las implementaciones de las clases de abstracción, al tiempo que el subsistema resulta independiente y portátil.
- Identificar los puntos de entrada a cada nivel del subsistema representándolos jerárquicamente.
¿Cómo usarlo en MQL5?
Para implementar el patrón de Fachada en código MQL5 y luego utilizarlo para escribir programas, ejecutaremos los siguientes pasos:
Crearemos el espacio Facade para declarar todo lo que necesitamos.
namespace Facade
Declaramos las clases SubSystemA, SubSystemB y SubSystemC
class SubSystemA { public: void Operation(void); }; void SubSystemA::Operation(void) { Print("The operation of the subsystem A"); } class SubSystemB { public: void Operation(void); }; void SubSystemB::Operation(void) { Print("The operation of the subsystem B"); } class SubSystemC { public: void Operation(void); }; void SubSystemC::Operation(void) { Print("The operation of the subsystem C"); }
Declaración de la clase de fachada Facade
class Facade { public: void Operation_A_B(void); void Operation_B_C(void); protected: SubSystemA subsystem_a; SubSystemB subsystem_b; SubSystemC subsystem_c; }; void Facade::Operation_A_B(void) { Print("The facade of the operation of A & B"); Print("The request of the facade of the subsystem A operation"); subsystem_a.Operation(); Print("The request of the facade of the subsystem B operation"); subsystem_b.Operation(); } void Facade::Operation_B_C(void) { Print("The facade of the operation of B & C"); Print("The request of the facade of the subsystem B operation"); subsystem_b.Operation(); Print("The request of the facade of the subsystem C operation"); subsystem_c.Operation(); }
Declaración de la clase de cliente Client
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; }
Inicio del cliente
void Client::Run(void) { Facade facade; Print("The request of client of the facade operation A & B"); facade.Operation_A_B(); Print("The request of client of the facade operation B & C"); facade.Operation_B_C(); }
A continuación le mostramos todo el código en un solo bloque:
namespace Facade { class SubSystemA { public: void Operation(void); }; void SubSystemA::Operation(void) { Print("The operation of the subsystem A"); } class SubSystemB { public: void Operation(void); }; void SubSystemB::Operation(void) { Print("The operation of the subsystem B"); } class SubSystemC { public: void Operation(void); }; void SubSystemC::Operation(void) { Print("The operation of the subsystem C"); } class Facade { public: void Operation_A_B(void); void Operation_B_C(void); protected: SubSystemA subsystem_a; SubSystemB subsystem_b; SubSystemC subsystem_c; }; void Facade::Operation_A_B(void) { Print("The facade of the operation of A & B"); Print("The request of the facade of the subsystem A operation"); subsystem_a.Operation(); Print("The request of the facade of the subsystem B operation"); subsystem_b.Operation(); } void Facade::Operation_B_C(void) { Print("The facade of the operation of B & C"); Print("The request of the facade of the subsystem B operation"); subsystem_b.Operation(); Print("The request of the facade of the subsystem C operation"); subsystem_c.Operation(); } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Facade facade; Print("The request of client of the facade operation A & B"); facade.Operation_A_B(); Print("The request of client of the facade operation B & C"); facade.Operation_B_C(); } }
Peso mosca (Flyweight)
El patrón estructural Peso Mosca (Flyweight) se utiliza para reducir costes cuando tratamos con un gran número de objetos pequeños, ya que para soportarlo luego se utiliza el uso conjunto.
¿Qué hace el patrón?
El uso conjunto para el soporte puede resultar útil desde el punto de vista de la memoria, por eso en el original se llama Flyweight (Peso Mosca).
La estructura del patrón Flyweight se muestra en el siguiente esquema:
Vamos a considerar los elementos presentes en el esquema anterior:
- Flyweight.
- ConcreteFlyweight.
- UnsharedConcreteFlyweight.
- FlyweightFactory.
- Client.
¿Qué problema resuelve el patrón?
Este patrón puede utilizarse cuando:
- En la aplicación se usa un gran número de objetos.
- Necesitaremos reducir los altos costes de almacenamiento.
- Gran parte del estado de un objeto puede implementarse como externo.
- Si eliminamos el estado externo, muchos grupos de objetos podrán sustituirse por menos objetos comunes.
- La identidad del objeto no resulta tan importante para la aplicación desde el punto de vista de la dependencia.
¿Cómo usarlo en MQL5?
Ahora vamos a escribir el código MQL5 de este patrón:
Comenzaremos creando el área de declaración de nuestro patrón Flyweight, dentro del cual declararemos todo lo que necesitemos.
namespace Flyweight
Para ello, utilizaremos la palabra clave interface y declararemos Flyweight
interface Flyweight;
Crearemos la clase Pair con miembros protegidos y públicos como participantes
class Pair { protected: string key; Flyweight* value; public: Pair(void); Pair(string,Flyweight*); ~Pair(void); Flyweight* Value(void); string Key(void); }; Pair::Pair(void){} Pair::Pair(string a_key,Flyweight *a_value): key(a_key), value(a_value){} Pair::~Pair(void) { delete value; } string Pair::Key(void) { return key; } Flyweight* Pair::Value(void) { return value; }
Luego crearemos la clase Reference y definiremos su constructor y destructor.
class Reference { protected: Pair* pairs[]; public: Reference(void); ~Reference(void); void Add(string,Flyweight*); bool Has(string); Flyweight* operator[](string); protected: int Find(string); }; Reference::Reference(void){} Reference::~Reference(void) { int total=ArraySize(pairs); for(int i=0; i if (CheckPointer(ipair)) { delete ipair; } } } int Reference::Find(string key) { int total=ArraySize(pairs); for(int i=0; i if (ipair.Key()==key) { return i; } } return -1; } bool Reference::Has(string key) { return (Find(key)>-1)?true:false; } void Reference::Add(string key,Flyweight *value) { int size=ArraySize(pairs); ArrayResize(pairs,size+1); pairs[size]=new Pair(key,value); } Flyweight* Reference::operator[](string key) { int find=Find(key); return (find>-1)?pairs[find].Value():NULL; }
Después declararemos una interfaz Flyweight para influir en el estado externo
interface Flyweight { void Operation(int extrinsic_state); };
A continuación, declararemos la clase ConcreteFlyweight
class ConcreteFlyweight:public Flyweight { public: void Operation(int extrinsic_state); protected: int intrinsic_state; }; void ConcreteFlyweight::Operation(int extrinsic_state) { intrinsic_state=extrinsic_state; printf("The intrinsic state - %d",intrinsic_state); }
Luego declararemos la clase UnsharedConcreteFlyweight
class UnsharedConcreteFlyweight:public Flyweight { protected: int all_state; public: void Operation(int extrinsic_state); }; void UnsharedConcreteFlyweight::Operation(int extrinsic_state) { all_state=extrinsic_state; Print("all state - %d",all_state); }
Y declararemos la clase FlyweightFactory
class FlyweightFactory { protected: Reference pool; public: FlyweightFactory(void); Flyweight* Flyweight(string key); }; FlyweightFactory::FlyweightFactory(void) { pool.Add("1",new ConcreteFlyweight); pool.Add("2",new ConcreteFlyweight); pool.Add("3",new ConcreteFlyweight); } Flyweight* FlyweightFactory::Flyweight(string key) { if(!pool.Has(key)) { pool.Add(key,new ConcreteFlyweight()); } return pool[key]; }
Declaración de la clase de cliente Client
class Client { public: string Output(); void Run(); }; string Client::Output(void) { return __FUNCTION__; }
Inicio del cliente
void Client::Run(void) { int extrinsic_state=7; Flyweight* flyweight; FlyweightFactory factory; flyweight=factory.Flyweight("1"); flyweight.Operation(extrinsic_state); flyweight=factory.Flyweight("10"); flyweight.Operation(extrinsic_state); flyweight=new UnsharedConcreteFlyweight(); flyweight.Operation(extrinsic_state); delete flyweight; }
A continuación le mostramos el código completo en un bloque:
namespace Flyweight { interface Flyweight; class Pair { protected: string key; Flyweight* value; public: Pair(void); Pair(string,Flyweight*); ~Pair(void); Flyweight* Value(void); string Key(void); }; Pair::Pair(void){} Pair::Pair(string a_key,Flyweight *a_value): key(a_key), value(a_value){} Pair::~Pair(void) { delete value; } string Pair::Key(void) { return key; } Flyweight* Pair::Value(void) { return value; } class Reference { protected: Pair* pairs[]; public: Reference(void); ~Reference(void); void Add(string,Flyweight*); bool Has(string); Flyweight* operator[](string); protected: int Find(string); }; Reference::Reference(void){} Reference::~Reference(void) { int total=ArraySize(pairs); for(int i=0; i if (CheckPointer(ipair)) { delete ipair; } } } int Reference::Find(string key) { int total=ArraySize(pairs); for(int i=0; i if (ipair.Key()==key) { return i; } } return -1; } bool Reference::Has(string key) { return (Find(key)>-1)?true:false; } void Reference::Add(string key,Flyweight *value) { int size=ArraySize(pairs); ArrayResize(pairs,size+1); pairs[size]=new Pair(key,value); } Flyweight* Reference::operator[](string key) { int find=Find(key); return (find>-1)?pairs[find].Value():NULL; } interface Flyweight { void Operation(int extrinsic_state); }; class ConcreteFlyweight:public Flyweight { public: void Operation(int extrinsic_state); protected: int intrinsic_state; }; void ConcreteFlyweight::Operation(int extrinsic_state) { intrinsic_state=extrinsic_state; Print("The intrinsic state - %d",intrinsic_state); } class UnsharedConcreteFlyweight:public Flyweight { protected: int all_state; public: void Operation(int extrinsic_state); }; void UnsharedConcreteFlyweight::Operation(int extrinsic_state) { all_state=extrinsic_state; Print("all state - %d",all_state); } class FlyweightFactory { protected: Reference pool; public: FlyweightFactory(void); Flyweight* Flyweight(string key); }; FlyweightFactory::FlyweightFactory(void) { pool.Add("1",new ConcreteFlyweight); pool.Add("2",new ConcreteFlyweight); pool.Add("3",new ConcreteFlyweight); } Flyweight* FlyweightFactory::Flyweight(string key) { if(!pool.Has(key)) { pool.Add(key,new ConcreteFlyweight()); } return pool[key]; } class Client { public: string Output(); void Run(); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { int extrinsic_state=7; Flyweight* flyweight; FlyweightFactory factory; flyweight=factory.Flyweight("1"); flyweight.Operation(extrinsic_state); flyweight=factory.Flyweight("10"); flyweight.Operation(extrinsic_state); flyweight=new UnsharedConcreteFlyweight(); flyweight.Operation(extrinsic_state); delete flyweight; } }
Proxy
Hemos llegado al último patrón de los tipos de patrones de diseño estructural, el patrón Proxy. Este patrón tiene muchos tipos, desde el punto de vista de la representatividad. En general, podemos decir que Proxy puede ser utilizado para presentar una alternativa o sustituto de otro objeto para el control completo en cuanto al acceso a ese objeto. La clase de este patrón también se conoce como Surrogate.
¿Qué hace el patrón?
Este patrón proporciona un sustituto para controlar el acceso a un objeto.
A continuación le mostramos un esquema de la estructura del patrón de diseño "Proxy":
Vamos a considerar los elementos presentes en el esquema anterior:
- Proxy.
- Subject.
- Real subject.
¿Qué problema resuelve el patrón?
El patrón Proxy es adecuado para las siguientes situaciones:- Si necesitamos un representante local de un objeto en otro espacio de direcciones, podemos utilizar un Proxy remoto que lo represente.
- Si necesitamos un objeto muy exigente y costoso, podemos utilizar un proxy virtual que cree estos objetos.
- Si necesitamos controlar el acceso al objeto principal o fuente, podemos utilizar un proxy de seguridad.
- Si necesitamos sustituir un puntero simple, podemos utilizar una referencia inteligente.
¿Cómo usarlo en MQL5?
Para implementar el patrón Proxy en código MQL5 y luego utilizarlo para escribir programas, realizaremos los siguientes pasos:
Primero declararemos el espacio Proxy para declarar todo lo que necesitemos en él en cuanto a variables, funciones, clases, etc.
namespace Proxy
Luego declararemos la clase de sujeto como participante
class Subject { public: virtual void Request(void)=0; };
Creación de la clase RealSubject
class RealSubject:public Subject { public: void Request(void); }; void RealSubject::Request(void) { Print("The real subject"); }
Creación de una clase Proxy como participante
class Proxy:public Subject { protected: RealSubject* real_subject; public: ~Proxy(void); void Request(void); }; Proxy::~Proxy(void) { delete real_subject; } void Proxy::Request(void) { if(!CheckPointer(real_subject)) { real_subject=new RealSubject; } real_subject.Request(); }
Declaración de la clase de cliente Client
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; }
Inicio del cliente
void Client::Run(void) { Subject* subject=new Proxy; subject.Request(); delete subject; }
A continuación le mostramos el código completo en un bloque:
namespace Proxy { class Subject { public: virtual void Request(void)=0; }; class RealSubject:public Subject { public: void Request(void); }; void RealSubject::Request(void) { Print("The real subject"); } class Proxy:public Subject { protected: RealSubject* real_subject; public: ~Proxy(void); void Request(void); }; Proxy::~Proxy(void) { delete real_subject; } void Proxy::Request(void) { if(!CheckPointer(real_subject)) { real_subject=new RealSubject; } real_subject.Request(); } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Subject* subject=new Proxy; subject.Request(); delete subject; } }
Conclusión
En este artículo, hemos expuesto una sencilla introducción al tema de los patrones de diseño estructurales. Asimismo, hemos definido los tipos de patrones estructurales y hemos aprendido a escribir código limpio que pueda reutilizarse, ampliarse y probarse repetidamente. Para cada patrón, hemos entendido los puntos principales: la esencia del patrón, el propósito, la estructura y qué problemas de diseño resuelve.
Además, hemos considerado los siguientes patrones de diseño estructurales:
- Adaptador (Adapter)
- Puente (Bridge)
- Compositor (Composite)
- Decorador (Decorator)
- Fachada (Facade)
- Peso mosca (Flyweight)
- Proxy
Como ya hemos comentado en la Parte 1, los desarrolladores se beneficiarán de los conocimientos sobre los patrones de diseño, ya que ahorran mucho tiempo y eliminan la necesidad de reinventar la rueda. Para ello, estos usan soluciones predefinidas, probadas y prácticas para problemas concretos. Con un conocimiento suficiente de la programación orientada a objetos, podemos empezar a trabajar eficazmente con patrones de diseño.
Para aprender aún más sobre el tema de los patrones, le recomiendo leer los siguientes materiales:
- Design Patterns - Elements of Reusable Object-Oriented Software by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides
- Design Patterns for Dummies by Steve Holzner
- Head First Design Patterns by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra
Espero que este artículo le haya resultado útil y haya encontrado algo nuevo para usted en el campo de la escritura de software. Quizá estos conocimientos le ayuden a crear programas más eficaces en MQL5. Si le ha gustado este artículo, lea también el resto de contenidos. Los enlaces siguientes le llevarán a mis otros artículos. Concretamente, encontrará una serie de artículos sobre la creación de sistemas comerciales basados en los indicadores técnicos más populares como RSI, MACD, Bollinger Bands, Moving averages, Stochastics y otros. Espero que le resulten útiles y le ayuden a llevar su desarrollo de software y su comercio al siguiente nivel.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13724





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso