Design della classe: inserire dati o iniettare repository / servizio per recuperare i dati?

0

Diciamo per esempio che sto sviluppando una classe immaginaria chiamata WidgetMaker . Se la classe dipende a un certo punto dai dati di ricerca memorizzati in un database, è meglio progettare la classe con una dipendenza dai dati o da un servizio per recuperare i dati?

Ha più senso farlo?

public class WidgetMaker {

    public WidgetMaker(LookupData lookupData) {     
    }

    public Widget MakeWidget() {

        if(lookupData.SomeField) { 
            return new Widget("blue"); 
        }
        else { 
            return new Widget("red"); 
        }  
    }
}

-o questo -

public class WidgetMaker {

    private readonly LookupDataRepo lookupRepo
    private LookupData lookupData;

    public WidgetMaker(LookupDataRepo lookupRepo) {     
        lookupRepo = lookupRepo;
    }

    public Widget MakeWidget() {

        if(lookupData == null) {
            lookupData = lookupRepo.GetLookupData();
        } 

        if(lookupData.SomeField) { 
            return new Widget("blue"); 
        }
        else { 
            return new Widget("red"); 
        } 
    }
}

Il primo modo sembra il design "giusto" perché stiamo iniettando ciò di cui la classe ha bisogno. Rende la classe più facile al test unitario. Altrimenti dobbiamo passare un falso deposito solo per testare la logica.

Sul rovescio della medaglia, sembra che stiamo spingendo il recupero dei dati da qualche altra parte, forse una classe WidgetMakerFactory . Quindi qualunque classe abbia bisogno di una WidgetMaker , otterrebbe invece la dipendenza da WidgetMakerFactory che sembra che stiamo ripetendo lo stesso problema.

public class WidgetController : Controller {

    private WidgetMakerFactory factory;

    public WidgetController(WidgetMakerFactory factory) {
        this.factory = factory;
    }

    public ActionResult Index() {
        return View(factory.Create().MakeWidget())
    }
}
    
posta Rahul Patel 08.05.2018 - 20:05
fonte

2 risposte

4

Probabilmente dovresti iniziare separando le tue preoccupazioni in piccoli pezzi estensibili che possono essere facilmente uniti e sostituiti.

interface Transform {
    Widget widget(LookupData lookupData);
}

Questo è di per sé un pezzo

interface Source {
   LookupData lookupData();
}

C'è il tuo effetto collaterale di lettura.

class WidgetMaker {
    Source source;
    Transform transform;

    Widget widget() {
        return transform.widget(source.lookupData());
    }
}

C'è la tua composizione.

class Cache implements Source {
    LookupData cache = null;          
    Source upstream;

    LookupData lookupData() {
        if (null == cache) {
            cache = upstream.lookupData();
        }
        return cache;
    }
}

Ora puoi mettere una mini cache di fronte a qualsiasi fonte

... e così via.

it seems you're designing towards injecting the service to fetch the data?

Sto progettando di poter cambiare idea. Vedi Parnas 1971 .

L'inserimento di dati immutabili direttamente in un consumatore sarà la mia prima scelta. È semplice, non intacca l'implementazione con la shell imperativa , che rende il sistema più facile da ragionare su e più facile da testare.

Ma ... se i dati sono qualcosa che può essere modificato durante la vita di WidgetMaker, allora hai molte opzioni possibili su come invalidare la tua copia cache dei dati più recente e lo stesso WidgetMaker dovrebbe essere isolato dalla strategia scelta.

Quando mi dici database, si apre la possibilità che tu abbia bisogno di accedere ai dati in cui la rappresentazione ufficiale di riferimento è sotto il controllo di un processo diverso. Questo è il grande timore - che richiede di accedere allo stato tramite la shell imperativa , che nel mondo orientato agli oggetti significa che vuoi essere seriamente considerando porte e adattatori .

    
risposta data 08.05.2018 - 21:47
fonte
0

La mia più grande domanda sarebbe la frequenza con cui si prevede che i dati cambino e se si supponga che debbano essere memorizzati nella cache.

Senza conoscere la risposta a questa domanda, mi spingerei verso l'opzione B, iniettando un'astrazione che fornirà i dati invece di iniettare i dati.

Se si iniettano i dati, come verrà configurata l'iniezione? Si potrebbe finire con il codice all'interno della propria configurazione DI che richiama il repository per fornire i dati da iniettare nella classe che ne ha bisogno. Preferirei evitarlo e configurare semplicemente il contenitore per risolvere una classe. In questo modo il contenitore è responsabile solo per creare classi, non invocarne uno per fornire dati a un altro.

Inoltre, se decidi di implementare il caching (come con l'intercettazione) è più facile inserirlo intorno alla classe che fornisce i dati invece della classe che possiede e riceve i dati. Cosa succede se una classe riceve due serie di dati, ciascuno con diverse scadenze? Non ci sarebbe alcun modo di gestire la vita della classe per soddisfare entrambi. (Senza contare che sarebbe davvero strano configurare un'istanza di classe scaduta.) Anche se esiste un solo set di dati è più facile pensare mentalmente alla memorizzazione nella cache del richiamo per ottenere i dati rispetto alla memorizzazione nella cache della classe che riceve e necessita dei dati.

Sulla stessa linea, cosa succede se più classi dipendono dagli stessi dati? Se hai determinato il criterio di cache appropriato per il sorgente dei dati e applicato, allora si applica ovunque vengano utilizzati i dati.

Se si inietta il repository, indipendentemente dal fatto che sia necessario o meno il caching, non è necessario capire tutte le cose in anticipo. Se hai bisogno di caching in seguito, devi solo aggiungerlo al repository (idealmente usando l'intercettazione.) Quindi per me questa è l'opzione più sicura e flessibile.

    
risposta data 17.05.2018 - 18:20
fonte

Leggi altre domande sui tag