Design-Patterns-Es Libre de Culpas
Design-Patterns-Es Libre de Culpas
Design-Patterns-Es Libre de Culpas
Sumérggete en lo
loss
PATR
TRONE
ONESS
DE
DI
DISEÑO
SEÑO
v2020-1.6
Unas palabras sobre
derechos de autor
¡Hola! Mi nombre es Alexander Shvets.
Soy el autor del libro Sumérgete en los
patrones de diseño y del curso online
Dive Into Refactoring.
Índice de contenido
Índice de contenido............................................................................................... 4
Cómo leer este libro.............................................................................................. 6
FUNDAMENTOS DE LA POO .................................................................................. 7
Conceptos básicos de POO ............................................................... 8
Los pilares de la POO ...................................................................... 13
Relaciones entre objetos................................................................ 21
INTRODUCCIÓN A LOS PATRONES DE DISEÑO............................................... 27
¿Qué es un patrón de diseño?...................................................... 28
¿Por qué debería aprender sobre patrones? ........................... 33
PRINCIPIOS DE DISEÑO DE SOFTWARE .......................................................... 34
Características del buen diseño................................................... 35
Principios del diseño..................................................................................... 39
§ Encapsula lo que varía ................................................................ 40
§ Programa a una interfaz, no a una implementación ........ 45
§ Favorece la composición sobre la herencia......................... 50
Principios SOLID ............................................................................................ 54
§ S: Principio de responsabilidad única.................................... 55
§ O: Principio de abierto/cerrado................................................ 57
§ L: Principio de sustitución de Liskov...................................... 61
§ I: Principio de segregación de la interfaz ............................. 68
§ D: Principio de inversión de la dependencia....................... 71
5 Índice de contenido
Objetos, clases
¿Te gustan los gatos? Espero que sí, porque voy a intentar
explicar los conceptos de POO utilizando varios ejemplos
con gatos.
Jerarquías de clase
Todo va muy bien mientras hablamos de una sola clase. Natu-
ralmente, un programa real contiene más de una clase. Algu-
nas de esas clases pueden estar organizadas en jerarquías de
clase. Veamos lo que esto significa.
11 Fundamentos de la POO / Conceptos básicos de POO
Abstracción
La mayoría de las veces, cuando creas un programa con POO,
das forma a los objetos del programa con base a objetos del
mundo real. Sin embargo, los objetos del programa no repre-
sentan a los originales con una precisión del 100 % (y rara vez
es necesario que lo hagan). En su lugar, tus objetos tan solo co-
pian atributos y comportamientos de objetos reales en un con-
texto específico, ignorando el resto.
Encapsulación
Para arrancar el motor de un auto, tan solo debes girar una
llave o pulsar un botón. No necesitas conectar cables bajo el
capó, rotar el cigüeñal y los cilindros, e iniciar el ciclo de po-
15 Fundamentos de la POO / Los pilares de la POO
Herencia
La herencia es la capacidad de crear nuevas clases sobre otras
existentes. La principal ventaja de la herencia es la reutiliza-
ción de código. Si quieres crear una clase ligeramente diferen-
te a una ya existente, no hay necesidad de duplicar el código.
En su lugar, extiendes la clase existente y colocas la funciona-
lidad adicional dentro de una subclase resultante que hereda
los campos y métodos de la superclase.
Polimorfismo
Veamos algunos ejemplos con animales. La mayoría de los
Animal puede emitir sonidos. Podemos anticipar que todas
1 bag = [new
new Cat(), new Dog()];
2
3 foreach (Animal a : bag)
4 a.makeSound()
5
6 // ¡Miau!
7 // ¡Guau!
Dependencia
Asociación
1 class Professor is
2 field Student student
3 // ...
4 method teach(Course c) is
5 // ...
6 this
this.student.remember(c.getKnowledge())
asociación.
24 Fundamentos de la POO / Relaciones entre objetos
Agregación
Composición
La visión global
Ahora que conocemos todos los tipos de relaciones entre ob-
jetos, veamos cómo se conectan entre sí. Esperemos que esto
te ayude a responder preguntas como: “¿cuál es la diferencia
entre agregación y composición?” o “¿la herencia es un tipo de
dependencia?”.
26 Fundamentos de la POO / Relaciones entre objetos
Los patrones más universales y de más alto nivel son los patro-
nes de arquitectura. Los desarrolladores pueden implementar
estos patrones prácticamente en cualquier lenguaje. Al contra-
rio que otros patrones, pueden utilizarse para diseñar la arqui-
tectura de una aplicación completa.
Reutilización de código
Costos y tiempo son dos de los parámetros más valiosos a la
hora de desarrollar cualquier producto de software. Dedicar
menos tiempo al desarrollo se traduce en entrar en el mercado
antes que la competencia. Unos costos más bajos en el desa-
rrollo significa que habrá más dinero disponible para marke-
ting y un alcance más amplio a clientes potenciales.
1
Aquí tienes un apunte de sabiduría de Erich Gamma , uno
de los padres fundadores de los patrones de diseño, sobre el
papel que estos juegan en la reutilización de código:
„
zar ideas y conceptos de diseño con independencia del código
concreto.
Extensibilidad
El cambio es lo único constante en la vida de un programador.
Del mismo modo, puedes aislar las partes del programa que
varían, en módulos independientes, protegiendo el resto del
código frente a efectos adversos. Al hacerlo, dedicarás menos
tiempo a lograr que el programa vuelva a funcionar, imple-
mentando y probando los cambios. Cuanto menos tiempo de-
diques a realizar cambios, más tiempo tendrás para
implementar funciones.
41 Principios del diseño / Encapsula lo que varía
puestos incluidos.
1 method getOrderTotal(order) is
2 total = 0
3 foreach item in order.lineItems
4 total += item.price * item.quantity
5
6 if (order.country == "US")
7 total += total * 0.07 // Impuesto sobre la venta de EUA
8 else if (order.country == "EU"):
9 total += total * 0.20 // IVA europeo
10
11 return total
ANTES: el código de cálculo del impuesto está mezclado con el resto del
código del método.
42 Principios del diseño / Encapsula lo que varía
1 method getOrderTotal(order) is
2 total = 0
3 foreach item in order.lineItems
4 total += item.price * item.quantity
5
6 total += total * getTaxRate(order.country)
7
8 return total
9
10 method getTaxRate(country) is
11 if (country == "US")
12 return 0.07 // Impuesto sobre la venta de EUA
13 else if (country == "EU")
14 return 0.20 // IVA europeo
15 else
16 return 0
Ejemplo
Veamos otro ejemplo que ilustra que trabajar con objetos a
través de interfaces puede ser más beneficioso que depender
de sus clases concretas. Imagina que estás creando un simu-
lador de empresa de desarrollo de software. Tienes distintas
clases que representan varios tipos de empleados.
Ejemplo
Imagina que debes crear una aplicación de un catálogo para
un fabricante de automóviles. La empresa fabrica autos y ca-
miones; pueden ser eléctricos o de gasolina; todos los mode-
los pueden tener controles manuales o piloto automático.
Principios SOLID
Ahora que conoces los principios básicos de diseño, veamos
cinco que se conocen popularmente como los principios
SOLID. Robert Martin los presentó en el libro Desarrollo ágil de
1
software: principios, patrones y prácticas .
Hay más: si una clase hace demasiadas cosas, tienes que cambiarla
cada vez que una de esas cosas cambia. Al hacerlo, te arriesgas a
descomponer otras partes de la clase que no pretendías cambiar. Si
sientes que te resulta difícil centrarte en un aspecto específico del
programa cada vez, recuerda el principio de responsabilidad única
y comprueba si es el momento de dividir algunas clases en partes.
56 Principios SOLID / S: Principio de responsabilidad única
Ejemplo
La clase Empleado tiene varias razones para cambiar. La pri-
mera razón puede estar relacionada con el trabajo principal
de la clase: gestionar información de los empleados. Pero hay
otra razón: el formato del informe de horas de trabajo puede
cambiar con el tiempo, lo que te obliga a cambiar el código de
la clase.
O pen/Closed Principle
Principio de abierto/cerrado
Ejemplo
Tienes una aplicación de comercio electrónico con una clase
Pedido que calcula los costos de envío, y todos los métodos
◦ Digamos que hay una clase con un método que debe ali-
mentar gatos: alimentar(Gato c) . El código cliente siem-
pre pasa objetos de gatos a este método.
◦ Digamos que
tienes una clase con el método
comprarGato(): Gato . El código cliente espera recibir cual-
Ejemplo
Veamos un ejemplo de una jerarquía de clases de documento
que violan el principio de sustitución.
Ejemplo
Imagina que creaste una biblioteca que facilita la integración de
aplicaciones con varios proveedores de computación en la nube.
Aunque en la versión inicial sólo soportaba Amazon Cloud, cubría
todos los servicios y funciones de la nube.
3. Una vez que las clases de bajo nivel implementan esas interfa-
ces, se vuelven dependientes del nivel de la lógica de negocio,
invirtiendo la dirección de la dependencia original.
Ejemplo
En este ejemplo, la clase de alto nivel — que se ocupa de in-
formes presupuestarios — utiliza una clase de base de datos
de bajo nivel para leer y almacenar su información. Esto sig-
nifica que cualquier cambio en la clase de bajo nivel, como
en el caso del lanzamiento de una nueva versión del servi-
73 Principios SOLID / D: Principio de inversión de la dependencia
ANTES: una clase de alto nivel depende de una clase de bajo nivel.
Patrones creacionales
Los patrones creacionales proporcionan varios mecanismos de
creación de objetos que incrementan la flexibilidad y la reuti-
lización del código existente.
Factory
Method
Proporciona una interfaz para la creación de objetos en una su-
perclase, mientras permite a las subclases alterar el tipo de obje-
tos que se crearán.
Abstract
Factory
Permite producir familias de objetos relacionados sin especificar
sus clases concretas.
77 Patrones creacionales
Builder
Permite construir objetos complejos paso a paso. Este patrón nos
permite producir distintos tipos y representaciones de un objeto
empleando el mismo código de construcción.
Prototype
Permite copiar objetos existentes sin que el código dependa de
sus clases.
Singleton
Permite asegurarnos de que una clase tenga una única instancia,
a la vez que proporciona un punto de acceso global a dicha ins-
tancia.
78 Patrones creacionales / Factory Method
FACTORY METHOD
También llamado: Método fábrica, Constructor virtual
Problema
Imagina que estás creando una aplicación de gestión logística.
La primera versión de tu aplicación sólo es capaz de manejar
el transporte en camión, por lo que la mayor parte de tu códi-
go se encuentra dentro de la clase Camión .
Solución
El patrón Factory Method sugiere que, en lugar de llamar al
operador new para construir objetos directamente, se invo-
que a un método fábrica especial. No te preocupes: los objetos
se siguen creando a través del operador new , pero se invocan
desde el método fábrica. Los objetos devueltos por el método
fábrica a menudo se denominan productos.
Estructura
Pseudocódigo
Este ejemplo ilustra cómo puede utilizarse el patrón Factory
Method para crear elementos de interfaz de usuario (UI) multi-
plataforma sin acoplar el código cliente a clases UI concretas.
85 Patrones creacionales / Factory Method
Aplicabilidad
Utiliza el Método Fábrica cuando no conozcas de antemano las
dependencias y los tipos exactos de los objetos con los que
deba funcionar tu código.
Cómo implementarlo
1. Haz que todos los productos sigan la misma interfaz. Esta in-
terfaz deberá declarar métodos que tengan sentido en todos
los productos.
92 Patrones creacionales / Factory Method
Pros y contras
Evitas un acoplamiento fuerte entre el creador y los productos
concretos.
Principio de responsabilidad única. Puedes mover el código de
creación de producto a un lugar del programa, haciendo que
el código sea más fácil de mantener.
Principio de abierto/cerrado. Puedes incorporar nuevos tipos de
productos en el programa sin descomponer el código cliente
existente.
ABSTRACT FACTORY
También llamado: Fábrica abstracta
Problema
Imagina que estás creando un simulador de tienda de mue-
bles. Tu código está compuesto por clases que representan lo
siguiente:
Solución
Lo primero que sugiere el patrón Abstract Factory es que de-
claremos de forma explícita interfaces para cada producto di-
ferente de la familia de productos (por ejemplo, silla, sofá o
mesilla). Después podemos hacer que todas las variantes de
los productos sigan esas interfaces. Por ejemplo, todas las va-
riantes de silla pueden implementar la interfaz Silla , así
98 Patrones creacionales / Abstract Factory
Todas las variantes del mismo objeto deben moverse a una única jerarquía
de clase.
Estructura
Pseudocódigo
Este ejemplo ilustra cómo puede utilizarse el patrón Abstract
Factory para crear elementos de interfaz de usuario (UI) multi-
plataforma sin acoplar el código cliente a clases UI concretas,
mientras se mantiene la consistencia de todos los elementos
creados respecto al sistema operativo seleccionado.
103 Patrones creacionales / Abstract Factory
10 method createCheckbox():Checkbox
11
12
13 // Las fábricas concretas producen una familia de productos que
14 // pertenecen a una única variante. La fábrica garantiza que los
15 // productos resultantes sean compatibles. Las firmas de los
16 // métodos de las fábricas concretas devuelven un producto
17 // abstracto mientras que dentro del método se instancia un
18 // producto concreto.
19 class WinFactory implements GUIFactory is
20 method createButton():Button is
21 return new WinButton()
22 method createCheckbox():Checkbox is
23 return new WinCheckbox()
24
25 // Cada fábrica concreta tiene una variante de producto
26 // correspondiente.
27 class MacFactory implements GUIFactory is
28 method createButton():Button is
29 return new MacButton()
30 method createCheckbox():Checkbox is
31 return new MacCheckbox()
32
33
34 // Cada producto individual de una familia de productos debe
35 // tener una interfaz base. Todas las variantes del producto
36 // deben implementar esta interfaz.
37 interface Button is
38 method paint()
39
40 // Los productos concretos son creados por las fábricas
41 // concretas correspondientes.
106 Patrones creacionales / Abstract Factory
74 this
this.factory = factory
75 method createUI() is
76 this
this.button = factory.createButton()
77 method paint() is
78 button.paint()
79
80
81 // La aplicación elige el tipo de fábrica dependiendo de la
82 // configuración actual o de los ajustes del entorno y la crea
83 // durante el tiempo de ejecución (normalmente en la etapa de
84 // inicialización).
85 class ApplicationConfigurator is
86 method main() is
87 config = readApplicationConfigFile()
88
89 if (config.OS == "Windows") then
90 factory = new WinFactory()
91 else if (config.OS == "Mac") then
92 factory = new MacFactory()
93 else
94 throw new Exception("Error! Unknown operating system.")
95
96 Application app = new Application(factory)
Aplicabilidad
Utiliza el patrón Abstract Factory cuando tu código deba fun-
cionar con varias familias de productos relacionados, pero no
desees que dependa de las clases concretas de esos productos,
ya que puede ser que no los conozcas de antemano o sencilla-
mente quieras permitir una futura extensibilidad.
108 Patrones creacionales / Abstract Factory
Cómo implementarlo
1. Mapea una matriz de distintos tipos de productos frente a va-
riantes de dichos productos.
Pros y contras
Puedes tener la certeza de que los productos que obtienes de
una fábrica son compatibles entre sí.
Evitas un acoplamiento fuerte entre productos concretos y el
código cliente.
Principio de responsabilidad única. Puedes mover el código de
creación de productos a un solo lugar, haciendo que el código
sea más fácil de mantener.
Principio de abierto/cerrado. Puedes introducir nuevas variantes
de productos sin descomponer el código cliente existente.
BUILDER
También llamado: Constructor
Problema
Imagina un objeto complejo que requiere una inicialización
laboriosa, paso a paso, de muchos campos y objetos anida-
dos. Normalmente, este código de inicialización está sepulta-
do dentro de un monstruoso constructor con una gran cantidad
de parámetros. O, peor aún: disperso por todo el código
cliente.
Solución
El patrón Builder sugiere que saques el código de construcción
del objeto de su propia clase y lo coloques dentro de objetos
independientes llamados constructores.
Clase directora
Estructura
Pseudocódigo
Este ejemplo del patrón Builder ilustra cómo se puede reuti-
lizar el mismo código de construcción de objetos a la hora
de construir distintos tipos de productos, como automóviles, y
crear los correspondientes manuales para esos automóviles.
120 Patrones creacionales / Builder
93 method setEngine(...) is
94 // Añade instrucciones del motor.
95
96 method setTripComputer(...) is
97 // Añade instrucciones de la computadora de navegación.
98
99 method setGPS(...) is
100 // Añade instrucciones del GPS.
101
102 method getProduct():Manual is
103 // Devuelve el manual y rearma el constructor.
104
105
106 // El director sólo es responsable de ejecutar los pasos de
107 // construcción en una secuencia particular. Resulta útil cuando
108 // se crean productos de acuerdo con un orden o configuración
109 // específicos. En sentido estricto, la clase directora es
110 // opcional, ya que el cliente puede controlar directamente los
111 // objetos constructores.
112 class Director is
113 private field builder:Builder
114
115 // El director funciona con cualquier instancia de
116 // constructor que le pase el código cliente. De esta forma,
117 // el código cliente puede alterar el tipo final del
118 // producto recién montado.
119 method setBuilder(builder:Builder)
120 this
this.builder = builder
121
122 // El director puede construir multitud de variaciones de
123 // producto utilizando los mismos pasos de construcción.
124 method constructSportsCar(builder: Builder) is
126 Patrones creacionales / Builder
125 builder.reset()
126 builder.setSeats(2)
127 builder.setEngine(new
new SportEngine())
128 builder.setTripComputer(true
true)
129 builder.setGPS(true
true)
130
131 method constructSUV(builder: Builder) is
132 // ...
133
134
135 // El código cliente crea un objeto constructor, lo pasa al
136 // director y después inicia el proceso de construcción. El
137 // resultado final se extrae del objeto constructor.
138 class Application is
139
140 method makeCar() is
141 director = new Director()
142
143 CarBuilder builder = new CarBuilder()
144 director.constructSportsCar(builder)
145 Car car = builder.getProduct()
146
147 CarManualBuilder builder = new CarManualBuilder()
148 director.constructSportsCar(builder)
149
150 // El producto final a menudo se extrae de un objeto
151 // constructor, ya que el director no conoce y no
152 // depende de constructores y productos concretos.
153 Manual manual = builder.getProduct()
127 Patrones creacionales / Builder
Aplicabilidad
Utiliza el patrón Builder para evitar un “constructor
telescópico”.
1 class Pizza {
2 Pizza(int size) { ... }
3 Pizza(int size, boolean cheese) { ... }
4 Pizza(int size, boolean cheese, boolean pepperoni) { ... }
5 // ...
Cómo implementarlo
1. Asegúrate de poder definir claramente los pasos comunes de
construcción para todas las representaciones disponibles del
producto. De lo contrario, no podrás proceder a implementar
el patrón.
129 Patrones creacionales / Builder
Pros y contras
Puedes construir objetos paso a paso, aplazar pasos de la cons-
trucción o ejecutar pasos de forma recursiva.
Puedes reutilizar el mismo código de construcción al construir
varias representaciones de productos.
Principio de responsabilidad única. Puedes aislar un código de
construcción complejo de la lógica de negocio del producto.
PROTOTYPE
También llamado: Prototipo, Clon, Clone
Problema
Digamos que tienes un objeto y quieres crear una copia exacta
de él. ¿Cómo lo harías? En primer lugar, debes crear un nuevo
objeto de la misma clase. Después debes recorrer todos los
campos del objeto original y copiar sus valores en el nuevo
objeto.
Hay otro problema con el enfoque directo. Dado que debes co-
nocer la clase del objeto para crear un duplicado, el código
se vuelve dependiente de esa clase. Si esta dependencia adi-
cional no te da miedo, todavía hay otra trampa. En ocasiones
tan solo conocemos la interfaz que sigue el objeto, pero no su
134 Patrones creacionales / Prototype
Solución
El patrón Prototype delega el proceso de clonación a los pro-
pios objetos que están siendo clonados. El patrón declara una
interfaz común para todos los objetos que soportan la clona-
ción. Esta interfaz nos permite clonar un objeto sin acoplar el
código a la clase de ese objeto. Normalmente, dicha interfaz
contiene un único método clonar .
Estructura
Implementación básica
Pseudocódigo
En este ejemplo, el patrón Prototype nos permite producir co-
pias exactas de objetos geométricos sin acoplar el código a sus
clases.
1 // Prototipo base.
2 abstract class Shape is
3 field X: int
140 Patrones creacionales / Prototype
4 field Y: int
5 field color: string
6
7 // Un constructor normal.
8 constructor Shape() is
9 // ...
10
11 // El constructor prototipo. Un nuevo objeto se inicializa
12 // con valores del objeto existente.
13 constructor Shape(source: Shape) is
14 this
this()
15 this
this.X = source.X
16 this
this.Y = source.Y
17 this
this.color = source.color
18
19 // La operación clonar devuelve una de las subclases de
20 // Shape (Forma).
21 abstract method clone():Shape
22
23
24 // Prototipo concreto. El método de clonación crea un nuevo
25 // objeto y lo pasa al constructor. Hasta que el constructor
26 // termina, tiene una referencia a un nuevo clon. De este modo
27 // nadie tiene acceso a un clon a medio terminar. Esto garantiza
28 // la consistencia del resultado de la clonación.
29 class Rectangle extends Shape is
30 field width: int
31 field height: int
32
33 constructor Rectangle(source: Rectangle) is
34 // Para copiar campos privados definidos en la clase
35 // padre es necesaria una llamada a un constructor
141 Patrones creacionales / Prototype
36 // padre.
37 super
super(source)
38 this
this.width = source.width
39 this
this.height = source.height
40
41 method clone():Shape is
42 return new Rectangle(this
this)
43
44
45 class Circle extends Shape is
46 field radius: int
47
48 constructor Circle(source: Circle) is
49 super
super(source)
50 this
this.radius = source.radius
51
52 method clone():Shape is
53 return new Circle(this
this)
54
55
56 // En alguna parte del código cliente.
57 class Application is
58 field shapes: array of Shape
59
60 constructor Application() is
61 Circle circle = new Circle()
62 circle.X = 10
63 circle.Y = 10
64 circle.radius = 20
65 shapes.add(circle)
66
67 Circle anotherCircle = circle.clone()
142 Patrones creacionales / Prototype
68 shapes.add(anotherCircle)
69 // La variable `anotherCircle` (otroCírculo) contiene
70 // una copia exacta del objeto `circle`.
71
72 Rectangle rectangle = new Rectangle()
73 rectangle.width = 10
74 rectangle.height = 20
75 shapes.add(rectangle)
76
77 method businessLogic() is
78 // Prototype es genial porque te permite producir una
79 // copia de un objeto sin conocer nada de su tipo.
80 Array shapesCopy = new Array of Shapes.
81
82 // Por ejemplo, no conocemos los elementos exactos de la
83 // matriz de formas. Lo único que sabemos es que son
84 // todas formas. Pero, gracias al polimorfismo, cuando
85 // invocamos el método `clonar` en una forma, el
86 // programa comprueba su clase real y ejecuta el método
87 // de clonación adecuado definido en dicha clase. Por
88 // eso obtenemos los clones adecuados en lugar de un
89 // grupo de simples objetos Shape.
90 foreach (s in shapes) do
91 shapesCopy.add(s.clone())
92
93 // La matriz `shapesCopy` contiene copias exactas del
94 // hijo de la matriz `shape`.
143 Patrones creacionales / Prototype
Aplicabilidad
Utiliza el patrón Prototype cuando tu código no deba depender
de las clases concretas de objetos que necesites copiar.
Cómo implementarlo
1. Crea la interfaz del prototipo y declara el método clonar en
ella, o, simplemente, añade el método a todas las clases de
una jerarquía de clase existente, si la tienes.
Pros y contras
Puedes clonar objetos sin acoplarlos a sus clases concretas.
SINGLETON
También llamado: Instancia única
Problema
El patrón Singleton resuelve dos problemas al mismo tiempo,
vulnerando el Principio de responsabilidad única:
1. Garantizar que una clase tenga una única instancia. ¿Por qué
querría alguien controlar cuántas instancias tiene una clase?
El motivo más habitual es controlar el acceso a algún recurso
compartido, por ejemplo, una base de datos o un archivo.
Puede ser que los clientes ni siquiera se den cuenta de que trabajan con el
mismo objeto todo el tiempo.
150 Patrones creacionales / Singleton
Solución
Todas las implementaciones del patrón Singleton tienen estos
dos pasos en común:
Estructura
propia clase.
Pseudocódigo
En este ejemplo, la clase de conexión de la base de datos actúa
como Singleton. Esta clase no tiene un constructor público, por
lo que la única manera de obtener su objeto es invocando el
método obtenerInstancia . Este método almacena en caché
153 Patrones creacionales / Singleton
Aplicabilidad
Utiliza el patrón Singleton cuando una clase de tu programa
tan solo deba tener una instancia disponible para todos los
clientes; por ejemplo, un único objeto de base de datos com-
partido por distintas partes del programa.
Cómo implementarlo
1. Añade un campo estático privado a la clase para almacenar la
instancia Singleton.
Pros y contras
Puedes tener la certeza de que una clase tiene una única
instancia.
Obtienes un punto de acceso global a dicha instancia.
Patrones estructurales
Los patrones estructurales explican cómo ensamblar objetos y
clases en estructuras más grandes, a la vez que se mantiene la
flexibilidad y eficiencia de estas estructuras.
Adapter
Permite la colaboración entre objetos con interfaces incompati-
bles.
Bridge
Permite dividir una clase grande o un grupo de clases estrecha-
mente relacionadas, en dos jerarquías separadas (abstracción e
implementación) que pueden desarrollarse independientemente
la una de la otra.
159 Patrones estructurales
Composite
Permite componer objetos en estructuras de árbol y trabajar con
esas estructuras como si fueran objetos individuales.
Decorator
Permite añadir funcionalidades a objetos colocando estos objetos
dentro de objetos encapsuladores especiales que contienen estas
funcionalidades.
Facade
Proporciona una interfaz simplificada a una biblioteca, un frame-
work o cualquier otro grupo complejo de clases.
160 Patrones estructurales
Flyweight
Permite mantener más objetos dentro de la cantidad disponible
de memoria RAM compartiendo las partes comunes del estado
entre varios objetos en lugar de mantener toda la información en
cada objeto.
Proxy
Permite proporcionar un sustituto o marcador de posición para
otro objeto. Un proxy controla el acceso al objeto original, permi-
tiéndote hacer algo antes o después de que la solicitud llegue al
objeto original.
161 Patrones estructurales / Adapter
ADAPTER
También llamado: Adaptador, Envoltorio, Wrapper
Problema
Imagina que estás creando una aplicación de monitoreo del
mercado de valores. La aplicación descarga la información de
bolsa desde varias fuentes en formato XML para presentarla al
usuario con bonitos gráficos y diagramas.
Solución
Puedes crear un adaptador. Se trata de un objeto especial que
convierte la interfaz de un objeto, de forma que otro objeto
pueda comprenderla.
Estructura
Adaptador de objetos
Clase adaptadora
Pseudocódigo
Este ejemplo del patrón Adapter se basa en el clásico conflicto
entre piezas cuadradas y agujeros redondos.
Aplicabilidad
Utiliza la clase adaptadora cuando quieras usar una clase exis-
tente, pero cuya interfaz no sea compatible con el resto del
código.
171 Patrones estructurales / Adapter
Cómo implementarlo
1. Asegúrate de que tienes al menos dos clases con interfaces in-
compatibles:
172 Patrones estructurales / Adapter
Pros y contras
Principio de responsabilidad única. Puedes separar la interfaz o
el código de conversión de datos de la lógica de negocio pri-
maria del programa.
Principio de abierto/cerrado. Puedes introducir nuevos tipos de
adaptadores al programa sin descomponer el código cliente
existente, siempre y cuando trabajen con los adaptadores a
través de la interfaz con el cliente.
BRIDGE
También llamado: Puente
Problema
¿Abstracción? ¿Implementación? ¿Asusta? Mantengamos la
calma y veamos un ejemplo sencillo.
Solución
Este problema se presenta porque intentamos extender las
clases de forma en dos dimensiones independientes: por forma
y por color. Es un problema muy habitual en la herencia de
clases.
Abstracción e implementación
1
El libro de la GoF introduce los términos Abstracción e Imple-
mentación como parte de la definición del patrón Bridge. En
mi opinión, los términos suenan demasiado académicos y pro-
vocan que el patrón parezca más complicado de lo que es en
realidad. Una vez leído el sencillo ejemplo con las formas y los
colores, vamos a descifrar el significado que esconden las te-
mibles palabras del libro de esta banda de cuatro.
Estructura
Pseudocódigo
Este ejemplo ilustra cómo puede ayudar el patrón Bridge a
dividir el código monolítico de una aplicación que gestiona
dispositivos y sus controles remotos. Las clases Dispositivo
actúan como implementación, mientras que las clases
Remoto actúan como abstracción.
184 Patrones estructurales / Bridge
28 device.setVolume(0)
29
30
31 // La interfaz de "implementación" declara métodos comunes a
32 // todas las clases concretas de implementación. No tiene por
33 // qué coincidir con la interfaz de la abstracción. De hecho,
34 // las dos interfaces pueden ser completamente diferentes.
35 // Normalmente, la interfaz de implementación únicamente
36 // proporciona operaciones primitivas, mientras que la
37 // abstracción define operaciones de más alto nivel con base en
38 // las primitivas.
39 interface Device is
40 method isEnabled()
41 method enable()
42 method disable()
43 method getVolume()
44 method setVolume(percent)
45 method getChannel()
46 method setChannel(channel)
47
48
49 // Todos los dispositivos siguen la misma interfaz.
50 class Tv implements Device is
51 // ...
52
53 class Radio implements Device is
54 // ...
55
56
57 // En algún lugar del código cliente.
58 tv = new Tv()
59 remote = new RemoteControl(tv)
187 Patrones estructurales / Bridge
60 remote.togglePower()
61
62 radio = new Radio()
63 remote = new AdvancedRemoteControl(radio)
Aplicabilidad
Utiliza el patrón Bridge cuando quieras dividir y organizar una
clase monolítica que tenga muchas variantes de una sola fun-
cionalidad (por ejemplo, si la clase puede trabajar con diversos
servidores de bases de datos).
Por cierto, este último punto es la razón principal por la que tanta
gente confunde el patrón Bridge con el patrón Strategy. Recuerda
que un patrón es algo más que un cierto modo de estructurar tus
clases. También puede comunicar intención y el tipo de problema
que se está abordando.
Cómo implementarlo
1. Identifica las dimensiones ortogonales de tus clases. Estos
conceptos independientes pueden ser: abstracción/plataforma,
dominio/infraestructura, front end/back end, o interfaz/imple-
mentación.
Pros y contras
Puedes crear clases y aplicaciones independientes de
plataforma.
El código cliente funciona con abstracciones de alto nivel. No
está expuesto a los detalles de la plataforma.
190 Patrones estructurales / Bridge
COMPOSITE
También llamado: Objeto compuesto, Object Tree
Problema
El uso del patrón Composite sólo tiene sentido cuando el modelo
central de tu aplicación puede representarse en forma de árbol.
Solución
El patrón Composite sugiere que trabajes con Productos y
Cajas a través de una interfaz común que declara un método
Estructura
197 Patrones estructurales / Composite
Pseudocódigo
En este ejemplo, el patrón Composite te permite implementar
el apilamiento (stacking) de formas geométricas en un editor
gráfico.
que una forma simple. Sin embargo, en lugar de hacer algo por
su cuenta, una forma compuesta pasa la solicitud de forma re-
cursiva a todos sus hijos y “suma” el resultado.
20 method draw() is
21 // Dibuja un punto en X e Y.
22
23 // Todas las clases de componente pueden extender otros
24 // componentes.
25 class Circle extends Dot is
26 field radius
27
28 constructor Circle(x, y, radius) { ... }
29
30 method draw() is
31 // Dibuja un círculo en X y Y con radio R.
32
33 // La clase compuesta representa componentes complejos que
34 // pueden tener hijos. Normalmente los objetos compuestos
35 // delegan el trabajo real a sus hijos y después "recapitulan"
36 // el resultado.
37 class CompoundGraphic implements Graphic is
38 field children: array of Graphic
39
40 // Un objeto compuesto puede añadir o eliminar otros
41 // componentes (tanto simples como complejos) a o desde su
42 // lista hija.
43 method add(child: Graphic) is
44 // Añade un hijo a la matriz de hijos.
45
46 method remove(child: Graphic) is
47 // Elimina un hijo de la matriz de hijos.
48
49 method move(x, y) is
50 foreach (child in children) do
51 child.move(x, y)
201 Patrones estructurales / Composite
52
53 // Un compuesto ejecuta su lógica primaria de una forma
54 // particular. Recorre recursivamente todos sus hijos,
55 // recopilando y recapitulando sus resultados. Debido a que
56 // los hijos del compuesto pasan esas llamadas a sus propios
57 // hijos y así sucesivamente, se recorre todo el árbol de
58 // objetos como resultado.
59 method draw() is
60 // 1. Para cada componente hijo:
61 // - Dibuja el componente.
62 // - Actualiza el rectángulo delimitador.
63 // 2. Dibuja un rectángulo de línea punteada utilizando
64 // las coordenadas de delimitación.
65
66
67 // El código cliente trabaja con todos los componentes a través
68 // de su interfaz base. De esta forma el código cliente puede
69 // soportar componentes de hoja simples así como compuestos
70 // complejos.
71 class ImageEditor is
72 field all: array of Graphic
73
74 method load() is
75 all = new CompoundGraphic()
76 all.add(new
new Dot(1, 2))
77 all.add(new
new Circle(5, 3, 10))
78 // ...
79
80 // Combina componentes seleccionados para formar un
81 // componente compuesto complejo.
82 method groupSelected(components: array of Graphic) is
83 group = new CompoundGraphic()
202 Patrones estructurales / Composite
Aplicabilidad
Utiliza el patrón Composite cuando tengas que implementar
una estructura de objetos con forma de árbol.
Cómo implementarlo
1. Asegúrate de que el modelo central de tu aplicación pueda re-
presentarse como una estructura de árbol. Intenta dividirlo en
elementos simples y contenedores. Recuerda que los contene-
dores deben ser capaces de contener tanto elementos simples
como otros contenedores.
Pros y contras
Puedes trabajar con estructuras de árbol complejas con mayor
comodidad: utiliza el polimorfismo y la recursión en tu favor.
Principio de abierto/cerrado. Puedes introducir nuevos tipos de
elemento en la aplicación sin descomponer el código existen-
te, que ahora funciona con el árbol de objetos.
DECORATOR
También llamado: Decorador, Envoltorio, Wrapper
Problema
Imagina que estás trabajando en una biblioteca de notifica-
ciones que permite a otros programas notificar a sus usuarios
acerca de eventos importantes.
Solución
Cuando tenemos que alterar la funcionalidad de un objeto, lo
primero que se viene a la mente es extender una clase. No
obstante, la herencia tiene varias limitaciones importantes de
las que debes ser consciente.
Estructura
215 Patrones estructurales / Decorator
Pseudocódigo
En este ejemplo, el patrón Decorator te permite comprimir y
encriptar información delicada independientemente del códi-
go que utiliza esos datos.
216 Patrones estructurales / Decorator
• Después de que los datos son leídos del disco, pasan por los
mismos decoradores, que los descomprimen y decodifican.
121 if (enabledCompression)
122 source = new CompressionDecorator(source)
123
124 logger = new SalaryManager(source)
125 salary = logger.load()
126 // ...
Aplicabilidad
Utiliza el patrón Decorator cuando necesites asignar funciona-
lidades adicionales a objetos durante el tiempo de ejecución
sin descomponer el código que utiliza esos objetos.
Cómo implementarlo
1. Asegúrate de que tu dominio de negocio puede representar-
se como un componente primario con varias capas opcionales
encima.
4. Crea una clase base decoradora. Debe tener un campo para al-
macenar una referencia a un objeto envuelto. El campo debe
declararse con el tipo de interfaz de componente para permitir
la vinculación a componentes concretos, así como a decorado-
res. La clase decoradora base debe delegar todas las operacio-
nes al objeto envuelto.
Pros y contras
Puedes extender el comportamiento de un objeto sin crear una
nueva subclase.
Puedes añadir o eliminar responsabilidades de un objeto du-
rante el tiempo de ejecución.
Puedes combinar varios comportamientos envolviendo un ob-
jeto con varios decoradores.
Principio de responsabilidad única. Puedes dividir una clase mo-
nolítica que implementa muchas variantes posibles de com-
portamiento, en varias clases más pequeñas.
FACADE
También llamado: Fachada
Problema
Imagina que debes lograr que tu código trabaje con un amplio
grupo de objetos que pertenecen a una sofisticada biblioteca o
framework. Normalmente, debes inicializar todos esos objetos,
llevar un registro de las dependencias, ejecutar los métodos
en el orden correcto y así sucesivamente.
Solución
Una fachada es una clase que proporciona una interfaz sim-
ple a un subsistema complejo que contiene muchas partes
móviles. Una fachada puede proporcionar una funcionalidad
limitada en comparación con trabajar directamente con el sub-
sistema. Sin embargo, tan solo incluye las funciones realmente
importantes para los clientes.
Estructura
Pseudocódigo
En este ejemplo, el patrón Facade simplifica la interacción con
un framework complejo de conversión de vídeo.
27 class VideoConverter is
28 method convert(filename, format):File is
29 file = new VideoFile(filename)
30 sourceCodec = new CodecFactory.extract(file)
31 if (format == "mp4")
32 destinationCodec = new MPEG4CompressionCodec()
33 else
34 destinationCodec = new OggCompressionCodec()
35 buffer = BitrateReader.read(filename, sourceCodec)
36 result = BitrateReader.convert(buffer, destinationCodec)
37 result = (new
new AudioMixer()).fix(result)
38 return new File(result)
39
40 // Las clases Application no dependen de un millón de clases
41 // proporcionadas por el complejo framework. Además, si decides
42 // cambiar los frameworks, sólo tendrás de volver a escribir la
43 // clase fachada.
44 class Application is
45 method main() is
46 convertor = new VideoConverter()
47 mp4 = convertor.convert("funny-cats-video.ogg", "mp4")
48 mp4.save()
Aplicabilidad
Utiliza el patrón Facade cuando necesites una interfaz limitada
pero directa a un subsistema complejo.
Cómo implementarlo
1. Comprueba si es posible proporcionar una interfaz más simple
que la que está proporcionando un subsistema existente. Estás
bien encaminado si esta interfaz hace que el código cliente sea
independiente de muchas de las clases del subsistema.
234 Patrones estructurales / Facade
Pros y contras
Puedes aislar tu código de la complejidad de un subsistema.
FLYWEIGHT
También llamado: Peso mosca, Peso ligero, Cache
Problema
Para divertirte un poco después de largas horas de trabajo, de-
cides crear un sencillo videojuego en el que los jugadores se
tienen que mover por un mapa disparándose entre sí. Decides
implementar un sistema de partículas realistas que lo distin-
ga de otros juegos. Grandes cantidades de balas, misiles y me-
tralla de las explosiones volarán por todo el mapa, ofreciendo
una apasionante experiencia al jugador.
Solución
Observando más atentamente la clase Partícula , puede ser
que te hayas dado cuenta de que los campos de color y spri-
te consumen mucha más memoria que otros campos. Lo que
es peor, esos dos campos almacenan información casi idéntica
de todas las partículas. Por ejemplo, todas las balas tienen el
mismo color y sprite.
Flyweight y la inmutabilidad
Fábrica flyweight
Estructura
Pseudocódigo
En este ejemplo, el patrón Flyweight ayuda a reducir el uso de
memoria a la hora de representar millones de objetos de árbol
en un lienzo.
33
34 // El objeto contextual contiene la parte extrínseca del estado
35 // del árbol. Una aplicación puede crear millones de ellas, ya
36 // que son muy pequeñas: dos coordenadas en números enteros y un
37 // campo de referencia.
38 class Tree is
39 field x,y
40 field type: TreeType
41 constructor Tree(x, y, type) { ... }
42 method draw(canvas) is
43 type.draw(canvas, this
this.x, this
this.y)
44
45 // Las clases Tree y Forest son los clientes de flyweight.
46 // Puedes fusionarlas si no tienes la intención de desarrollar
47 // más la clase Tree.
48 class Forest is
49 field trees: collection of Trees
50
51 method plantTree(x, y, name, color, texture) is
52 type = TreeFactory.getTreeType(name, color, texture)
53 tree = new Tree(x, y, type)
54 trees.add(tree)
55
56 method draw(canvas) is
57 foreach (tree in trees) do
58 tree.draw(canvas)
249 Patrones estructurales / Flyweight
Aplicabilidad
Utiliza el patrón Flyweight únicamente cuando tu programa
deba soportar una enorme cantidad de objetos que apenas
quepan en la RAM disponible.
Cómo implementarlo
1. Divide los campos de una clase que se convertirá en flyweight
en dos partes:
Pros y contras
Puedes ahorrar mucha RAM, siempre que tu programa tenga
toneladas de objetos similares.
Puede que estés cambiando RAM por ciclos CPU cuando deba
calcularse de nuevo parte de la información de contexto cada
vez que alguien invoque un método flyweight.
251 Patrones estructurales / Flyweight
PROXY
Proxy es un patrón de diseño estructural que te permite pro-
porcionar un sustituto o marcador de posición para otro ob-
jeto. Un proxy controla el acceso al objeto original,
permitiéndote hacer algo antes o después de que la solicitud
llegue al objeto original.
253 Patrones estructurales / Proxy
Problema
¿Por qué querrías controlar el acceso a un objeto? Imagina que
tienes un objeto enorme que consume una gran cantidad de
recursos del sistema. Lo necesitas de vez en cuando, pero no
siempre.
Solución
El patrón Proxy sugiere que crees una nueva clase proxy con la
misma interfaz que un objeto de servicio original. Después ac-
tualizas tu aplicación para que pase el objeto proxy a todos los
clientes del objeto original. Al recibir una solicitud de un clien-
te, el proxy crea un objeto de servicio real y le delega todo el
trabajo.
Las tarjetas de crédito pueden utilizarse para realizar pagos tanto como
el efectivo.
Estructura
Pseudocódigo
Este ejemplo ilustra cómo el patrón Proxy puede ayudar a
introducir la inicialización diferida y el almacenamiento en
caché a una biblioteca de integración de YouTube de un
tercero.
19
20 method downloadVideo(id) is
21 // Descarga un archivo de video de YouTube.
22
23 // Para ahorrar ancho de banda, podemos guardar en caché
24 // resultados de la solicitud durante algún tiempo, pero se
25 // puede colocar este código directamente dentro de la clase de
26 // servicio. Por ejemplo, puede haberse proporcionado como parte
27 // de la biblioteca de un tercero y/o definido como `final`. Por
28 // eso colocamos el código de almacenamiento en caché dentro de
29 // una nueva clase proxy que implementa la misma interfaz que la
30 // clase servicio. Delega al objeto de servicio únicamente
31 // cuando deben enviarse las solicitudes reales.
32 class CachedYouTubeClass implements ThirdPartyYouTubeLib is
33 private field service: ThirdPartyYouTubeLib
34 private field listCache, videoCache
35 field needReset
36
37 constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
38 this
this.service = service
39
40 method listVideos() is
41 if (listCache == null || needReset)
42 listCache = service.listVideos()
43 return listCache
44
45 method getVideoInfo(id) is
46 if (videoCache == null || needReset)
47 videoCache = service.getVideoInfo(id)
48 return videoCache
49
50 method downloadVideo(id) is
260 Patrones estructurales / Proxy
51 if (!downloadExists(id) || needReset)
52 service.downloadVideo(id)
53
54 // La clase GUI, que solía trabajar directamente con un objeto
55 // de servicio, permanece sin cambios siempre y cuando trabaje
56 // con el objeto de servicio a través de una interfaz. Podemos
57 // pasar sin riesgo un objeto proxy en lugar de un objeto de
58 // servicio real, ya que ambos implementan la misma interfaz.
59 class YouTubeManager is
60 protected field service: ThirdPartyYouTubeLib
61
62 constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
63 this
this.service = service
64
65 method renderVideoPage(id) is
66 info = service.getVideoInfo(id)
67 // Representa la página del video.
68
69 method renderListPanel() is
70 list = service.listVideos()
71 // Representa la lista de miniaturas de los videos.
72
73 method reactOnUserInput() is
74 renderVideoPage()
75 renderListPanel()
76
77 // La aplicación puede configurar proxies sobre la marcha.
78 class Application is
79 method init() is
80 aYouTubeService = new ThirdPartyYouTubeClass()
81 aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
82 manager = new YouTubeManager(aYouTubeProxy)
261 Patrones estructurales / Proxy
83 manager.reactOnUserInput()
Aplicabilidad
Hay decenas de formas de utilizar el patrón Proxy. Repasemos
los usos más populares.
Cómo implementarlo
1. Si no hay una interfaz de servicio preexistente, crea una para
que los objetos de proxy y de servicio sean intercambiables.
No siempre resulta posible extraer la interfaz de la clase ser-
vicio, porque tienes que cambiar todos los clientes del servi-
cio para utilizar esa interfaz. El plan B consiste en convertir el
proxy en una subclase de la clase servicio, de forma que here-
de la interfaz del servicio.
Pros y contras
Puedes controlar el objeto de servicio sin que los clientes
lo sepan.
Puedes gestionar el ciclo de vida del objeto de servicio cuando
a los clientes no les importa.
El proxy funciona incluso si el objeto de servicio no está listo
o no está disponible.
Principio de abierto/cerrado. Puedes introducir nuevos proxies
sin cambiar el servicio o los clientes.
Patrones de
comportamiento
Los patrones de comportamiento tratan con algoritmos y la
asignación de responsabilidades entre objetos.
Chain of
Responsibility
Permite pasar solicitudes a lo largo de una cadena de manejado-
res. Al recibir una solicitud, cada manejador decide si la procesa
o si la pasa al siguiente manejador de la cadena.
Command
Convierte una solicitud en un objeto independiente que contiene
toda la información sobre la solicitud. Esta transformación te per-
mite parametrizar los métodos con diferentes solicitudes, retrasar
o poner en cola la ejecución de una solicitud y soportar operacio-
nes que no se pueden realizar.
267 Patrones de comportamiento
Iterator
Permite recorrer elementos de una colección sin exponer su re-
presentación subyacente (lista, pila, árbol, etc.).
Mediator
Permite reducir las dependencias caóticas entre objetos. El pa-
trón restringe las comunicaciones directas entre los objetos, for-
zándolos a colaborar únicamente a través de un objeto mediador.
Memento
Permite guardar y restaurar el estado previo de un objeto sin re-
velar los detalles de su implementación.
268 Patrones de comportamiento
Observer
Permite definir un mecanismo de suscripción para notificar a va-
rios objetos sobre cualquier evento que le suceda al objeto que
están observando.
State
Permite a un objeto alterar su comportamiento cuando su estado
interno cambia. Parece como si el objeto cambiara su clase.
Strategy
Permite definir una familia de algoritmos, colocar cada uno de
ellos en una clase separada y hacer sus objetos intercambiables.
269 Patrones de comportamiento
Template
Method
Define el esqueleto de un algoritmo en la superclase pero permi-
te que las subclases sobrescriban pasos del algoritmo sin cam-
biar su estructura.
Visitor
Permite separar algoritmos de los objetos sobre los que operan.
270 Patrones de comportamiento / Chain of Responsibility
CHAIN OF
RESPONSIBILITY
También llamado: Cadena de responsabilidad, CoR, Chain of
Command
Problema
Imagina que estás trabajando en un sistema de pedidos online.
Quieres restringir el acceso al sistema de forma que únicamen-
te los usuarios autenticados puedan generar pedidos. Además,
los usuarios que tengan permisos administrativos deben tener
pleno acceso a todos los pedidos.
Solución
Al igual que muchos otros patrones de diseño de compor-
tamiento, el Chain of Responsibility se basa en transformar
comportamientos particulares en objetos autónomos llamados
manejadores. En nuestro caso, cada comprobación debe poner-
se dentro de su propia clase con un único método que reali-
ce la comprobación. La solicitud, junto con su información, se
pasa a este método como argumento.
Estructura
278 Patrones de comportamiento / Chain of Responsibility
Pseudocódigo
En este ejemplo, el patrón Chain of Responsibility es responsa-
ble de mostrar información de ayuda contextual para elemen-
tos GUI activos.
Las clases GUI se crean con el patrón Composite. Cada elemento se vincula
a su elemento contenedor. En cualquier momento puedes crear una cadena
de elementos que comience con el propio elemento y recorra todos los
elementos contenedores.
280 Patrones de comportamiento / Chain of Responsibility
Aplicabilidad
Utiliza el patrón Chain of Responsibility cuando tu programa
deba procesar distintos tipos de solicitudes de varias maneras,
pero los tipos exactos de solicitudes y sus secuencias no se co-
nozcan de antemano.
Cómo implementarlo
1. Declara la interfaz manejadora y describe la firma de un méto-
do para manejar solicitudes.
◦ Si procesa la solicitud.
Pros y contras
Puedes controlar el orden de control de solicitudes.
COMMAND
También llamado: Comando, Orden, Action, Transaction
Problema
Imagina que estás trabajando en una nueva aplicación de edi-
ción de texto. Tu tarea actual consiste en crear una barra de
herramientas con unos cuantos botones para varias opera-
ciones del editor. Creaste una clase Botón muy limpia que
puede utilizarse para los botones de la barra de herramientas
y también para botones genéricos en diversos diálogos.
Solución
El buen diseño de software a menudo se basa en el principio
de separación de responsabilidades, lo que suele tener como
resultado la división de la aplicación en capas. El ejemplo más
habitual es tener una capa para la interfaz gráfica de usua-
rio (GUI) y otra capa para la lógica de negocio. La capa GUI
es responsable de representar una bonita imagen en pantalla,
capturar entradas y mostrar resultados de lo que el usuario y
la aplicación están haciendo. Sin embargo, cuando se trata de
hacer algo importante, como calcular la trayectoria de la luna
o componer un informe anual, la capa GUI delega el trabajo a
la capa subyacente de la lógica de negocio.
Puede que hayas observado que falta una pieza del rompeca-
bezas, que son los parámetros de la solicitud. Un objeto GUI
puede haber proporcionado al objeto de la capa de negocio al-
gunos parámetros. Ya que el método de ejecución del coman-
do no tiene parámetros, ¿cómo pasaremos los detalles de la
solicitud al receptor? Resulta que el comando debe estar pre-
configurado con esta información o ser capaz de conseguirla
por su cuenta.
295 Patrones de comportamiento / Command
Estructura
Pseudocódigo
En este ejemplo, el patrón Command ayuda a rastrear el histo-
rial de operaciones ejecutadas y hace posible revertir una ope-
ración si es necesario.
18 editor.text = backup
19
20 // El método de ejecución se declara abstracto para forzar a
21 // todos los comandos concretos a proporcionar sus propias
22 // implementaciones. El método debe devolver verdadero o
23 // falso dependiendo de si el comando cambia el estado del
24 // editor.
25 abstract method execute()
26
27
28 // Los comandos concretos van aquí.
29 class CopyCommand extends Command is
30 // El comando copiar no se guarda en el historial ya que no
31 // cambia el estado del editor.
32 method execute() is
33 app.clipboard = editor.getSelection()
34 return false
35
36 class CutCommand extends Command is
37 // El comando cortar no cambia el estado del editor, por lo
38 // que debe guardarse en el historial. Y se guardará siempre
39 // y cuando el método devuelva verdadero.
40 method execute() is
41 saveBackup()
42 app.clipboard = editor.getSelection()
43 editor.deleteSelection()
44 return true
45
46 class PasteCommand extends Command is
47 method execute() is
48 saveBackup()
49 editor.replaceSelection(app.clipboard)
302 Patrones de comportamiento / Command
50 return true
51
52 // La operación deshacer también es un comando.
53 class UndoCommand extends Command is
54 method execute() is
55 app.undo()
56 return false
57
58
59 // El historial global de comandos tan solo es una pila.
60 class CommandHistory is
61 private field history: array of Command
62
63 // El último dentro...
64 method push(c: Command) is
65 // Empuja el comando al final de la matriz del
66 // historial.
67
68 // ...el primero fuera.
69 method pop():Command is
70 // Obtiene el comando más reciente del historial.
71
72
73 // La clase editora tiene operaciones reales de edición de
74 // texto. Juega el papel de un receptor: todos los comandos
75 // acaban delegando la ejecución a los métodos del editor.
76 class Editor is
77 field text: string
78
79 method getSelection() is
80 // Devuelve el texto seleccionado.
81
303 Patrones de comportamiento / Command
82 method deleteSelection() is
83 // Borra el texto seleccionado.
84
85 method replaceSelection(text) is
86 // Inserta los contenidos del portapapeles en la
87 // posición actual.
88
89
90 // La clase Aplicación establece relaciones entre objetos. Actúa
91 // como un emisor: cuando algo debe hacerse, crea un objeto de
92 // comando y lo ejecuta.
93 class Application is
94 field clipboard: string
95 field editors: array of Editors
96 field activeEditor: Editor
97 field history: CommandHistory
98
99 // El código que asigna comandos a objetos UI puede tener
100 // este aspecto.
101 method createUI() is
102 // ...
103 copy = function
function() { executeCommand(
104 new CopyCommand(this
this, activeEditor)) }
105 copyButton.setCommand(copy)
106 shortcuts.onKeyPress("Ctrl+C", copy)
107
108 cut = function
function() { executeCommand(
109 new CutCommand(this
this, activeEditor)) }
110 cutButton.setCommand(cut)
111 shortcuts.onKeyPress("Ctrl+X", cut)
112
113 paste = function
function() { executeCommand(
304 Patrones de comportamiento / Command
Aplicabilidad
Utiliza el patrón Command cuando quieras parametrizar obje-
tos con operaciones.
Cómo implementarlo
1. Declara la interfaz de comando con un único método de
ejecución.
◦ Crear receptores.
Pros y contras
Principio de responsabilidad única. Puedes desacoplar las clases
que invocan operaciones de las que realizan esas operaciones.
Principio de abierto/cerrado. Puedes introducir nuevos coman-
dos en la aplicación sin descomponer el código cliente
existente.
Puedes implementar deshacer/rehacer.
ITERATOR
También llamado: Iterador
Problema
Las colecciones son de los tipos de datos más utilizados en
programación. Sin embargo, una colección tan solo es un con-
tenedor para un grupo de objetos.
Por otro lado, el código cliente que debe funcionar con varias
colecciones puede no saber cómo éstas almacenan sus ele-
mentos. No obstante, ya que todas las colecciones proporcio-
nan formas diferentes de acceder a sus elementos, no tienes
otra opción más que acoplar tu código a las clases de la colec-
ción específica.
Solución
La idea central del patrón Iterator es extraer el comportamien-
to de recorrido de una colección y colocarlo en un objeto inde-
pendiente llamado iterador.
313 Patrones de comportamiento / Iterator
Planeas visitar Roma por unos días y ver todas sus atraccio-
nes y puntos de interés. Pero, una vez allí, podrías perder
mucho tiempo dando vueltas, incapaz de encontrar siquiera el
Coliseo.
Estructura
Pseudocódigo
En este ejemplo, el patrón Iterator se utiliza para recorrer un
tipo especial de colección que encapsula el acceso al grafo so-
cial de Facebook. La colección proporciona varios iteradores
que recorren perfiles de distintas formas.
318 Patrones de comportamiento / Iterator
26
27
28 // La clase iteradora concreta.
29 class FacebookIterator implements ProfileIterator is
30 // El iterador necesita una referencia a la colección que
31 // recorre.
32 private field facebook: Facebook
33 private field profileId, type: string
34
35 // Un objeto iterador recorre la colección
36 // independientemente de otro iterador, por eso debe
37 // almacenar el estado de iteración.
38 private field currentPosition
39 private field cache: array of Profile
40
41 constructor FacebookIterator(facebook, profileId, type) is
42 this
this.facebook = facebook
43 this
this.profileId = profileId
44 this
this.type = type
45
46 private method lazyInit() is
47 if (cache == null
null)
48 cache = facebook.socialGraphRequest(profileId, type)
49
50 // Cada clase iteradora concreta tiene su propia
51 // implementación de la interfaz iteradora común.
52 method getNext() is
53 if (hasMore())
54 currentPosition++
55 return cache[currentPosition]
56
57 method hasMore() is
321 Patrones de comportamiento / Iterator
58 lazyInit()
59 return currentPosition < cache.length
60
61
62 // Aquí tienes otro truco útil: puedes pasar un iterador a una
63 // clase cliente en lugar de darle acceso a una colección
64 // completa. De esta forma, no expones la colección al cliente.
65 //
66 // Y hay otra ventaja: puedes cambiar la forma en la que el
67 // cliente trabaja con la colección durante el tiempo de
68 // ejecución pasándole un iterador diferente. Esto es posible
69 // porque el código cliente no está acoplado a clases iteradoras
70 // concretas.
71 class SocialSpammer is
72 method send(iterator: ProfileIterator, message: string) is
73 while (iterator.hasMore())
74 profile = iterator.getNext()
75 System.sendEmail(profile.getEmail(), message)
76
77
78 // La clase Aplicación configura colecciones e iteradores y
79 // después los pasa al código cliente.
80 class Application is
81 field network: SocialNetwork
82 field spammer: SocialSpammer
83
84 method config() is
85 if working with Facebook
86 this
this.network = new Facebook()
87 if working with LinkedIn
88 this
this.network = new LinkedIn()
89 this
this.spammer = new SocialSpammer()
322 Patrones de comportamiento / Iterator
90
91 method sendSpamToFriends(profile) is
92 iterator = network.createFriendsIterator(profile.getId())
93 spammer.send(iterator, "Very important message")
94
95 method sendSpamToCoworkers(profile) is
96 iterator = network.createCoworkersIterator(profile.getId())
97 spammer.send(iterator, "Very important message")
Aplicabilidad
Utiliza el patrón Iterator cuando tu colección tenga una estruc-
tura de datos compleja a nivel interno, pero quieras ocultar su
complejidad a los clientes (ya sea por conveniencia o por razo-
nes de seguridad).
Cómo implementarlo
1. Declara la interfaz iteradora. Como mínimo, debe tener un
método para extraer el siguiente elemento de una colección.
Por conveniencia, puedes añadir un par de métodos distintos,
como para extraer el elemento previo, localizar la posición ac-
tual o comprobar el final de la iteración.
Pros y contras
Principio de responsabilidad única. Puedes limpiar el código
cliente y las colecciones extrayendo algoritmos de recorrido
voluminosos y colocándolos en clases independientes.
Principio de abierto/cerrado. Puedes implementar nuevos tipos
de colecciones e iteradores y pasarlos al código existente sin
descomponer nada.
Puedes recorrer la misma colección en paralelo porque cada
objeto iterador contiene su propio estado de iteración.
325 Patrones de comportamiento / Iterator
• Puedes utilizar Visitor junto con Iterator para recorrer una es-
tructura de datos compleja y ejecutar alguna operación sobre
sus elementos, incluso aunque todos tengan clases distintas.
326 Patrones de comportamiento / Mediator
MEDIATOR
También llamado: Mediador, Intermediary, Controller
Problema
Digamos que tienes un diálogo para crear y editar perfiles de
cliente. Consiste en varios controles de formulario, como cam-
pos de texto, casillas, botones, etc.
Los elementos pueden tener muchas relaciones con otros elementos. Por
eso, los cambios en algunos elementos pueden afectar a los demás.
Solución
El patrón Mediator sugiere que detengas toda comunicación
directa entre los componentes que quieres hacer independien-
tes entre sí. En lugar de ello, estos componentes deberán cola-
borar indirectamente, invocando un objeto mediador especial
que redireccione las llamadas a los componentes adecuados.
Como resultado, los componentes dependen únicamente de
una sola clase mediadora, en lugar de estar acoplados a dece-
nas de sus colegas.
Los pilotos de los aviones que llegan o salen del área de con-
trol del aeropuerto no se comunican directamente entre sí. En
lugar de eso, hablan con un controlador de tráfico aéreo, que
está sentado en una torre alta cerca de la pista de aterrizaje.
331 Patrones de comportamiento / Mediator
Estructura
332 Patrones de comportamiento / Mediator
Pseudocódigo
En este ejemplo, el patrón Mediator te ayuda a eliminar de-
pendencias mutuas entre varias clases UI: botones, casillas y
etiquetas de texto.
Aplicabilidad
Utiliza el patrón Mediator cuando resulte difícil cambiar algu-
nas de las clases porque están fuertemente acopladas a un pu-
ñado de otras clases.
Cómo implementarlo
1. Identifica un grupo de clases fuertemente acopladas que se
beneficiarían de ser más independientes (p. ej., para un mante-
nimiento más sencillo o una reutilización más simple de esas
clases).
Pros y contras
• Principio de responsabilidad única. Puedes extraer las comu-
nicaciones entre varios componentes dentro de un único sitio,
haciéndolo más fácil de comprender y mantener.
MEMENTO
También llamado: Recuerdo, Instantánea, Snapshot
Problema
Imagina que estás creando una aplicación de edición de texto.
Además de editar texto, tu programa puede formatearlo, asi
como insertar imágenes en línea, etc.
Solución
Todos los problemas que hemos experimentado han sido pro-
vocados por una encapsulación fragmentada. Algunos objetos
intentan hacer más de lo que deben. Para recopilar los datos
necesarios para realizar una acción, invaden el espacio privado
de otros objetos en lugar de permitir a esos objetos realizar la
propia acción.
Estructura
Implementación basada en clases anidadas
Pseudocódigo
Este ejemplo utiliza el patrón Memento junto al patrón Com-
mand para almacenar instantáneas del estado complejo del
editor de texto y restaurar un estado previo a partir de estas
instantáneas cuando sea necesario.
24
25 // La clase memento almacena el estado pasado del editor.
26 class Snapshot is
27 private field editor: Editor
28 private field text, curX, curY, selectionWidth
29
30 constructor Snapshot(editor, text, curX, curY, selectionWidth) is
31 this
this.editor = editor
32 this
this.text = text
33 this
this.curX = curX
34 this
this.curY = curY
35 this
this.selectionWidth = selectionWidth
36
37 // En cierto punto, puede restaurarse un estado previo del
38 // editor utilizando un objeto memento.
39 method restore() is
40 editor.setText(text)
41 editor.setCursor(curX, curY)
42 editor.setSelectionWidth(selectionWidth)
43
44 // Un objeto de comando puede actuar como cuidador. En este
45 // caso, el comando obtiene un memento justo antes de cambiar el
46 // estado del originador. Cuando se solicita deshacer, restaura
47 // el estado del originador a partir del memento.
48 class Command is
49 private field backup: Snapshot
50
51 method makeBackup() is
52 backup = editor.createSnapshot()
53
54 method undo() is
55 if (backup != null
null)
354 Patrones de comportamiento / Memento
56 backup.restore()
57 // ...
Aplicabilidad
Utiliza el patrón Memento cuando quieras producir instantá-
neas del estado del objeto para poder restaurar un estado pre-
vio del objeto.
Cómo implementarlo
1. Determina qué clase jugará el papel de la originadora. Es im-
portante saber si el programa utiliza un objeto central de este
tipo o varios más pequeños.
El tipo de retorno del método debe ser del mismo que la in-
terfaz que extrajiste en el paso anterior (asumiendo que lo hi-
356 Patrones de comportamiento / Memento
Pros y contras
Puedes producir instantáneas del estado del objeto sin violar
su encapsulación.
357 Patrones de comportamiento / Memento
OBSERVER
También llamado: Observador, Publicación-Suscripción, Modelo-
patrón, Event-Subscriber, Listener
Problema
Imagina que tienes dos tipos de objetos: un objeto Cliente
y un objeto Tienda . El cliente está muy interesado en una
marca particular de producto (digamos, un nuevo modelo de
iPhone) que estará disponible en la tienda muy pronto.
Solución
El objeto que tiene un estado interesante suele denominarse suje-
to, pero, como también va a notificar a otros objetos los cambios
en su estado, le llamaremos notificador (en ocasiones también lla-
mado publicador). El resto de los objetos que quieren conocer los
cambios en el estado del notificador, se denominan suscriptores.
Estructura
Pseudocódigo
En este ejemplo, el patrón Observer permite al objeto editor
de texto notificar a otros objetos tipo servicio sobre los cam-
bios en su estado.
365 Patrones de comportamiento / Observer
28
29 // Los métodos de la lógica de negocio pueden notificar los
30 // cambios a los suscriptores.
31 method openFile(path) is
32 this
this.file = new File(path)
33 events.notify("open", file.name)
34
35 method saveFile() is
36 file.write()
37 events.notify("save", file.name)
38
39 // ...
40
41
42 // Aquí está la interfaz suscriptora. Si tu lenguaje de
43 // programación soporta tipos funcionales, puedes sustituir toda
44 // la jerarquía suscriptora por un grupo de funciones.
45
46
47 interface EventListener is
48 method update(filename)
49
50 // Los suscriptores concretos reaccionan a las actualizaciones
51 // emitidas por el notificador al que están unidos.
52 class LoggingListener implements EventListener is
53 private field log: File
54 private field message
55
56 constructor LoggingListener(log_filename, message) is
57 this
this.log = new File(log_filename)
58 this
this.message = message
59
368 Patrones de comportamiento / Observer
60 method update(filename) is
61 log.write(replace('%s',filename,message))
62
63 class EmailAlertsListener implements EventListener is
64 private field email: string
65
66 constructor EmailAlertsListener(email, message) is
67 this
this.email = email
68 this
this.message = message
69
70 method update(filename) is
71 system.email(email, replace('%s',filename,message))
72
73
74 // Una aplicación puede configurar notificadores y suscriptores
75 // durante el tiempo de ejecución.
76 class Application is
77 method config() is
78 editor = new TextEditor()
79
80 logger = new LoggingListener(
81 "/path/to/log.txt",
82 "Someone has opened the file: %s");
83 editor.events.subscribe("open", logger)
84
85 emailAlerts = new EmailAlertsListener(
86 "admin@example.com",
87 "Someone has changed the file: %s")
88 editor.events.subscribe("save", emailAlerts)
369 Patrones de comportamiento / Observer
Aplicabilidad
Utiliza el patrón Observer cuando los cambios en el estado de
un objeto puedan necesitar cambiar otros objetos y el grupo
de objetos sea desconocido de antemano o cambie dinámica-
mente.
Cómo implementarlo
1. Repasa tu lógica de negocio e intenta dividirla en dos partes:
la funcionalidad central, independiente del resto de código,
actuará como notificador; el resto se convertirá en un grupo de
clases suscriptoras.
Pros y contras
Principio de abierto/cerrado. Puedes introducir nuevas clases
suscriptoras sin tener que cambiar el código de la notificadora
(y viceversa si hay una interfaz notificadora).
Puedes establecer relaciones entre objetos durante el tiempo
de ejecución.
STATE
También llamado: Estado
Problema
El patrón State está estrechamente relacionado con el concep-
to de la Máquina de estados finitos.
1 class Document is
2 field state: string
3 // ...
4 method publish() is
5 switch (state)
6 "draft":
7 state = "moderation"
8 break
9 "moderation":
10 if (currentUser.role == 'admin')
11 state = "published"
12 break
13 "published":
14 // No hacer nada.
15 break
16 // ...
Solución
El patrón State sugiere que crees nuevas clases para todos los
estados posibles de un objeto y extraigas todos los comporta-
mientos específicos del estado para colocarlos dentro de esas
clases.
Estructura
381 Patrones de comportamiento / State
Pseudocódigo
En este ejemplo, el patrón State permite a los mismos contro-
les del reproductor de medios comportarse de forma diferente,
dependiendo del estado actual de reproducción.
33 method clickNext() is
34 state.clickNext()
35 method clickPrevious() is
36 state.clickPrevious()
37
38 // Un estado puede invocar algunos métodos del servicio en
39 // el contexto.
40 method startPlayback() is
41 // ...
42 method stopPlayback() is
43 // ...
44 method nextSong() is
45 // ...
46 method previousSong() is
47 // ...
48 method fastForward(time) is
49 // ...
50 method rewind(time) is
51 // ...
52
53
54 // La clase estado base declara métodos que todos los estados
55 // concretos deben implementar, y también proporciona una
56 // referencia inversa al objeto de contexto asociado con el
57 // estado. Los estados pueden utilizar la referencia inversa
58 // para dirigir el contexto a otro estado.
59 abstract class State is
60 protected field player: AudioPlayer
61
62 // El contexto se pasa a sí mismo a través del constructor
63 // del estado. Esto puede ayudar al estado a extraer
64 // información de contexto útil si la necesita.
385 Patrones de comportamiento / State
65 constructor State(player) is
66 this
this.player = player
67
68 abstract method clickLock()
69 abstract method clickPlay()
70 abstract method clickNext()
71 abstract method clickPrevious()
72
73
74 // Los estados concretos implementan varios comportamientos
75 // asociados a un estado del contexto.
76 class LockedState extends State is
77
78 // Cuando desbloqueas a un jugador bloqueado, puede asumir
79 // uno de dos estados.
80 method clickLock() is
81 if (player.playing)
82 player.changeState(new
new PlayingState(player))
83 else
84 player.changeState(new
new ReadyState(player))
85
86 method clickPlay() is
87 // Bloqueado, no hace nada.
88
89 method clickNext() is
90 // Bloqueado, no hace nada.
91
92 method clickPrevious() is
93 // Bloqueado, no hace nada.
94
95 // También pueden disparar transiciones de estado en el
96 // contexto.
386 Patrones de comportamiento / State
129 else
130 player.rewind(5)
Aplicabilidad
Utiliza el patrón State cuando tengas un objeto que se com-
porta de forma diferente dependiendo de su estado actual, el
número de estados sea enorme y el código específico del esta-
do cambie con frecuencia.
Cómo implementarlo
1. Decide qué clase actuará como contexto. Puede ser una clase
existente que ya tiene el código dependiente del estado, o una
nueva clase, si el código específico del estado está distribuido
a lo largo de varias clases.
Pros y contras
Principio de responsabilidad única. Organiza el código relacio-
nado con estados particulares en clases separadas.
Principio de abierto/cerrado. Introduce nuevos estados sin cam-
biar clases de estado existentes o la clase contexto.
390 Patrones de comportamiento / State
STRATEGY
También llamado: Estrategia
Problema
Un día decidiste crear una aplicación de navegación para via-
jeros ocasionales. La aplicación giraba alrededor de un bonito
mapa que ayudaba a los usuarios a orientarse rápidamente en
cualquier ciudad.
Solución
El patrón Strategy sugiere que tomes esa clase que hace algo
específico de muchas formas diferentes y extraigas todos esos
algoritmos para colocarlos en clases separadas llamadas estra-
tegias.
Estructura
Pseudocódigo
En este ejemplo, el contexto utiliza varias estrategias para eje-
cutar diversas operaciones aritméticas.
16 method execute(a, b) is
17 return a - b
18
19 class ConcreteStrategyMultiply implements Strategy is
20 method execute(a, b) is
21 return a * b
22
23 // El contexto define la interfaz de interés para los clientes.
24 class Context is
25 // El contexto mantiene una referencia a uno de los objetos
26 // de estrategia. El contexto no conoce la clase concreta de
27 // una estrategia. Debe trabajar con todas las estrategias a
28 // través de la interfaz estrategia.
29 private strategy: Strategy
30
31 // Normalmente, el contexto acepta una estrategia a través
32 // del constructor y también proporciona un setter
33 // (modificador) para poder cambiar la estrategia durante el
34 // tiempo de ejecución.
35 method setStrategy(Strategy strategy) is
36 this
this.strategy = strategy
37
38 // El contexto delega parte del trabajo al objeto de
39 // estrategia en lugar de implementar varias versiones del
40 // algoritmo por su cuenta.
41 method executeStrategy(int a, int b) is
42 return strategy.execute(a, b)
43
44
45 // El código cliente elige una estrategia concreta y la pasa al
46 // contexto. El cliente debe conocer las diferencias entre
47 // estrategias para elegir la mejor opción.
400 Patrones de comportamiento / Strategy
48 class ExampleApplication is
49 method main() is
50 Create context object.
51
52 Read first number.
53 Read last number.
54 Read the desired action from user input.
55
56 if (action == addition) then
57 context.setStrategy(new
new ConcreteStrategyAdd())
58
59 if (action == subtraction) then
60 context.setStrategy(new
new ConcreteStrategySubtract())
61
62 if (action == multiplication) then
63 context.setStrategy(new
new ConcreteStrategyMultiply())
64
65 result = context.executeStrategy(First number, Second number)
66
67 Print result.
Aplicabilidad
Utiliza el patrón Strategy cuando quieras utiliza distintas va-
riantes de un algoritmo dentro de un objeto y poder cambiar
de un algoritmo a otro durante el tiempo de ejecución.
Cómo implementarlo
1. En la clase contexto, identifica un algoritmo que tienda a sufrir
cambios frecuentes. También puede ser un enorme condicio-
nal que seleccione y ejecute una variante del mismo algoritmo
durante el tiempo de ejecución.
Pros y contras
Puedes intercambiar algoritmos usados dentro de un objeto
durante el tiempo de ejecución.
Puedes aislar los detalles de implementación de un algoritmo
del código que lo utiliza.
Puedes sustituir la herencia por composición.
TEMPLATE METHOD
También llamado: Método plantilla
Problema
Imagina que estás creando una aplicación de minería de datos
que analiza documentos corporativos. Los usuarios suben a la
aplicación documentos en varios formatos (PDF, DOC, CSV) y
ésta intenta extraer la información relevante de estos docu-
mentos en un formato uniforme.
Solución
El patrón Template Method sugiere que dividas un algoritmo
en una serie de pasos, conviertas estos pasos en métodos y
coloques una serie de llamadas a esos métodos dentro de un
único método plantilla. Los pasos pueden ser abstractos , o
contar con una implementación por defecto. Para utilizar el
algoritmo, el cliente debe aportar su propia subclase, imple-
mentar todos los pasos abstractos y sobrescribir algunos de los
opcionales si es necesario (pero no el propio método plantilla).
Estructura
Pseudocódigo
En este ejemplo, el patrón Template Method proporciona un
“esqueleto” para varias ramas de inteligencia artificial (IA) en
un sencillo videojuego de estrategia.
29 else
30 sendWarriors(enemy.position)
31
32 abstract method sendScouts(position)
33 abstract method sendWarriors(position)
34
35 // Las clases concretas tienen que implementar todas las
36 // operaciones abstractas de la clase base, pero no deben
37 // sobrescribir el propio método plantilla.
38 class OrcsAI extends GameAI is
39 method buildStructures() is
40 if (there are some resources) then
41 // Construye granjas, después cuarteles y después
42 // fortaleza.
43
44 method buildUnits() is
45 if (there are plenty of resources) then
46 if (there are no scouts)
47 // Crea peón y añádelo al grupo de exploradores.
48 else
49 // Crea soldado, añádelo al grupo de guerreros.
50
51 // ...
52
53 method sendScouts(position) is
54 if (scouts.length > 0) then
55 // Envía exploradores a posición.
56
57 method sendWarriors(position) is
58 if (warriors.length > 5) then
59 // Envía guerreros a posición.
60
416 Patrones de comportamiento / Template Method
Aplicabilidad
Utiliza el patrón Template Method cuando quieras permitir a
tus clientes que extiendan únicamente pasos particulares de
un algoritmo, pero no todo el algoritmo o su estructura.
Cómo implementarlo
1. Analiza el algoritmo objetivo para ver si puedes dividirlo en
pasos. Considera qué pasos son comunes a todas las subclases
y cuáles siempre serán únicos.
Pros y contras
Puedes permitir a los clientes que sobrescriban tan solo cier-
tas partes de un algoritmo grande, para que les afecten menos
los cambios que tienen lugar en otras partes del algoritmo.
Puedes colocar el código duplicado dentro de una superclase.
VISITOR
También llamado: Visitante
Problema
Imagina que tu equipo desarrolla una aplicación que funcio-
na con información geográfica estructurada como un enor-
me grafo. Cada nodo del grafo puede representar una entidad
compleja, como una ciudad, pero también cosas más específi-
cas, como industrias, áreas turísticas, etc. Los nodos están co-
nectados con otros si hay un camino entre los objetos reales
que representan. Técnicamente, cada tipo de nodo está repre-
sentado por su propia clase, mientras que cada nodo específi-
co es un objeto.
Había otra razón para el rechazo. Era muy probable que, una
vez que se implementara esta función, alguien del departa-
mento de marketing te pidiera que incluyeras la capacidad de
exportar a otros formatos, o te pidiera alguna otra cosa extra-
ña. Esto te forzaría a cambiar de nuevo esas preciadas y frági-
les clases.
423 Patrones de comportamiento / Visitor
Solución
El patrón Visitor sugiere que coloques el nuevo comporta-
miento en una clase separada llamada visitante, en lugar de
intentar integrarlo dentro de clases existentes. El objeto que
originalmente tenía que realizar el comportamiento se pasa
ahora a uno de los métodos del visitante como argumento, de
modo que el método accede a toda la información necesaria
contenida dentro del objeto.
1 // Código cliente
2 foreach (Node node in graph)
3 node.accept(exportVisitor)
4
5 // Ciudad
6 class City is
7 method accept(Visitor v) is
8 v.doForCity(this
this)
9 // ...
10
11 // Industria
12 class Industry is
13 method accept(Visitor v) is
14 v.doForIndustry(this
this)
15 // ...
Estructura
Pseudocódigo
En este ejemplo, el patrón Visitor añade soporte de exporta-
ción XML a la jerarquía de clases de formas geométricas.
429 Patrones de comportamiento / Visitor
12 // ...
13
14 // Observa que invocamos `visitDot`, que coincide con el
15 // nombre de la clase actual. De esta forma, hacemos saber
16 // al visitante la clase del elemento con el que trabaja.
17 method accept(v: Visitor) is
18 v.visitDot(this
this)
19
20 class Circle implements Shape is
21 // ...
22 method accept(v: Visitor) is
23 v.visitCircle(this
this)
24
25 class Rectangle implements Shape is
26 // ...
27 method accept(v: Visitor) is
28 v.visitRectangle(this
this)
29
30 class CompoundShape implements Shape is
31 // ...
32 method accept(v: Visitor) is
33 v.visitCompoundShape(this
this)
34
35
36 // La interfaz Visitor declara un grupo de métodos de visita que
37 // se corresponden con clases de elemento. La firma de un método
38 // de visita permite al visitante identificar la clase exacta
39 // del elemento con el que trata.
40 interface Visitor is
41 method visitDot(d: Dot)
42 method visitCircle(c: Circle)
43 method visitRectangle(r: Rectangle)
431 Patrones de comportamiento / Visitor
Aplicabilidad
Utiliza el patrón Visitor cuando necesites realizar una opera-
ción sobre todos los elementos de una compleja estructura de
objetos (por ejemplo, un árbol de objetos).
Cómo implementarlo
1. Declara la interfaz visitante con un grupo de métodos “visitan-
tes”, uno por cada clase de elemento concreto existente en el
programa.
Pros y contras
Principio de abierto/cerrado. Puedes introducir un nuevo com-
portamiento que puede funcionar con objetos de clases dife-
rentes sin cambiar esas clases.
Principio de responsabilidad única. Puedes tomar varias versio-
nes del mismo comportamiento y ponerlas en la misma clase.
Un objeto visitante puede acumular cierta información útil
mientras trabaja con varios objetos. Esto puede resultar útil
435 Patrones de comportamiento / Visitor
Debes actualizar todos los visitantes cada vez que una clase se
añada o elimine de la jerarquía de elementos.
Los visitantes pueden carecer del acceso necesario a los cam-
pos y métodos privados de los elementos con los que se supo-
ne que deben trabajar.
• Puedes utilizar Visitor junto con Iterator para recorrer una es-
tructura de datos compleja y ejecutar alguna operación sobre
sus elementos, incluso aunque todos tengan clases distintas.
Conclusión
¡Felicidades! ¡Has llegado al final del libro!
Sin embargo, hay muchos otros patrones en el mundo. Es-
pero que el libro se convierta en tu punto de partida para
aprender patrones y desarrollar superpoderes para el diseño
de programas.