Quando utilizzare un metodo statico invece di un costruttore?

4

Ho una breve domanda per te: immaginiamo di avere una classe simile a questa.

public class StreamTradeDataProvider : ITradeDataProvider
{
    public StreamTradeDataProvider(Stream stream)
    {
        this.stream = stream;
    }

    public IEnumerable<string> GetTradeData()
    {
        var tradeData = new List<string>();
        using (var reader = new StreamReader(stream))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                tradeData.Add(line);
            }
        }
        return tradeData;
    }

    private Stream stream;
}

Quando passeresti attraverso lo stream nel costruttore? Meglio ancora, sarebbe una valida opzione per rendere GetTradeData un metodo statico e passare attraverso il flusso a quello? Qual è la differenza fino a quando manutenibilità e funzionalità vanno? Questo è sempre stato poco chiaro per me. Sembra che non conosca mai quando utilizzare un backing field privato + costruttore o semplicemente un metodo statico, come questo:

public class StreamTradeDataProvider : ITradeDataProvider
{
    public StreamTradeDataProvider() { }

    public static IEnumerable<string> GetTradeData(Stream stream)
    {
        var tradeData = new List<string>();
        using (var reader = new StreamReader(stream))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                tradeData.Add(line);
            }
        }
        return tradeData;
    }    
}

Grazie in anticipo!

    
posta user3357969 04.02.2017 - 16:03
fonte

2 risposte

4

Credo che la domanda sia più se il tuo algoritmo debba essere statico o stateless piuttosto che se il metodo debba essere statico o meno. Credo che sia meglio evitare i metodi statici in questo caso.

Nel tuo esempio la tua interfaccia ITradeDataProvider implica la definizione di un metodo di istanza, non di uno statico. I metodi statici non sono ereditabili e pertanto ciò influirebbe sul design polimorfico del metodo GetTradeData se lo facessi.

La domanda più pertinente è, quindi, il merito di passare i dati contestuali nel costruttore (statefull) o in un parametro del metodo di istanza (stateless).

Riutilizzabilità

Un aspetto che potresti considerare è che dovresti riuscire a riutilizzare ITradeDataProvider . Se pensi al tuo ITradeDataProvider come a un modello di strategia semplicemente incapsulando un algoritmo per gestire uno stream, e in particolare se hai effettivamente bisogno di elaborare più flussi contemporaneamente o con una certa frequenza, allora ha più senso passare questi dettagli contestuali in un parametro di metodo. In questo modo puoi riutilizzare più volte la stessa istanza di strategia.

ITradeDataProvider strategy = new DefaultTradeDataProvider();
for(Stream stream : getMyTradeDataStreams()) {
   IEnumerable<string> tradeData = strategy.getTradeData(stream);   
}

Quale potrebbe essere il punto di creare più istanze di DefaultTradeDataProvider se tutti sono semplicemente in procinto di eseguire un algoritmo nello stream fornito? Un'istanza è più che sufficiente qui.

In questo caso l'implementazione di ITradeDataProvider è senza stato e ci assicuriamo di passare qualsiasi stato di cui abbia bisogno per fare il suo lavoro.

Co-locazione di spazio e tempo

Un aspetto interessante del modello qui è che i dati contestuali e l'algoritmo di strategia sono probabilmente collocati nel tempo e nello spazio. Cioè, quando ricevi il flusso di dati, è nello stesso posto in cui probabilmente lo userai pure.

Vantaggi di apolidia e multi-threading

Un oggetto stateless è in genere più riutilizzabile e non ti devi preoccupare dei problemi multi-threading se riesci a condividerli con altri componenti che potrebbero essere in esecuzione in altri thread.

Con un oggetto stateless probabilmente puoi semplicemente creare un'istanza singleton del tuo algoritmo e riutilizzarla in modo sicuro ogni volta.

Statefull Closure

Tuttavia, le cose cambiano un po 'quando si vuole creare una chiusura statica, cioè quando si intende pacchettizzare dati e funzionalità e magari utilizzarla in un secondo momento e / o in un altro posto.

In tal caso potrebbe essere conveniente impacchettare lo stato all'interno della chiusura.

ITradeDataProvider provider = new DefaultTradeDataProvider(stream);
someOtherService.withProvider(provider);

