2009-11-24 8 views
10

Come illustrato di seguito, esistono due modi semplici per eseguire una copiatrice di flussi (barra che introduce Apache Commons o simili). Quale dovrei andare a fare, e perché?Progettazione del metodo statico rispetto all'oggetto

public class StreamCopier { 
private int bufferSize; 

public StreamCopier() { 
    this(4096); 
} 

public StreamCopier(int bufferSize) { 
    this.bufferSize = bufferSize; 
} 

public long copy(InputStream in , OutputStream out) throws IOException{ 
    byte[] buffer = new byte[bufferSize]; 
    int bytesRead; 
    long totalBytes = 0; 
    while((bytesRead= in.read(buffer)) != -1) { 
     out.write(buffer,0,bytesRead); 
     totalBytes += bytesRead; 
    } 

    return totalBytes; 
} 
} 

vs

public class StreamCopier { 

public static long copy(InputStream in , OutputStream out) 
    throws IOException { 
    return this.copy(in,out,4096); 
} 

public static long copy(InputStream in , OutputStream out,int bufferSize) 
    throws IOException { 
    byte[] buffer = new byte[bufferSize]; 
    int bytesRead; 
    long totalBytes = 0; 
    while ((bytesRead= in.read(buffer)) != -1) { 
     out.write(buffer,0,bytesRead); 
     totalBytes += bytesRead; 
    } 

    return totalBytes; 
} 
} 
+0

.. c'è un bug nel primo 'copia' metodo nel secondo esempio: il terzo parametro deve essere eliminato. Forse qualcuno potrebbe applicare un bugfix!? –

risposta

19

mi piacerebbe andare con la versione non-statico (esempio), e l'offerta ai consumatori come una dipendenza esplicita con un setter:

  • beffardo fuori per il test delle unità è quindi banale, così le prove di i consumatori non sono accoppiati all'attuazione;
  • lo swapping delle funzionalità è semplice, ad es .: utilizzando una sottoclasse;
  • funziona bene con i sistemi di iniezione dipendenti.

Modifica

In risposta a una (utile!) Commento di "come fa questo aiuto beffardo?", Ecco come potrebbe funzionare:.

class ThingThatUsesStreamCopier { 

    // our copier instance. set in constructor, but might equally use 
    // a setter for this: 
    private StreamCopier copier; 

    public ThingThatUsesStreamCopier(StreamCopier copier) { 
     this.copier = copier; 
    } 

    public void makeCopy(Stream in, Stream out) { 
     // probably something a little less trivial... 
     copier.copy(in, out); 
    } 
} 

Quando vengo a testare ThingThatUsesStreamCopier, posso creare una versione mock object di un StreamCopier e creare un'istanza della ThingThatUsesStreamCopier utilizzando questo finto

In questo modo, ho piena controllo sul comportamento del mio simulato, quindi il mio test è disaccoppiato da qualsiasi reale implementazione di StreamCopier. Sto solo testando il consumatore, non il consumatore più il consumato.

+2

+1, prendere in giro metodi statici è quasi impossibile. –

+2

Quindi, ecco cosa non capisco di questo argomento: con la versione dell'istanza, l'utente dirà semplicemente 'nuovo StreamCopier(). Copy (in, out)'. Com'è più facile deridere o scambiare? –

+0

L'utilizzo di metodi statici rende inoltre più difficile la creazione di sottoclassi StreamCopier correlate in futuro. – hallidave

11

vorrei andare per la versione statica perché non c'è stato.

In genere non esiste un punto in un oggetto stateless a meno che non sia necessario per scopi di ereditarietà (metodi virtuali).

Se è probabile che gli utenti vogliano prendere in giro la funzionalità, preferirei un'interfaccia su un'implementazione concreta - l'implementatore dell'interfaccia non potrebbe essere statico, quindi in questo caso dovresti utilizzare un oggetto istanziato.

Modifica: Un paio di anni più tardi e ora desidero incolpare il mio ex autore per suggerire la versione statica. Vorrei andare per la versione di istanza senza alcuna esitazione di sorta in questi giorni.

+0

Perché il cambio di cuore? – stuart

+1

È più difficile testare il codice che dipende dai metodi statici, poiché è più difficile sovrascrivere il comportamento dei metodi statici. Nella mia esperienza con Java e C# essere in grado di prendere in giro un oggetto porta a uno sviluppo di test molto più veloce di dover usare una libreria per sovrascrivere i metodi statici. – dice

0

Dato che non vedo grandi differenze nelle prestazioni, penso che questa sia semplicemente una questione di praticità e secondo me il metodo statico è molto più pratico ;-).

