Patrones de diseño – Factory method

En la entrada sobre patrones de diseño hablamos sobre los patrones y explicamos los distintos tipos. En esta ocasión hablaremos del patrón de creación Factory method.

El propósito de este patrón es abstraer cómo creamos objetos de un tipo concreto sin que nos tengamos que preocupar de los detalles, simplemente le tenemos que proporcionar la información necesaria para que decida qué objeto debe crear.

Factory method

En el post sobre como SOLID nos podía dar flexibilidad vimos cómo aplicar este patrón para crear enemigos. Aquí vamos a ver otro ejemplo común en el mundo de los videojuegos, la creación de power ups. Para esto utilizaremos Scriptable Objects de Unity que nos permiten hacer la configuración desde el editor.

Vamos a ver el diagrama de clases y ahora os cuento:

Factory method
Diagrama factory method

En el diagrama podemos ver que tenemos una clase abstracta PowerUp y dos clases concretas de PowerUp. Aquí hemos utilizado una clase abstracta pero también podría ser una interfaz, lo importante es que sea padre de todos los power ups. Estas clases concretas son las que queremos instanciar mediante la factoría.

PowerUpFactory es la única que se va a encargar de instanciar los PowerUps y sabrá que power up instanciar por la ID que le pasemos.

Como habíamos dicho, vamos a utilizar un ScriptableObject para configurar todos los PowerUp que pueden ser creados, así la factoría no tendrá que ser un MonoBehaviour y la podremos testear fácilmente.

Implementación del patrón Factory method

Configuración de power ups

    public abstract class PowerUp : MonoBehaviour
    {
        [SerializeField] protected string id;

        public string Id => id;
    }
    [CreateAssetMenu(menuName = "Custom/Power up configuration")]
    public class PowerUpsConfiguration : ScriptableObject
    {
        [SerializeField] private PowerUp[] powerUps;
        private Dictionary<string, PowerUp> idToPowerUp;

        private void Awake()
        {
            idToPowerUp = new Dictionary<string, PowerUp>(powerUps.Length);
            foreach (var powerUp in powerUps)
            {
                idToPowerUp.Add(powerUp.Id, powerUp);
            }
        }

        public PowerUp GetPowerUpPrefabById(string id)
        {
            if (!idToPowerUp.TryGetValue(id, out var powerUp))
            {
                throw new Exception($"PowerUp with id {id} does not exit");
            }
            return powerUp;
        }
    }

La clase factoría

    public class PowerUpsFactory
    {
        private readonly PowerUpsConfiguration powerUpsConfiguration;

        public PowerUpsFactory(PowerUpsConfiguration powerUpsConfiguration)
        {
            this.powerUpsConfiguration = powerUpsConfiguration;
        }
        
        public PowerUp Create(string id)
        {
            var prefab = powerUpsConfiguration.GetPowerUpPrefabById(id);

            return Object.Instantiate(prefab);
        }
    }

Con esto ya tendríamos nuestra factoría funcionando, ahora solo tendríamos que ponerlo todo en común.

PowerUpSpawner, el consumidor

public class PowerUpSpawner
    {
        private readonly PowerUpsFactory powerUpsFactory;

        public PowerUpSpawner(PowerUpsFactory powerUpsFactory)
        {
            this.powerUpsFactory = powerUpsFactory;
        }
        
        // Logic

        private void SpawnPowerUp(string id)
        {
            powerUpsFactory.Create(id);
        }
    }

Fijaros en que no necesitamos que sea MonoBehavior y gracias a eso lo podremos testear.

Installer

Para ponerlo en común vamos a utilizar una clase Installer que no es otra cosa que el punto de entrada a nuestro juego. El Installer se encargaría de resolver todas las dependencias necesarias y crear los objetos iniciales, cuando esto crezca simplemente lo dividiremos en sub-installers.

public class GameInstaller : MonoBehaviour
    {
        [SerializeField] private PowerUpsConfiguration powerUpsConfiguration;

        private void Start()
        {
            var powerUpsFactory = new PowerUpsFactory(Instantiate(powerUpsConfiguration));
            var powerUpSpawner = new PowerUpSpawner(powerUpsFactory); // PowerUpSpawner podría ser un MonoBehaviour y simplemente instanciarlo
        }
    }

Conclusión

En esta entrada hemos visto lo sencillo que es aplicar el patrón de factoría y los enormes beneficios que nos aporta. Esto junto a resto de patrones y Solid, pueden marcar una gran diferencia en nuestro código.

Puedes descargarte el código de ejemplo AQUÍ.

Como siempre digo, no te quedes con la duda y pregúntanos en un comentario 🙂.

Fuentes

Otros patrones de diseño

Resumen
➤ Patrones de diseño - Factory method
Nombre del artículo
➤ Patrones de diseño - Factory method
Descripción
En este post aprenderemos a utilizar el PATRON de DISEÑO 🏭 Factory Method, y lo veremos utilizando ejemplos reales 💻 del mundo de los videojuegos 🕹.
Autor
Publisher Name
The power ups
Publisher Logo