Cuando utilizar el Patrón de Diseño Factory en Laravel

Spread the love

Hace poco me preguntaron para que servía exactamente el Patrón Factory y si era algo que se podía usar en Laravel.

La respuesta a la primera pregunta:

El patrón Factory:

Nos ayuda a crear instancias de otras clases, con la finalidad de ocultar la complejidad que se requiere para crearlas.

y a la segunda: claro que puedes usarlo en Laravel!

Es mas, Laravel utiliza este patrón en varias partes de su código.

Los patrones de diseños son ideas, y como tales lo mas importante es entender para que sirven y luego ver como podemos usar esa idea en el código.

Bien déjame presentarte al Patrón Factory en su forma mas básica.

En código esto puede representarse de la siguiente forma.

class SimpleFactory 
{
    public static function make() {
    
        return new OneClass();
    
    }
}

Como puedes apreciar es solo una clase con un método que crea una instancia de otra!

y si lo pensamos un poco mas, también puede crear mas de una instancia.

nada fuera de lo común, la forma de usar esta clase es como sigue.

Una clase que se conoce como «Cliente» que en la mayoría de los casos puede ser nuestro Controller va a usar el SimpleFactory, Para crear otra clase (OneClass) mediante su método make

Todo esto se puede resumir en código como sigue.

$oneClass = SimpleFactory::make();
$oneClass->someMethod();

Sí lo piensas puedes meter lo que necesites en ese método make y obtener la instancia que tu quieras!

¿Cómo puedo utilizar esto en algo real?

Es lo que veremos en este articulo

Pero vamos por pasos, ya sabemos que son los Patrones Factory, ahora debemos saber cuando no y cuando sí utilizar este patrón.

Repositorio

Si quieres ver todos los detalles de la implementación Seba Carrasco Poblete a realizado un gran trabajo creando un repositorio que recrea este ejemplo basándose en la explicación 👍🏼

Video

En el vídeo puede ver una implementación del patrón simple factory.

¿Cuando no utilizar el Patrón Factory?

Este patrón como en todas las cosas, tiene un caso en el que no nos beneficia en nada.

y es cuando podemos instanciar una clase con algo tan simple como

User::create([
    'name' => $request->name,
    'email' => $request->email,
    'password' => Hash::make($request->password),
    'state' => User::PENDING,
]);

vamos a suponer que la primera vez que se crea un usuario debe de tener un estado de «pendiente», por lo cual tenemos ‘state’ = User::PENDING. 

Este caso podemos resolverlo estableciendo un valor por defecto, por ejemplo podemos utilizar el arreglo attributes para establecer el valor por defecto en el modelo:

protected $attributes = [
        'status' => self::PENDING,
        
];

Otra opción es establecer el valor en la migración o usar eventos de Eloquent.

en cualquiera de esos casos el cambio hará que tengamos una llamada mas simple:

User::create($request);

Por lo cual usar un factory no nos ayudaría a mejorar nuestro código.

Así que en este punto es posible que te preguntes…

¿Cuando debemos de utilizar el Patrón Factory?

Existen dos casos en los cuales podemos sacar provecho de este patrón y son los siguientes.

Caso 1: El objeto que necesitamos crear, puede cambiar y afectar su utilización

Este caso cubre las situaciones en las que la clase puede cambiar de nombre o puede cambiar la firma de su constructor en cuanto al numero de parámetros o tipo de datos que la clase necesita para instanciarse.

//Antes
public function __construct($type, $data)
{
    // code
}

//Despues
public function __construct(Array $data)
{
    // code
}

Es evidente que un cambio en el nombre de la clase o de la firma del constructor, afectara el uso de la clase en donde sea que la utilices.

Aquí el Factory te protege contra cambios que se originan de estas situaciones.

Un ejemplo practico es en el uso de name constructors, como en el caso de la clase Carbón.

    if(is_string($request->date))
        $this->date = Carbon::createFromFormat('Y-m-d', $request->date);

    if(is_array($request->date) && count($request->date) == 3 && Arr::isAssoc($request->date))
        $this->date =  Carbon::create(...array_values($request->date));

    if($this->date == null)
        response()->json('Invalid Format', 422);

    $dateRequest = $this->date->format('Y-m-d');

    //...

