Il livello di granularità per ripetere il codice: il download, la decompressione, ecc. devono essere gestiti da classi separate?

1

Faccio largo uso di DI, ma mi chiedo, dov'è il limite di "granulosità", quando alcune serie di funzionalità dovrebbero essere separate in una classe - facciamo un esempio:

public class DownloadManager : IDownloadManager
{
    public Uri ResourceIdentifier { get; }
    public DownloadManager(Uri uniqueResourceIdentifier)
    {
        if (!uniqueResourceIdentifier.IsWellFormedOriginalString()) throw new ArgumentException(nameof(uniqueResourceIdentifier));
        ResourceIdentifier = uniqueResourceIdentifier;
    }
    public DownloadManager(string uniqueResourceIdentifier) : this(new Uri(uniqueResourceIdentifier)) { }

    public async Task<byte[]> GetBytesAsync(CancellationToken token = new CancellationToken())
    {
        using (var client = new HttpClient())
        using (var response = await client.GetAsync(ResourceIdentifier, token).ConfigureAwait(false))
        {
            var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
            return data;
        }
    }
}

Mi sembra che questa classe non porti quasi nessun ulteriore vantaggio, e persino ostacoli alcune operazioni:

  • e se avessimo bisogno di uno stream (ReadAsStreamAsync ())?
  • non è una grande idea sparpagliare il codice con async, poiché si diffonde come GPL (vedi anche Task etiqutte ).
  • può condurre (ma in questo caso promuove piuttosto la composizione) al problema yo-yo

e potrebbe essere sostituito con un metodo statico come questo (o anche l'estensione alla classe Uri):

public static async Task<byte[]> GetBytesAsync(/*this */Uri uniqueResourceIdentifier, CancellationToken token = new CancellationToken())
{
    if (!uniqueResourceIdentifier.IsWellFormedOriginalString()) throw new ArgumentException(nameof(uniqueResourceIdentifier));
    using (var client = new HttpClient())
    using (var response = await client.GetAsync(uniqueResourceIdentifier, token).ConfigureAwait(false))
    {
        var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
        return data;
    }
}

Ma non si può semplicemente DI un metodo statico, quindi questo sarà racchiuso nella classe sottile implementando un'interfaccia.

Opzione 3: La terza opzione sarebbe, il codice potrebbe essere scritto solo dove è necessario (l'API è grande, quasi nessuna configurazione ridondante), rischiando che, se necessario, il codice dovrà essere refactored (o copiato) nel futuro. Inoltre, potrebbe essere allettante piuttosto che creare una classe di Dio come FileManager, che scaricherà, decomprimerà CRUD & tutto, ma sacrificando la libertà e "S" da SOLID.

  1. Quale è il preferito in questo caso specifico (quasi lo stesso vale per decomprimere i file, usando ZipArchive e un paio di ottimi MS API)?
  2. In base a cosa, dovrei prendere una decisione su questo tre approcci da adottare?
posta Piotr Falkowski 11.11.2015 - 19:48
fonte

2 risposte

2

Non sono sicuro di seguire tutti i dettagli del codice, l'esempio e le opzioni che hai inserito nel testo della tua domanda. Ma affronterò in modo specifico il titolo della domanda, ovvero "Il livello di granularità per ripetere il codice":

Non esiste una regola fissa su granularità ; il design non riguarda regole fisse (altrimenti scriveremmo semplicemente un programma per computer per fare progetti e molti sviluppatori sarebbero ... che lavorano per l'Über?). Il design ha euristica , che sono come regole empiriche che sembrano aver funzionato bene in passato per determinati tipi di problemi.

Modelli software di assegnazione di responsabilità generale (GRASP) è un'euristica molto utile per la progettazione (non molto diversa da SOLID). Nel tuo caso, somiglia molto al problema risolto dal principio Pure Fabrication (enfasi mia):

Who is responsible when you are desperate, and do not want to violate high cohesion and low coupling?

Assign a highly cohesive set of responsibilities to an artificial or convenience "behavior" class that does not represent a problem domain concept—something made up, in order to support high cohesion, low coupling, and reuse.

La tua classe DownloadManager è sicuramente riutilizzabile e sembra piuttosto coesa. Non sono sicuro della necessità di DI, a meno che non sia davvero qualcosa che varia nelle implementazioni (ma questa è un'altra domanda).

Ho evidenziato la parola disperata sopra, perché la nozione di granularità che cerchi è lì. Mentre assegni le responsabilità alle classi nel tuo progetto, ad un certo punto ti rendi conto che dare la responsabilità di scaricare (o decomprimere) ad altre classi viola il strong principio di coesione, e quindi non sei soddisfatto da ciò (la scelta di Larman della parola "disperata"). "è probabilmente un'iperbole, come un modo per farlo attaccare al cervello).

La soluzione è essenzialmente quella di ridefinire tale responsabilità in una classe separata che è molto coesa. Pure Fabrication è molto simile al refactoring Extract Class , tranne le pure fabrications non sembrano classi di dominio problematico. L'esempio per Extract Class ha una nuova classe chiamata TelephoneNumber , che sembra una classe dominio-problema. Ma la tua classe DownloadManager suona come una classe dominio-soluzione (specialmente la parte Manager ).

Le classi Visitor nel pattern GoF con lo stesso nome sono anche ottimi esempi di Pure Fabrications. Sono altamente coesivi, ma si sono verificati solo dopo che gli sviluppatori si sono frustrati con l'aggiunta di responsabilità non coerenti alle classi in una struttura dati. Aiuta a pensare al design prima che esisteva.

    
risposta data 12.11.2015 - 16:07
fonte
3

Posso ricordarti di valutare la conformità con i principi SOLID .

Inoltre, tieni presente l'idea che "abbastanza buono vuol dire abbastanza buono". Se le astrazioni extra non sono necessarie, allora non farlo. È una complicazione inutile.

Ma il download, la decompressione, ecc dovrebbero essere in classi separate se possono essere usati indipendentemente, o se i metodi alternativi di download e / o decompressione potrebbero essere desiderabili.

Uno dei principali professionisti per le classi separate è la testabilità; un enorme vantaggio del principio di responsabilità unica.

È tutta una questione di equilibrio. Se stai riscontrando che il codice sta diventando ingestibile, allora fai qualcosa a riguardo fissando una buona euristica OOP come SOLID.

Infine, ricorda di codificare su un'interfaccia. Progettare prima l'interfaccia. Scrivi uno snippet di codice che utilizzi il modulo in questione, come se esistesse già, nel modo più naturale. Quindi scrivi quel codice in base a quell'interfaccia. Quindi, infine, decidi l'implementazione e mantienila separata dall'interfaccia.

    
risposta data 12.11.2015 - 02:30
fonte

Leggi altre domande sui tag