incapsulamento

Ora someOtherService non deve preoccuparsi di trasmettere dettagli contestuali al provider. Il provider include già questi dettagli. Verrà utilizzato da someService in un altro luogo e momento diverso da quello da cui è stato originariamente creato. Questo è conveniente perché someOtherService potrebbe non avere nemmeno accesso a stream , che è incapsulato in qualche altro luogo a cui non può raggiungere (cioè l'implementazione del provider). Il someOtherService non interessa nemmeno se l'origine dei dati è un stream o qualcosa di diverso. Questa è la sola responsabilità della chiusura.

Quindi questo potrebbe essere un modo conveniente per incapsulare stato e comportamento e condividerlo con altri oggetti.

Complessità dello stato di conservazione

In questo caso, devi trattare responsabilmente lo stato. Significa che il provider potrebbe non essere condivisibile tra più thread e probabilmente sarebbe necessario creare nuove istanze di esso per thread, altrimenti dovresti forzarlo a renderlo thread-safe se vuoi condividerlo in modo sicuro con altri thread.

Nel caso dello stream di esempio della tua domanda originale, che cosa dovrebbe accadere una volta che hai consumato il flusso una volta? Puoi ancora invocare correttamente getTradeData() e consumare di nuovo lo stream?

E così, come puoi vedere, l'impacchettamento dello stato ha implicazioni più complesse.

    
risposta data 04.02.2017 - 17:59
fonte
0

Non c'è davvero alcuna differenza.

Il primo esempio è chiamato constructor injection .

Il secondo tipo è qualcosa che chiamerei iniezione di dipendenza parametrica , o semplicemente la modellazione di uso : A usa B .

Il vantaggio dell'iniezione del costruttore è che si mette l'oggetto ricevente dall'inizio in uno stato valido .

Come regola generale, dovresti distinguere tra dipendenze necessarie necessarie per il corretto funzionamento dell'oggetto e le dipendenze facoltative .

Supponiamo che tu abbia un writer che incapsula la scrittura in% diversodestinations. Sarebbe necessario inizializzarlo con il componente scrittura . Questo sarebbe fatto tramite constructor injection . Senza l'oggetto sarebbe inutile.

Ma forse hai bisogno della possibilità di fare un passo processing , quindi potresti fare setter injection per questo componente facoltativo: SetPreProcessor .

Questo copre il tuo primo esempio.

Per il secondo esempio: Stream non è né un componente necessario né un componente opzionale di StreamTradeDataProvider .

Per completare il contesto, è necessario un terzo oggetto, un cosiddetto mediator . Il mediator ha accesso a Stream e a StreamTradeDataProvider e il suo lavoro è quello di riunire entrambi.

When would you pass through the stream in the constructor?

Ogni volta quando voglio costruire un componente autonomo da altri componenti che potrebbero essere (teoricamente) passati (diciamo un logger con un componente FileWriter ).

Better yet, would it be a viable option to make the GetTradeData a static method and pass through the stream to that?

In termini di testabilità i metodi non statici sono più facili da deridere. Puoi istanziare qualsiasi tipo di oggetto con il metodo desiderato su di esso. Non vedo alcun vantaggio del un metodo statico qui.

What is the difference as long as maintainability and functionality go?

Come sempre: Sei al comando! Quando il codice funziona, cosa dovrebbe e non hai problemi a testare e lavorare su di esso (inclusi i tuoi co-programmatori), puoi fare quello che vuoi . Non c'è legge, che proibisce qualsiasi cosa. Anche per copia e amp; incolla non stai andando all'inferno. Vedo (qui) nessun vantaggio diretto. In circostanze diverse (o più concrete) potrebbe essercene una. Ma per quanto riguarda questo esempio: fai quello che ti dice il tuo cuore.

This has always been unclear to me. It seems like I never know when to use a private backing field + constructor or simply a static method

E temo che anche dopo il mio post rimanga una certa non chiarezza.

Se Stream è come un battery dovresti iniettarlo, quando è più simile a knife va bene farlo in entrambi i modi.

Il vantaggio di constructor injection è solo, che non ti dimentichi di mettere le batterie nel giocattolo che fai passare.

    
risposta data 04.02.2017 - 18:12
fonte

Leggi altre domande sui tag