design-patterns - estructurales - patrones de diseño pdf
Patrón de diseño del constructor: ¿Por qué necesitamos un Director? (7)
Digamos que quieres hacer un pastel sin los ingredientes secos. Lo que va a hacer es simplemente agregar un nuevo método al Director o hacer otro Director. Esto lo protegerá de la complejidad de la herencia y también hará que su código sea más flexible.
Recientemente me he encontrado con el patrón de diseño Builder. Parece que diferentes autores usan "Patrón de generador" para referirse a diferentes sabores, así que permítanme describir el patrón que estoy preguntando.
Tenemos un algoritmo para crear productos , es decir, objetos de diferentes tipos. A un nivel suficientemente alto de abstracción, el algoritmo es el mismo para todos los tipos de productos, pero cada tipo de producto requiere una implementación diferente de cada uno de los pasos abstractos del algoritmo. Por ejemplo, podríamos tener el siguiente algoritmo para hornear pasteles:
1. Add liquids.
2. Mix well.
3. Add dry ingredients.
4. Mix well.
5. Pour batter into baking pan.
6. Bake.
7. Return baked cake.
Diferentes tartas requerirían diferentes implementaciones de estos pasos, es decir, qué líquidos / ingredientes secos usar, a qué velocidad mezclar, cuánto tiempo hornear, etc.
El patrón dice hacerlo así. Para cada producto creamos una clase de constructor concreto con una implementación para cada uno de los pasos anteriores. Todas estas clases se derivan de una clase base de generador abstracto , que es esencialmente una interfaz. Entonces, por ejemplo, tendremos una clase base abstracta de CakeBaker
con métodos virtuales puros AddLiquid()
, MixLiquids()
, etc. Los panaderos de concreto serán subclases concretas, por ejemplo,
class ChocolateCakeBaker : public CakeBaker {
public:
virtual void AddLiquids()
{
// Add three eggs and 1 cup of cream
}
virtual void AddDryIngredients()
{
// Add 2 cups flour, 1 cup sugar, 3 tbsp cocoa powder,
// 2 bars ground chocolate, 2 tsp baking powder
}
...
...
};
El LemonCitrusCakeBaker
también sería una subclase de CakeBaker
, pero usaría diferentes ingredientes y cantidades en sus métodos.
Los diferentes tipos de torta serán igualmente subclases de una clase base abstracta de Cake
.
Finalmente, tenemos una clase para implementar el algoritmo abstracto. Este es el director . En el ejemplo de panadería podríamos llamarlo ExecutiveBaker
. Esta clase aceptaría (del cliente) un objeto constructor concreto y usaría sus métodos para crear y devolver el producto deseado.
Aquí está mi pregunta. ¿Por qué necesitamos que el director esté separado del generador de abstractos? ¿Por qué no enrollarlos en una clase base abstracta de constructor único, haciendo que los métodos públicos del generador de abstractos original estén protegidos (y las subclases concretas anulan estos como antes).
El constructor sabe cómo hacer pasos específicos. El director sabe cómo ensamblar todo usando los pasos del constructor.
Trabajan juntos.
La única fragilidad que puedo ver con este patrón es que el cliente puede llamar a los métodos de Builder directamente sin Director, lo que puede traer algunos problemas e incoherencias (por ejemplo, no llamar al método Init que forma parte de todo el algoritmo)
Estoy de acuerdo contigo. Creo que el otro enfoque es que CakeBaker debería tener un método GetCake () que devuelve un método de pastel (clase Cake) y MakeCake () donde se ejecutará el algoritmo. Eso está bien, pero por otro lado hay una separación responsable allí. Considere constructores abstractos y constructores específicos como constructores de partes de pastel solo y Director como gerente o diseñador cuya responsabilidad es ensamblar y producir un pastel.
La desventaja de los patrones es que contaminan nuestra comprensión del dominio de negocios con términos técnicos y borran nuestro enfoque.
Como lo veo, hay demasiado acoplamiento entre la torta y el conocimiento de cómo hacerlo. Se pueden desacoplar introduciendo una idea de pastel con una receta en nuestro código (más como pedir prestado al mundo real, diseñar nuestro modelo por dominio empresarial). La receta tendría ingredientes y pasos para hornear (solo un nombre de paso, no una implementación real porque las recetas no hacen pasteles) sobre cómo hacer la torta que describe la receta. Nuestro panadero tendría un método BakeCake (receta) y un montón de métodos más pequeños de acuerdo con los pasos de horneado, como mezclar, agregar ingredientes, etc.
Tenga en cuenta que si necesita modelar a un chef en general, no solo a baker cake, también necesitará disociar el conocimiento de cómo hacer bakes con baker. Se podría hacer introduciendo la idea de que el chef tiene una habilidad.
La parte central del patrón Builder se refiere al Abstract Builder y sus subclases (constructores de concreto). De acuerdo con los patrones de diseño de GoF , el director simplemente "notifica al constructor cada vez que se debe construir una parte del producto", lo que el cliente puede hacer perfectamente.
La clase StringBuilder en la API de Java es un ejemplo de un constructor sin el director respectivo, generalmente la clase del cliente lo "dirige".
Además, en Effective Java y Creación y destrucción de objetos de Java , Joshua Bloch sugiere el uso del patrón de creación , y no incluye un director.
La variación de GoF del patrón Builder NO tiene Builder WITHOUT Director. Hay un punto diferente a esto, pero lo explicaré más a fondo.
El punto del patrón Builder es brindarte múltiples formas de crear el mismo objeto. El constructor solo debe tener métodos que construyan diferentes partes de un objeto, pero el algoritmo, la forma en que se ejecutan estas funciones, debe ser la preocupación de Director. Sin el Director, cada cliente tendría la necesidad de saber EXACTAMENTE cómo funciona el edificio. Pero con el Director, todo lo que el Cliente debe saber es qué Builder utilizar en un caso específico.
Entonces, lo que tenemos aquí son dos partes:
- Constructor, que crea partes del objeto una por una. Lo importante a tener en cuenta es que para esto mantiene el estado del objeto creado.
- Director, que controla la forma en que se ejecuta el constructor.
Ahora al punto al que me refería anteriormente. La parte Builder del patrón es útil en otros casos y ha sido utilizada por diferentes proveedores SIN el Director para diferentes propósitos. Un ejemplo concreto de tal uso sería el Generador de consultas de Doctrine .
La desventaja de tal enfoque es que cuando el Generador comienza a construir un objeto, se vuelve con estado y si el Cliente no reinicia el Generador después de que se creó el objeto, otro Cliente o el mismo Cliente que se haya usado más de una vez podría obtener las partes. del objeto que fue creado anteriormente. Por esta razón, Doctrine usa un patrón de fábrica para crear cada instancia del Generador.
Espero que esto ayude a esos googlear.
Si se separa en Director y Generador, ha documentado la responsabilidad diferente de ensamblar un producto de un conjunto de partes (director) y la responsabilidad de crear la pieza (constructor).
- En el constructor puedes cambiar cómo se construye una parte. En su caso, si un
AddLiquid()
debe agregar crema o leche. - En el director puede cambiar cómo ensamblar las piezas. En su caso, utilizando
AddChocolate()
lugar deAddFruits()
obtendrá un pastel diferente.
Si desea esta flexibilidad adicional, cambiaría su nombre a (ya que el uso de Baker en el constructor sugiere, fue el trabajo de los constructores de ensamblar las piezas)
class LightBakingSteps : public BakingSteps {
public:
virtual void AddLiquids()
{
// Add milk instead of cream
}
virtual void AddDryIngredients()
{
// Add light sugar
}
...
};
class ChoclateCakeBaker : public CakeBaker {
public:
Cake Bake(BakingSteps& steps)
{
steps.AddLiquieds();
steps.AddChocolate(); // chocolate instead of fruits
return builder.getCake();
}
}