¡Hola! Hoy continuaremos estudiando patrones de diseño y hablaremos sobre el método de fábrica (FactoryMethod). Descubrirá qué es y para qué tareas es adecuada esta plantilla. Examinaremos este patrón de diseño en la práctica y exploraremos su estructura. Para que todo lo anterior le quede claro, debe comprender los siguientes temas:
- Herencia en Java.
- Métodos y clases abstractos en Java.
¿Qué problema resuelve el método de fábrica?
En todos los patrones de diseño de fábricas, hay dos grupos de participantes: creadores (las propias fábricas) y productos (los objetos creados por las fábricas). Imagínese la situación: tenemos una fábrica que produce automóviles con la marca AutoRush. Sabe crear modelos de coches con diferentes tipos de carrocería:- sedanes
- camionetas
- cupé
- sedanes AutoRush
- Furgonetas AutoRush
- cupé AutoRush
- Sedanes OneAuto
- Camionetas OneAuto
- cupé OneAuto
Un poco sobre la plantilla de fábrica.
Déjame recordarte: construimos una pequeña cafetería virtual contigo. En él aprendimos a crear diferentes tipos de café utilizando una sencilla fábrica. Hoy perfeccionaremos este ejemplo. Recordemos cómo era nuestra cafetería con una sencilla fábrica. Tuvimos una clase de café:public class Coffee {
public void grindCoffee(){
// перемалываем кофе
}
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Y también varios de sus herederos - tipos específicos de café que nuestra fábrica podía producir:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Para facilitar la aceptación de pedidos, hemos introducido transferencias:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
La fábrica de café en sí tenía este aspecto:
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 por último, la cafetería en sí:
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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
Modernización de una fábrica sencilla.
Nuestra cafetería está funcionando bien. Tanto es así que estamos pensando en ampliar. Queremos abrir varios puntos nuevos. Como emprendedores, no crearemos cafeterías monótonas. Quiero que cada uno tenga su propio toque. Por eso, para empezar, abriremos dos puntos: en estilo italiano y americano. Los cambios afectarán no sólo al interior, sino también a las bebidas:- En una cafetería italiana utilizaremos café exclusivamente de marcas italianas, con molienda y tostado especiales.
- La porción americana será un poco más grande y con cada pedido serviremos malvaviscos derretidos: malvaviscos.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Y se convierte en 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 {}
Como queremos mantener el modelo de negocio actual sin cambios, queremos que el método orderCoffee(CoffeeType type)
experimente una cantidad mínima de cambios. Echemos un vistazo:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
¿Qué opciones tenemos? Ya sabemos escribir fábrica, ¿verdad? Lo más simple que me viene a la mente de inmediato es escribir dos fábricas similares y luego pasar la implementación requerida a nuestra cafetería en el constructor. Entonces la clase de la cafetería no cambiará. Primero, necesitamos crear una nueva clase de fábrica, heredar de nuestra fábrica simple y anular el archivo createCoffee (CoffeeType type)
. Escribamos fábricas para crear café en estilos italiano y 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 requerida a CoffeeShop. Veamos cómo sería el código para pedir café en diferentes cafeterías. Por ejemplo, capuchino en estilos italiano y americano:
public class Main {
public static void main(String[] args) {
/*
Закажем капучино в итальянском стиле:
1. Создадим фабрику для приготовления итальянского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику итальянского кофе
3. Закажем наш кофе
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Закажем капучино в американском стиле
1. Создадим фабрику для приготовления американского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику американского кофе
3. Закажем наш кофе
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Creamos dos cafeterías diferentes, trasladando cada una a la fábrica requerida. Por un lado, hemos logrado nuestro objetivo, pero por otro... Algo rasca el alma incontenible del emprendedor... Averigüemos qué pasa. En primer lugar, la abundancia de fábricas. ¿Es posible crear cada vez tu propia fábrica para un nuevo punto y, además, asegurarte de que al crear una cafetería, la fábrica requerida se transfiera al constructor? En segundo lugar, sigue siendo una fábrica sencilla. Sólo un poco modernizado. Todavía estamos estudiando un nuevo patrón aquí. En tercer lugar, ¿no es posible hacerlo de otra manera? Sería genial si pudiéramos localizar todas las preguntas sobre cómo preparar café dentro del aula CoffeeShop
, vinculando los procesos de creación de café y servicio del pedido, pero al mismo tiempo manteniendo suficiente flexibilidad para preparar café en diferentes estilos. La respuesta es sí, puedes. Esto se denomina 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 el problema de la manera más eficiente posible, nosotros:- Devolvamos el método
createCoffee(CoffeeType type)
a la claseCoffeeShop
. - Hagamos este método abstracto.
- La clase en sí se
CoffeeShop
volverá abstracta. - La clase
CoffeeShop
tendrá herederos.
CoffeeShop
, implementando un método createCoffee(CoffeeType type)
acorde con las mejores tradiciones de los baristas italianos. Entonces, en orden. Paso 1. Hagamos la clase Coffee
abstracta. Ahora tenemos dos familias de productos diferentes. Las bebidas de café italianas y estadounidenses todavía comparten un ancestro común: el Coffee
. Sería correcto hacerlo abstracto:
public abstract class Coffee {
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Paso 2. Hazlo CoffeeShop
abstracto, con un método abstracto.createCoffee(CoffeeType type)
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Paso 3. Crea una cafetería italiana, una clase descendiente de la cafetería abstracta. En él implementamos el método createCoffee(CoffeeType type)
teniendo en cuenta las particularidades 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. Hagamos lo mismo con una cafetería de 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. Echemos un vistazo a cómo sería pedir un café con leche al estilo americano e italiano:
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 en nuestra cafetería.
Cómo funciona el método de fábrica
Ahora echemos un vistazo más de cerca a lo que tenemos. El siguiente diagrama muestra las clases resultantes. Los bloques verdes son clases de creadores, los bloques azules son clases de productos. ¿Qué conclusiones se pueden sacar?- Todos los productos son implementaciones de la clase abstracta
Coffee
. - Todos los creadores son implementaciones de la clase abstracta
CoffeeShop
. - Observamos dos jerarquías de clases paralelas:
- Jerarquía de productos. Vemos descendientes italianos y descendientes americanos.
- Jerarquía de creadores. Vemos descendientes italianos y descendientes americanos.
- La superclase
CoffeeShop
no tiene información sobre qué implementación de producto específica (Coffee
) se creará. - Una superclase
CoffeeShop
delega la creación de un producto específico a sus descendientes. - Cada clase descendiente
CoffeeShop
implementa un método de fábricacreateCoffee()
de acuerdo con sus características específicas. En otras palabras, dentro de las implementaciones de las clases de creador, se toma la decisión de preparar un producto específico en función de las características específicas de la clase de creador.
Estructura del 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 contiene implementaciones de todos los métodos que interactúan con los productos, excepto el método de fábrica.
factoryMethod()
Todos los descendientes de la clase deben implementar un método abstractoCreator
.- La clase
ConcreteCreator
implementa un métodofactoryMethod()
que produce directamente un 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: ser descendientes de una clase de producto común. Esto es necesario para que las clases que utilizan productos puedan operar en ellos a nivel de abstracciones en lugar de implementaciones concretas.
GO TO FULL VERSION