Utilizzo dei delegati per evitare la duplicazione della creazione di risorse

1

Sto scrivendo un PCL che utilizza HttpClient per visitare alcuni siti ed estrarre i dati da essi. Il mio codice iniziale assomigliava a questo:

public static class Download
{
    public async static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            string source = await client
                .GetStringAsync(videoUri);

            // get links...

            return await client
                .GetByteArrayAsync(links.First());
        }
    }
}

Mi sono reso conto, tuttavia, che questo sarebbe uno spreco di risorse se l'utente della mia libreria già avesse un HttpClient in mano. Ad esempio:

// Caller code
using (var client = new HttpClient())
{
    // Do some work with client

    byte[] bytes = await Download.FromYouTubeAsync("some://uri"); // A second HttpClient is created here, wasting resources and time...

    // Do some work with client and bytes
}

Una soluzione potrebbe essere quella di aggiungere un overload prendendo un HttpClient come parametro, ma cosa succede se l'utente volesse usare WebClient invece? O un HttpWebRequest ? O qualche altro tipo di client che non era disponibile nel PCL?

La mia soluzione

Ho deciso di prendere i parametri delegati per ottenere l'origine della pagina e per scaricare il file basato sull'URI. Il mio codice ora appare così:

public static class Download
{
    public async static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            return await FromYouTubeAsync(
                () => client
                .GetStringAsync(videoUri),
                uri => client
                .GetByteArrayAsync(uri));
        }
    }

    public async static Task<byte[]> FromYouTubeAsync(
        Func<Task<string>> sourceFactory, Func<string, Task<byte[]>> downloadFactory)
    {
        string source = await sourceFactory();

        // get links...

        return await downloadFactory(links.First());
    }
}

Il problema

Hm. Aspetta, e se il chiamante utilizza un client che non supporta async , come HttpWebRequest ? È meglio aggiungere un altro sovraccarico prendendo un Func<string> e un Func<string, byte[]> . Cosa fare se si desidera effettuare la prima chiamata sincrona, ma la seconda chiamata async ? O vice versa? OK, aggiungi solo 2 più sovraccarichi. Aspetta, e se il chiamante vuole che l'intera operazione sia sincrona? Andiamo ad aggiungere 4 Download.FromYouTube overload avvolgendo ciascuno dei metodi async . Spero che sia necessario visitare la pagina un'altra di YouTube nel caso in cui la firma del video sia crittografata! Certo, andiamo ad aggiungere 8 mor -

Penso che tu possa vedere dove sta andando. Come faccio a mantenere questa flessibilità senza spaventare l'utente quando Visual Studio dice che ci sono 17 diversi overload per questo metodo?

    
posta James Ko 03.07.2015 - 04:13
fonte

3 risposte

1

Perché non rendere FromYouTubeAsync una funzione pura (ad esempio, che accetta una stringa che rappresenta l'HTML della pagina video di youtube e che restituisce l'URL del file video mp4 che trova all'interno), e quindi chiama il chiamante in che modo esattamente per scaricare i byte su Internet, se pensi che vogliano avere il controllo sul processo che stai insinuando?

Puoi sempre fornire un'implementazione predefinita utilizzando HttpClient per quei clienti che non si preoccupano veramente di come vengono scaricati e vogliono solo un'API il più semplice possibile.

    
risposta data 03.07.2015 - 09:51
fonte
2

Il tuo primo argomento riguardante il riutilizzo di HttpClient ha perfettamente senso.
Per quanto riguarda il resto delle richieste, penso che tu stia cercando di ottimizzare prima del tempo. Ciò può portare a complessità non necessarie.
Inoltre, vorrei avvisarti dell'uso delle classi statiche. Questa è solitamente una cattiva idea. Rende il tuo codice più difficile da testare ed estendere.
In ogni caso, di seguito è un design che consente la flessibilità desiderata.

Dichiara un'interfaccia.

public interface IDownloadable
{
    Task<byte[]> FromUriAsync(string videoUri);
}

Implementalo per HttpClient e consenti il passaggio dell'istanza di HttpClient come parametro costruttore.

public class HttClientpDownloader : IDisposable, IDownloadable
{
    private readonly HttpClient _client;

    public HttClientpDownloader() : this(new HttpClient())
    {

    }
    public HttClientpDownloader(HttpClient client)
    {
        _client = client;
    }

    public void Dispose()
    {
        if (_client == null)
            return;

        _client.Dispose();
    }

    public Task<byte[]> FromUriAsync(string videoUri)
    {
        throw new NotImplementedException();
    }
}

Come per il resto delle tue richieste, il modello di design Factory Method dovrebbe essere in grado di risolverli. Implementa solo WebClientDownloader , HttpWebRequestDownloader e qualsiasi altra cosa desideri nelle classi dedicate.
Lo stesso approccio può funzionare anche per la tua richiesta parzialmente asincrona. Basta implementare quella logica in una classe dedicata. Qualcosa come PartiallyAsyncHttpClientDownloader .

public class DownloaderFactory
{
    public IDownloadable Create(/*pass whatever parameters to make teh decision*/){
        switch (param)
        {
            case '1':
                return new HttClientpDownloader();
            case '2':
                return new WebClientDownloader();
            ...
        }
   }
}
    
risposta data 03.07.2015 - 06:34
fonte
1

Dopo averci pensato durante la notte e guardando la risposta di Nebu, ecco le soluzioni che ho trovato:

  • Restituisce solo l'URI e accetta uno a / sync Func. Anche se penso che sia ragionevole per un'API restituire l'URI a un video (nel caso in cui l'utente non voglia scaricarlo ancora ), Penso che sia un po 'tecnico chiedergli di scaricare la fonte della pagina e mi indurrebbe a fare più sovraccarichi (es. SourceToBytes , SourceToUri ).
  • Fai in modo che il downloader di origine HTML accetti una stringa come URI. In altre parole, anziché () => client.GetStringAsync("some://uri") , sembrerebbe uri => client.GetStringAsync(uri) . La ragione per cui lo dico è che così facendo mi impedirebbe di fornire altri 8 sovraccarichi chiedendo come scaricare la stringa da quell'altra pagina su YouTube (nel caso in cui la firma del video sia crittografata), quando ha già detto che è% codice%. TL; DR: Questo mi impedisce di dover creare altri 8 sovraccarichi.
  • Sposta tutti i metodi Func in una classe AdvancedDownload. Questo aiuterà a non spaventare l'utente quando fa clic sugli overload per il metodo.

EDIT: Dopo alcune altre complicazioni ho anche deciso di cancellare tutti i metodi statici ed ereditare tutto tranne uno di essi da una classe base. Nonostante abbia ~ 15 diversi metodi standard nella base, ora devo solo sovrascrivere il metodo one ogni volta che voglio espandermi in un nuovo sito web (ad es. Vimeo).

Grazie a @DanielS e @NebuPookins per i suggerimenti!

    
risposta data 03.07.2015 - 16:54
fonte

Leggi altre domande sui tag