Usando Lazy con FactoryPattern

Estaba actualizando la aplicación de mi esposa, el autopublicador en wordpress (prometo hacer un post al respecto) y me di cuenta que muchas tareas repetitivas se hacían en varios lugares (probablemente, porque estoy desactualizado y tengo que trabajar en eso).

Así que revisando, empecé por tratar de mejorar el acceso a los datos.

Por lo pronto, descubrí que tres clases hacían la misma búsqueda a la base de datos, y entonces me di cuenta que no estaba teniendo un patrón de servicios para las clase MVVM (lo cual es lógico, la base de datos solo tiene una tabla y solo guarda datos de login a los sitios).

Aunque esto no estaría mal de por si, podría aggiornar la aplicación para crear una clase de servicios, que por lo menos, sirviera esa tabla, al mejor estilo EF.

Asi que investigando, nada mejor que modernizar la aplicación, mejorando sus tiempos de respuestas (como si fuera necesario) pero mas que todo, para aprender nuevos patrones (aunque vuelvo a aclarar, en esta aplicación es totalmente innecesario).

Empecemos viendo que es Lazy.

Segun la documentacion, es un objeto que no se inicializa hasta que se lo necesita. Buenisimo, puedo usar el mismo para traer la informacion de los sitios, pero solo cuando los necesite en la pantalla (la pantalla principal, no los necesita).

private Lazy<ImmutableList<MiObjeto>> _MiObjetoLista;

public ImmutableList<MiObjeto> MiObjetoLista
{
    get
    { 
        return _MiObjetoLista.Value; 
    }
}

De esa forma, defino un campo privado que es una lista Ininmutable, de MiObjeto. Al definir la misma como Lazy, le estoy diciendo que se va a cargar cuando alguien llame a la misma.

En la propiedad publica, hago uso de Lazy.Value, que es quien se va a encargar explicitamente de cargar el valor. Hasta ese momento, solo se definio todo. Lo que hace falta, es decirle a ese objeto como se va a cargar.

Para ello, en el create de mi objeto, hago lo siguiente:

public MiClase()
{
    _MiObjetoLista= new Lazy<ImmutableList<MiObjeto>>(
        () => GetList()
    );
}

public ImmutableList<MiObjeto> GetList()
{
    var db = new Database();
    using (var conn = db.GetConnection())
    {
        //Estoy usando dapper
        var r = conn.Query<MiObjeto>(
@"select 
URL as SiteURL,
User,
Pass as Password
from sites");
        return r.ToImmutableList();
    }
}

Perooooo.. este codigo tiene un gran problema… No es asincronico.. y toda llamada a base de datos, deberia ser asincronica. Entonces, tenemos que solucionar eso. Lazy no tiene ningun problema con metodos asincronicos, pero Los constructores si tienen problemas.

Entonces para solucionar eso, debemos re pensar la estrategia. Cuando pasa esto, tenemos dos opciones:

  1. Crear un metodo Inicializar, que sea asincronico y cree las variables
  2. Usar Factory Pattern

Cuando vemos el metodo 1, es directo. Se crea el metodo, y luego de crear la clase, se llama. Digamos que seria algo asi:

private Lazy<Task<ImmutableList<MiObjeto>>> _MiObjetoLista;

public MiClase()
{
    
}

public void Inicializar()
{
    _MiObjetoLista = new Lazy<Task<ImmutableList<MiObjeto>>>(
        async () => await GetList()
    );
}

Deberiamos modificar GetList, para que sea asincronico:

async public Task<ImmutableList<MiObjeto>> GetList()
{
    var db = new Database();
    using (var conn = db.GetConnection())
    {
        var r = await conn.QueryAsync<MiObjeto>(
@"select 
URL as SiteURL,
User,
Pass as Password
from sites");
       return r.ToImmutableList();
    }
}

Y al crear el objeto habria que hacer lo siguiente:

MiClase c = New MiClase();
c.Inicializar();

Con eso, la clase se crea. Pero siempre tengo que estar escribiendo dos líneas, una para crear el objeto, y otra para inicializarlo. Y además, me tengo que acordar que este objeto necesita inicializarse cada vez que lo necesito.

Entonces, para ahorrarse esto, veamos el metodo 2. Creemos una factory que cree que el objeto asincronicamente.

La teoria atras de esto es la siguiente, async factory pattern (o patron de fabrica asincronico, no se para que traducirlo si suena horrible) dice que tenemos que tener un metodo estatico, que nos devuelva el objeto ya construido. Dicho metodo, debe estar dentro del objeto a construir, de forma tal de poder acceder al constructor privado del mismo, ya que el objeto no se puede construir desde afuera (no tiene constructor publico).

El constructor privado recibe por parametro los datos a llenar en sus campos. Y el metodo de construccion, que es asincronico, devuelve la propia clase, ya construida.

Este metodo, por ejemplo, nos impide hacer:

MiObjeto c = new MiObjeto();

Porque tira un error de que no es accesible el metodo (el constructor).

Entonces, para usar el patron factory, nuestra clase deberia quedar asi:

private ImmutableList<MiObjeto> _Lf;
public ImmutableList<MiObjeto> Lf
{
    get
    {
        return _Lf;
    }
}

//Notese la necesidad de agregar static para poder pasarlo
async public static Task<ImmutableList<MiObjeto>> GetSiteList()
{
    var db = new Database();
    using (var conn = db.GetConnection())
    {
        var r = await conn.QueryAsync<MiObjeto>(
@"select 
URL as SiteURL,
User,
Pass as Password
from sites");
        return r.ToImmutableList();
    }
}

//Constructor privado
private SiteServices(Task<ImmutableList<MiObjeto>> f)
{
    _Lf= new Lazy<Task<ImmutableList<MiObjeto>>>(async () => await f);
}

//Creador del objeto, aqui se ve el patron
public static SiteServices CreateSiteServicesAsync()
{
    SiteServices s = new SiteServices(GetSiteList());
    return s;
}

y para generar un objeto de esta clase, hacemos lo siguiente:

SiteServices s = SiteServices.CreateSiteServicesAsync();

Ahora.. notemos algo.. todo esto que hice, en el caso que nos compete, es asincronico, pero en ningun lado use async o wait… porque como estoy usando lazy, y el objeto todavia no se creo, todo eso esta encapsulado dentro de lazy. Entonces, en este caso, deberiamos volver al primer ejemplo, y ver si lo podemos refactorizar.

private Lazy<Task<ImmutableList<SiteList>>> _SiteList;
public Task<ImmutableList<SiteList>> SiteList => _SiteList.Value;
        
public SiteServices() 
{
    _SiteList = new Lazy<Task<ImmutableList<SiteList>>>(async () =>
    await GetSiteList());
}

async public Task<ImmutableList<SiteList>> GetSiteList()
{
    var db = new Database();
    using (var conn = db.GetConnection())
    {
        var r = await conn.QueryAsync<SiteList>(
@"select 
URL as SiteURL,
User,
Pass as Password
from sites");
        return r.ToImmutableList();
    }

}

Y si. En este caso, podemos refactorizarlo ya que al usar lazy, estamos encapsulando la parte asincronica hasta que realmente la utilicemos…

En un proximo capitulo, usamos DI o un patron singleton?

No Comments, Be The First!

Your email address will not be published.