¡Hola! Hoy continuaremos estudiando patrones de diseño y discutiremos el patrón del método de fábrica. Descubrirá qué es y para qué tareas es adecuado este patrón. Consideraremos este patrón de diseño en la práctica y estudiaremos su estructura. Para asegurarse de que todo esté claro, debe comprender los siguientes temas:
- Herencia en Java.
- Clases y métodos abstractos en Java
¿Qué problema resuelve el método de fábrica?
Todos los patrones de diseño de fábricas tienen dos tipos de participantes: creadores (las propias fábricas) y productos (los objetos creados por las fábricas). Imagine la siguiente situación: tenemos una fábrica que produce automóviles con la marca CodeGym. Sabe cómo crear modelos de automóviles con varios tipos de carrocerías:- sedanes
- camionetas
- cupés
- sedán CodeGym
- Furgonetas CodeGym
- CodeGym cupés
- sedán OneAuto
- Furgonetas OneAuto
- Cupés OneAuto
Un poco sobre el patrón de fábrica.
Permítanme recordarles que anteriormente construimos una pequeña cafetería virtual. Con la ayuda de una sencilla fábrica, aprendimos a crear diferentes tipos de café. Hoy volveremos a trabajar este ejemplo. Recordemos cómo se veía nuestra cafetería, con su sencilla fábrica. Tuvimos una clase de café:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Y varias clases secundarias correspondientes a tipos específicos de café que nuestra fábrica podría producir:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Creamos una enumeración para facilitar la realización de pedidos:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
La fábrica de café en sí se veía así:
public class SimpleCoffeeFactory {
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new Americano();
break;
case ESPRESSO:
coffee = new Espresso();
break;
case CAPPUCCINO:
coffee = new Cappuccino();
break;
case CAFFE_LATTE:
coffee = new CaffeLatte();
break;
}
return coffee;
}
}
Y finalmente, la cafetería en sí se veía así:
public class CoffeeShop {
private final SimpleCoffeeFactory coffeeFactory;
public CoffeeShop(SimpleCoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
}
Modernización de una fábrica sencilla
Nuestra cafetería está funcionando muy bien. Tanto es así que estamos pensando en ampliar. Queremos abrir algunas ubicaciones nuevas. Somos audaces y emprendedores, por lo que no crearemos cafeterías aburridas. Queremos que cada tienda tenga un toque especial. En consecuencia, para empezar, abriremos dos ubicaciones: una italiana y otra americana. Estos cambios afectarán no solo al diseño interior, sino también a las bebidas que se ofrecen:- en la cafetería italiana utilizaremos exclusivamente marcas de café italiano, con molienda y tostado especiales.
- la ubicación estadounidense tendrá porciones más grandes y serviremos malvaviscos con cada pedido.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Pero ahora tendremos 8:
public class ItalianStyleAmericano extends Coffee {}
public class ItalianStyleCappucino extends Coffee {}
public class ItalianStyleCaffeLatte extends Coffee {}
public class ItalianStyleEspresso extends Coffee {}
public class AmericanStyleAmericano extends Coffee {}
public class AmericanStyleCappucino extends Coffee {}
public class AmericanStyleCaffeLatte extends Coffee {}
public class AmericanStyleEspresso extends Coffee {}
Dado que queremos mantener el modelo comercial actual, queremos que el orderCoffee(CoffeeType type)
método sufra la menor cantidad de cambios posible. Mira esto:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
¿Qué opciones tenemos? Bueno, ya sabemos cómo escribir una fábrica, ¿verdad? Lo más simple que viene a la mente de inmediato es escribir dos fábricas similares y luego pasar la implementación deseada al constructor de nuestra cafetería. Al hacer esto, la clase de la cafetería no cambiará. Primero, necesitamos crear una nueva clase de fábrica, hacer que herede nuestra fábrica simple y luego anular el createCoffee(CoffeeType type)
método. Escribamos fábricas para crear café estilo italiano y café estilo americano:
public class SimpleItalianCoffeeFactory extends SimpleCoffeeFactory {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
public class SimpleAmericanCoffeeFactory extends SimpleCoffeeFactory{
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Ahora podemos pasar la implementación de fábrica deseada a CoffeeShop. Veamos cómo sería el código para pedir café en diferentes cafeterías. Por ejemplo, capuchino estilo italiano y estilo americano:
public class Main {
public static void main(String[] args) {
/*
Order an Italian-style cappuccino:
1. Create a factory for making Italian coffee
2. Create a new coffee shop, passing the Italian coffee factory to it through the constructor
3. Order our coffee
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Order an American-style cappuccino
1. Create a factory for making American coffee
2. Create a new coffee shop, passing the American coffee factory to it through the constructor
3. Order our coffee
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Creamos dos cafeterías diferentes, pasando la fábrica deseada a cada una. Por un lado, hemos logrado nuestro objetivo, pero por otro lado... De alguna manera esto no les sienta bien a los empresarios... Averigüemos qué es lo que está mal. Primero, la abundancia de fábricas. ¿Qué? Ahora, para cada nueva ubicación, se supone que debemos crear su propia fábrica y, además de eso, asegurarnos de que la fábrica relevante se pase al constructor al crear una cafetería. En segundo lugar, sigue siendo una simple fábrica. Recién modernizado ligeramente. Pero estamos aquí para aprender un nuevo patrón. En tercer lugar, ¿no es posible un enfoque diferente? Sería genial si pudiéramos poner todos los temas relacionados con la preparación del café en elCoffeeShop
clase al vincular los procesos de creación de café y pedidos de servicio, al mismo tiempo que mantiene la flexibilidad suficiente para hacer varios estilos de café. La respuesta es sí, podemos. Esto se llama el patrón de diseño del método de fábrica.
De una simple fábrica a un método de fábrica
Para resolver la tarea de la manera más eficiente posible:- Devolvemos el
createCoffee(CoffeeType type)
método a laCoffeeShop
clase. - Haremos este método abstracto.
- La
CoffeeShop
clase misma se volverá abstracta. - La
CoffeeShop
clase tendrá clases para niños.
CoffeeShop
clase, que implementa el createCoffee(CoffeeType type)
método de acuerdo con las mejores tradiciones de los baristas italianos. Ahora, un paso a la vez. Paso 1. Haz que la Coffee
clase sea abstracta. Disponemos de dos familias completas de productos diferentes. Aún así, los cafés italiano y estadounidense tienen un ancestro común: la Coffee
clase. Sería adecuado hacerlo abstracto:
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Paso 2. Hacer CoffeeShop
abstracto, con un createCoffee(CoffeeType type)
método abstracto
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Paso 3. Cree una cafetería italiana, que es descendiente de la cafetería abstracta. Implementamos el createCoffee(CoffeeType type)
método en él, teniendo en cuenta los detalles de las recetas italianas.
public class ItalianCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
Paso 4. Hacemos lo mismo para la cafetería estilo americano
public class AmericanCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Paso 5. Mira cómo se verán los lattes americanos e italianos:
public class Main {
public static void main(String[] args) {
CoffeeShop italianCoffeeShop = new ItalianCoffeeShop();
italianCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
CoffeeShop americanCoffeeShop = new AmericanCoffeeShop();
americanCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
}
}
Felicidades. Acabamos de implementar el patrón de diseño del método de fábrica usando nuestra cafetería como ejemplo.
El principio detrás de los métodos de fábrica
Ahora consideremos con mayor detalle lo que obtuvimos. El siguiente diagrama muestra las clases resultantes. Los bloques verdes son clases de creadores y los bloques azules son clases de productos. ¿Qué conclusiones podemos sacar?- Todos los productos son implementaciones de la
Coffee
clase abstracta. - Todos los creadores son implementaciones de la
CoffeeShop
clase abstracta. - Vemos dos jerarquías de clases paralelas:
- Jerarquía de productos. Vemos descendientes de italianos y descendientes de estadounidenses.
- Jerarquía de creadores. Vemos descendientes de italianos y descendientes de estadounidenses.
- La
CoffeeShop
superclase no tiene información sobre qué producto específico (Coffee
) se creará. - La
CoffeeShop
superclase delega la creación de un producto específico a sus descendientes. - Cada descendiente de la
CoffeeShop
clase implementa uncreateCoffee()
método de fábrica de acuerdo con sus propias características específicas. En otras palabras, las implementaciones de las clases de productores preparan productos específicos basados en las especificaciones de la clase de productores.
Estructura de un método de fábrica
El diagrama anterior muestra la estructura general del patrón del método de fábrica. ¿Qué más es importante aquí?- La clase Creator implementa todos los métodos que interactúan con los productos, excepto el método de fábrica.
- El método abstracto
factoryMethod()
debe ser implementado por todos los descendientes de laCreator
clase. - La
ConcreteCreator
clase implementa elfactoryMethod()
método, que crea directamente el producto. - Esta clase es responsable de crear productos específicos. Esta es la única clase con información sobre la creación de estos productos.
- Todos los productos deben implementar una interfaz común, es decir, deben ser descendientes de una clase de producto común. Esto es necesario para que las clases que usan productos puedan operar sobre ellos como abstracciones, en lugar de implementaciones específicas.
GO TO FULL VERSION