Como puedes observar, en este caso cambia la firma y el tipo de parámetro que se requiere para crear el objeto Carbon,

Por esta razón requerimos examinar el contenido del $request para decidir que name constructorse va usar para obtener una instancia de Carbon.

Si copiaras esto e intentaras usarlo en otras partes de tu código y luego tuvieras que cambiar algo nuevamente, tendrías que hacerlo en todos los lugares donde usaste este parte del código.

Para evitarte la molestia de tener que hacer todos esos cambios, puedes usar el Simple Factorypara que contenga toda esa lógica y si tienes que cambiar algo.

Solo lo haríamos en el Simple Factory y creo que eso sería genial!

Así que movamos todos esas condiciones a un mejor lugar.

class DateFactory
{
    public static function make($date): Carbon
    {
        if(is_string($date))
            return Carbon::createFromFormat('Y-m-d', $date);

        if(is_array($date) && count($date) == 3 && Arr::isAssoc($date))
            return Carbon::create(...array_values($date));

        throw new \InvalidArgumentException('Invalid type or format arguments');

    }
}

Este cambio nos deja con un código como el siguiente.

$dateRequest = DateFactory::make($request->date)->format('Y-m-d');
//More Code....

¿Qué tal?, se entiende mucho, mucho mejor y simplificamos las cosas.

Caso 2: Cuando no sabemos el tipo de objeto que vamos a recibir (Tiempo de ejecución)

EL tipo de situaciones que derivan de este caso son aquellas en las que se requiere

de lógica de decisión para determinar el tipo de objeto o la forma en la que se va a construir.

Este tipo de decisiones obviamente se determina durante la ejecución y es por eso que no podemos saber de forma anticipada el tipo de objeto que se va a utilizar

¿Cómo es esto?

Un ejemplo practico es por ejemplo cuando podemos decidir el formato para generar y descargar un reporte como en el siguiente ejemplo.

class ReportController 
{
    public function report(Request $request)
    {
        $type = $request->get('type');

        switch($type) {
            case 'pdf': 
                $report = new PdfReport($request);
                break;
            case 'excel':
                $report = new ExcelReport($request);
                break;
            case 'csv':
                $report = new CsvReport($request);
                break;
            default:
                throw new Exception(
                    "No report for type $type";
                );
        }

        return $report->dowload();
    }
}

Como puedes observar el resultado del tipo de reporte depende de la elección que se envía en el request.

Este tipo de lógica puede colocarse en el Simple Factory, para simplificar el controlador y reducir el riesgo de cambio.

Así que hagamos una clase para ese propósito.

class ReportFactory
{
    public static function create(Request $request)
    {
         switch($request->type) {
            case 'pdf': 
                return new PdfReport($request);
            case 'excel':
                return new ExcelReport($request);
            case 'csv':
                return new CsvReport($request);
            default:
                throw new Exception(
                    "No report for type $request->type";
                );
        }   
    }

Ahora veamos como queda nuestro controlador

class ReportController 
{
    public function report(Request $request)
    {
        $report = ReportFactory::create($request);

        return $report->dowload();
    }
}

¿Sencillo no lo crees? con un cambio mínimo, hemos logrado que el código se entienda mucho mejor,

ademas si algo cambia en el Switch no debemos de preocuparnos, porque el controlador puede seguir trabajando si cambios adicionales.

Resumen

Para finalizar recuerda, los mas importante es la idea así que aplica este concepto!!

No tengas miedo, que te valga un pepino como queda, recuerda, nadie va a calificar tu código y no esperes tener una implementación «Pro».

Mejor soluciona tu caso, ve como te funciona y luego mejora el código que resulte de hacer uso del Patrón Factory.

Y después, estudia otras formas de este patrón.

Me despido y recuerda compartir este articulo y deja tus comentarios!