¡Entendamos el método de fábrica con un ejemplo!

Digamos que estamos creando un juego que tiene diferentes tipos de personajes, como guerreros, magos y arqueros. Cada uno de estos personajes tiene un conjunto específico de habilidades y ataques.

Veamos cómo se ve la estructura del código sin implementar el patrón de fábrica.

guerrero de clase pública {
@Anular
ataque de vacío público () {
System.out.println(“¡El guerrero ataca con una espada!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡El guerrero defiende con un escudo!”);
}
}

Asistente de clase pública{
@Anular
ataque de vacío público () {
System.out.println(“¡El mago ataca con magia!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡El mago defiende con un hechizo!”);
}
}

Arquero de clase pública {
@Anular
ataque de vacío público () {
System.out.println(“¡El arquero ataca con un arco!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡Archer se defiende esquivando!”);
}
}

Juego de clase pública {
principal vacío estático público (cadena[] argumentos) {
// Crea un personaje guerrero
Personaje guerrero = nuevo guerrero();
guerrero.ataque();
guerrero.defender();

// Crea un personaje mago
Asistente de personajes = nuevo asistente();
mago.ataque();
mago.defender();

// Crea un personaje Arquero
Personaje arquero = nuevo Arquero();
arquero.ataque();
arquero.defender();

// hacer algo con los personajes…
}
}

En esta versión del código, creamos manualmente cada tipo de objeto de personaje (Guerrero, Mago y Arquero) y llamamos a sus respectivos métodos de ataque() y defensa() directamente. Este enfoque puede funcionar para proyectos pequeños con una cantidad limitada de objetos, pero puede volverse difícil de manejar y de mantener a medida que aumenta la cantidad de objetos y su lógica asociada.

¿Cuál es el problema en este diseño de código?

La clase Juego debe modificarse cada vez que se agrega un nuevo tipo de personaje, lo que puede generar problemas de mantenimiento, ya que la clase se vuelve más compleja y difícil de mantener. Por ejemplo, si se agrega un nuevo tipo de personaje “Caballero” al juego, necesitaríamos modificar la clase Juego para agregar un nuevo caso de cambio para “Caballero” e implementar la lógica para crear un nuevo objeto Caballero.

¡Veamos cómo implementar el mismo código usando el método de fábrica!

¡Cómo ayuda la interfaz!

Primero, definiremos una interfaz llamada Personaje que representa un personaje genérico en el juego:

Carácter de interfaz pública {
ataque vacío();
defensa nula();
}

A continuación, crearemos clases concretas que implementen la interfaz Character. Aquí hay un ejemplo de una clase de Guerrero:

Personaje del juego guerreroclase pública Guerrero implementa Carácter {
@Anular
ataque de vacío público () {
System.out.println(“¡El guerrero ataca con una espada!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡El guerrero defiende con un escudo!”);
}
}

De manera similar, crearemos clases Wizard y Archer que implementen la interfaz Character.

Personaje del juego magoEl asistente de clase pública implementa el carácter {
@Anular
ataque de vacío público () {
System.out.println(“¡El mago ataca con magia!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡El mago defiende con un hechizo!”);
}
}

Personaje del juego arqueroclase pública Archer implementa Carácter {
@Anular
ataque de vacío público () {
System.out.println(“¡El arquero ataca con un arco!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡Archer se defiende esquivando!”);
}
}

A continuación definiremos una clase abstracta llamado CharacterFactory que define un método de fábrica para crear personajes:

clase abstracta pública CharacterFactory {
Carácter abstracto público createCharacter();
}

Observe que el método createCharacter() es abstracto, lo que significa que cada subclase de CharacterFactory tendrá que proporcionar su propia implementación de este método.

Ahora, podemos crear clases concretas que extiendan CharacterFactory e implementen el método createCharacter() para devolver un tipo específico de carácter. A continuación se muestra un ejemplo de WarriorFactory:

clase pública WarriorFactory extiende CharacterFactory {
@Anular
Carácter público crear carácter() {
devolver nuevo guerrero();
}
}

De manera similar, crearemos clases WizardFactory y ArcherFactory que extienden CharacterFactory e implementan el método createCharacter() para devolver un Wizard o Archer, respectivamente.

Finalmente, podemos usar CharacterFactory y sus subclases para crear nuevos personajes en nuestra clase Game:

Juego de clase pública {
principal vacío estático público (cadena[] argumentos) {
Fábrica de CharacterFactory = nueva WarriorFactory();
Carácter carácter = factory.createCharacter();
personaje.ataque();
personaje.defender();
}
}

En este ejemplo, tenemos tres clases que implementan la interfaz del personaje: guerrero, mago y arquero. También tenemos tres clases de fábrica: WarriorFactory, WizardFactory y ArcherFactory, cada una de las cuales crea el tipo apropiado de objeto Character.

En la clase Juego, creamos un objeto de fábrica (CharacterFactory) y luego lo usamos para crear un objeto Personaje (personaje). Luego podemos usar los métodos de ataque y defensa en el objeto del personaje sin necesidad de saber la clase exacta de objeto que se creó.

La ventaja de utilizar el patrón Factory Method es que nos permite crear objetos sin acoplar estrechamente nuestro código a clases específicas. Esto hace que nuestro código sea más flexible y fácil de mantener, ya que podemos intercambiar fácilmente diferentes implementaciones de Character sin tener que modificar el resto de nuestro código.

Ahora veamos la verdadera magia.

Personaje del juego Ninja

Digamos que queremos agregar un nuevo tipo de personaje a nuestro juego, como un personaje “Ninja”.

Si usamos el patrón de diseño Factory Method, podemos agregar fácilmente un nuevo tipo de personaje creando una nueva clase de fábrica para ese tipo, sin modificar la clase Game:

clase pública Ninja implementa Carácter {
@Anular
ataque de vacío público () {
System.out.println(“¡El mago ataca con magia!”);
}

@Anular
defensa nula pública() {
System.out.println(“¡El mago defiende con un hechizo!”);
}
}

clase pública NinjaFactory extiende CharacterFactory {
@Anular
Carácter público crear carácter() {
devolver nuevo Ninja();
}
}

Juego de clase pública {
principal vacío estático público (cadena[] argumentos) {
Fábrica de CharacterFactory = nueva NinjaFactory();
Personaje ninja = factory.createCharacter();
ninja.ataque();
ninja.defender();
}
}

Como podemos ver, simplemente creamos una nueva clase NinjaFactory que extiende la clase abstracta CharacterFactory e implementa el método createCharacter para devolver un nuevo objeto Ninja. Luego, en la clase Game, usamos NinjaFactory para crear un nuevo objeto Ninja sin hacer referencia directa a la clase Ninja.

De esta manera, podemos agregar fácilmente nuevos tipos de personajes a nuestro juego creando nuevas clases de fábrica para ellos, sin modificar la lógica del juego existente en la clase Juego. Esto hace que nuestro código sea más modular y flexible, y más fácil de mantener y ampliar en el futuro.