6

Vorrei andare con la versione statica.

Non è necessario un oggetto poiché non si sta memorizzando lo stato, quindi perché creare un chiamante per creare un oggetto solo per chiamare un metodo.

+1

Parole d'oro "perché il chiamante crea un oggetto solo per chiamare un metodo"! –

2

Tutto dipende dal modello di utilizzo. Forse hai solo bisogno di copiare qualcosa da un InputStream a un OutputStream di tanto in tanto? Allora probabilmente non importa. Tuttavia, se si sta eseguendo molta copia in vari ambienti (flussi di rete, LAN e WAN, copia di file sul disco locale) si potrebbe stare meglio quando si ha la possibilità di selezionare la dimensione del buffer utilizzato per la copia.

Quindi, perché limitarsi a un solo metodo? Implementalo con metodi object e un costruttore che prende una dimensione del buffer (usata per le tue esigenze variabili), e magari aggiungi un metodo statico per ottenere un'istanza pseudo-singleton che usa alcune dimensioni del buffer di default (che è usata per la copia casuale ogni ora e poi).

+1

Il termine "singleton" è un po 'fuori posto qui. – BalusC

+0

È più simile a un singleton opzionale, sai? Come un singleton ma non proprio. :) (Sì, forse dovrei usare qualche altro termine. Ci sono termini per pseudo-singleton?) – Bombe

+0

Implementazione predefinita? :) – Groo

1

Ci sarà una minima differenza in overhead (la statica sarà assegnato una volta vs allocati su base di istanza) soprattutto dato che lo stato è costituito da un singolo int. Generalmente raramente vado per le classi statiche perché rendono difficili i test unitari. C'è poco overhead nel rendere le classi basate su istanze piuttosto che statiche (un'assegnazione, azzerando la memoria e chiamando il costruttore - che sono tutte v operazioni rapide) e a causa dell'incapacità di deridere la statica vedo poco beneficio.

Tra le altre cose la classe statica può anche aumentare notevolmente l'accoppiamento; una classe statica può essere referenziata da qualsiasi luogo purché venga fatto riferimento al riferimento all'assembly. Quando si tratta di refactoring, ciò può portare a problemi (ad esempio, i riferimenti staitc internamente vengono trascinati nel grafico delle dipendenze, ecc.).

1
public static long copy 

Non hai bisogno di un esempio per eseguire il metodo perché non stai memorizzazione dello stato, e non si ha intenzione di avere sottoclassi di questo.

Vorrei solo aggiungere la parola chiave final.

I metodi ausiliari come questo sono buoni utilizzi dei metodi di classe rispetto ai metodi di istanza.

1

non importa cosa usare in questo momento, ma si potrebbe pensare, cosa avresti bisogno in futuro. Forse hai qualche piano per estendere le operazioni di buffer o qualcosa del genere. Sceglierei metodi non statici

0

Dipenderà dall'utilizzo. Il codice che chiama la copia sa qual è la dimensione del buffer appropriata? Potrebbe essere che la decisione sia stata presa al di fuori di tale codice e un'istanza StreamCopier è una cosa migliore da passare come parametro rispetto a una dimensione del buffer (ad esempio se si scopre che in un momento successivo era necessario un parametro aggiuntivo no sarebbero necessarie modifiche al codice)

1

Se si utilizza la statica, è necessario evitare i nomi WET.

WET è sinonimo di tutto ciò di scrittura per due volte, in tal modo, invece di StreamCopier.copy chiamano

Copy.stream(in,out) 

in questo modo il codice legge più come l'inglese.

+0

Non sono troppo sicuro di questo, avere una classe "Copia" sembra un po 'ampia. Se fosse necessario scrivere una funzione di copia per un'entità completamente indipendente, il metodo andrebbe anche nella classe Copy? – JonoW

+2

'Streams.copy (in, out)' suona bene. –

+0

@jonow lo farebbe. In realtà questo è il modo in cui organizzo tutti i miei metodi di aiuto. 'Any.satisfy',' All.notNull' ecc. Ovviamente (a meno che tu non abbia classi parziali come in C#) questo presuppone un mondo chiuso, come potrebbe non essere dato in alcuni progetti pubblici. – akuhn

0

Il metodo statico indica la codifica di una classe concreta, non un'interfaccia. Significa un accoppiamento più stretto e rende più difficile l'Unit Testing. È qui che la regola "contiene stato" cade.

0

Quando si utente Metodo statico, si chiami in qualsiasi momento che restituirà lo stesso oggetto, ma si crea new A() creerà nuovo oggetto quando si lavora con esso

Problemi correlati