2009-04-23 9 views
12

Ho una classe che devo chiamare uno o due metodi molte volte uno dopo l'altro. I metodi attualmente restituiscono void. Stavo pensando, sarebbe meglio averlo restituito this, in modo che i metodi possano essere annidati? o è considerato molto molto pessimo? o se cattivo, sarebbe meglio se restituisse un nuovo oggetto dello stesso tipo? O cosa ne pensi? Per fare un esempio ho creato tre versioni di una classe di vipera:C#: restituisce "questo" per l'annidamento del metodo?

// Regular 
class Adder 
{ 
    public Adder() { Number = 0; } 

    public int Number { get; private set; } 

    public void Add(int i) { Number += i; } 
    public void Remove(int i) { Number -= i; } 
} 

// Returning this 
class Adder 
{ 
    public Adder() { Number = 0; } 

    public int Number { get; private set; } 

    public Adder Add(int i) { Number += i; return this; } 
    public Adder Remove(int i) { Number -= i; return this; } 
} 

// Returning new 
class Adder 
{ 
    public Adder() : this(0) { } 
    private Adder(int i) { Number = i; } 

    public int Number { get; private set; } 

    public Adder Add(int i) { return new Adder(Number + i); } 
    public Adder Remove(int i) { return new Adder(Number - i); } 
} 

Il primo può essere utilizzato in questo modo:

var a = new Adder(); 
    a.Add(4); 
    a.Remove(1); 
    a.Add(7); 
    a.Remove(3); 

Gli altri due possono essere utilizzati in questo modo:

var a = new Adder() 
     .Add(4) 
     .Remove(1) 
     .Add(7) 
     .Remove(3); 

Dove l'unica differenza è che a nel primo caso è il new Adder() mentre nel secondo è il risultato dell'ultimo metodo.

Il primo che trovo rapidamente diventa ... fastidioso per scrivere più e più volte. Quindi mi piacerebbe usare una delle altre versioni.

Il terzo funziona in modo simile a molti altri metodi, come molti metodi String e metodi di estensione IEnumerable. Immagino che abbia il suo lato positivo in quanto puoi fare cose come var a = new Adder(); var b = a.Add(5); e poi averne uno che era 0 e uno che era 5. Ma allo stesso tempo, non è un po 'costoso creare nuovi oggetti tutto il tempo? E quando morirà il primo oggetto? Quando il primo metodo restituisce un po '? O?

In ogni modo, mi piace quello che restituisce this e pensare userò quello, ma io sono molto curioso di sapere cosa gli altri pensano di questo caso. E ciò che è considerato la migliore pratica.

+1

Sei di terza classe non fa quello che hai detto che fa. Dovresti aggiungere o sottrarre da Numero e trasferirlo nel nuovo Adder (...) come numero intero separato. – Samuel

+0

oh, buona cattura! grazie =) – Svish

risposta

17

Lo stile "restituisci questo" viene talvolta chiamato fluent interface ed è una pratica comune.

+0

aha. freddo. quindi suppongo che andrò per quello quindi =) – Svish

+2

Nota che lo stile fluente scrive in modo simile ad un'alternativa molto diversa, che è rendere il tuo oggetto immutabile e restituire nuove istanze dell'oggetto invece di modificare lo stato interno. Con una classe semplice come quella nell'esempio (non che l'esempio sia molto realistico) potresti voler considerare i pro e i contro semplicemente rendendola invece immutabile. – mquander

+2

Sia 'return this' che' return new' sono esempi con diverse applicazioni: jQuery '$ ('# eleId'). Show()' restituisce 'this' perché altrimenti creerebbe una copia di eleId. Linq 'myCollection.Where (x => x.IsValid)' restituisce un nuovo oggetto perché altrimenti avrebbe cambiato myCollection, che potrebbe essere immutabile. – Keith

0

Considera questo: se torni a questo codice in 5 anni, questo ha senso per te? Se è così, allora suppongo che tu possa andare avanti.

Per questo esempio specifico, però, sembrerebbe che sovraccaricare gli operatori + e - renderebbe le cose più chiare e ottenere la stessa cosa.

+0

Beh, certo: p In questo caso il migliore sarebbe stato utilizzare un tipo direttamente int di. Ma era proprio quello che potevo inventare come un semplice esempio di metodi nidificati. Il mio obiettivo era il metodo di chiamata, non quello che effettivamente hanno fatto: p – Svish

+0

Tranne quando torni tra 5 anni. Una catena di metodi si rivelerà istantaneamente con un passaggio del mouse mentre gli operatori richiedono di osservare la classe stessa. – Samuel

+0

ah, questo è un buon punto sì. – Svish

6

Mi piace "sintassi fluente" e prenderei il secondo. Dopo tutto, potresti ancora usarlo come prima cosa, per le persone che si sentono a disagio con una sintassi fluente.

un'altra idea di fare un'interfaccia come le vipere uno più facile da usare:

public Adder Add(params int[] i) { /* ... */ } 
public Adder Remove(params int[] i) { /* ... */ } 

Adder adder = new Adder() 
    .Add(1, 2, 3) 
    .Remove(3, 4); 

cerco sempre di fare le interfacce breve e di facile lettura, ma molte persone amano scrivere il codice complicato come possibile.

+0

Fantastica idea con l'utilizzo dei parametri. Potrei usarlo da solo ... – Svish

0
  1. Per il caso specifico, sovraccaricare gli operatori aritmetici sarebbe probabilmente la soluzione migliore.

  2. Il ritorno a this (interfaccia fluente) è pratica comune per creare espressioni: i test di unità e le strutture di simulazione usano molto questo. Fluent Hibernate è un altro esempio.

  3. Anche restituire una nuova istanza può essere una buona scelta. Ti permette di rendere la tua lezione immutabile - in generale una cosa buona e molto utile nel caso del multithreading. Ma pensate al sovraccarico di creazione dell'oggetto se l'immutabilità non è di alcuna utilità per voi.

+0

Ben riassunto =) – Svish

0

Se si chiama Adder, vorrei tornare con questo. Tuttavia, è strano che una classe Adder contenga una risposta.

Si potrebbe pensare di renderlo simile a MyNumber e creare un metodo Add().

Idealmente (IMHO), che non cambierebbe il numero che viene memorizzato all'interno del vostro esempio, ma creare una nuova istanza con il nuovo valore, che ritorni:

class MyNumber 
{ 
    ... 

    MyNumber Add(int i) 
    { 
     return new MyNumber(this.Value + i); 
    } 
} 
+0

Beh, certo. E anche straniero per contenere un metodo chiamato Rimuovi, che in realtà avrebbe dovuto essere chiamato Sottrai. Ma non era questo il punto qui: p – Svish

0

credo che per le interfacce semplici, l'interfaccia "fluente" è molto utile, soprattutto perché è molto semplice da implementare. Il valore dell'interfaccia fluente è che elimina un sacco di lanugine estranea che ostacola la comprensione. Lo sviluppo di una simile interfaccia può richiedere molto tempo, specialmente quando l'interfaccia inizia a essere coinvolta. Dovresti preoccuparti di come l'uso dell'interfaccia "legge"; Nella mia mente, l'uso più convincente per una simile interfaccia è come comunica l'intento del programmatore, non la quantità di caratteri che salva.

Per rispondere alla tua domanda specifica, mi piace lo stile "restituisci questo". Il mio tipico uso dell'interfaccia fluente è definire un insieme di opzioni. Cioè, creo un'istanza della classe e quindi utilizzo i metodi fluenti sull'istanza per definire il comportamento desiderato dell'oggetto. Se ho un'opzione sì/no (ad esempio per la registrazione), cerco di non avere un metodo "setLogging (bool state)", ma piuttosto due metodi "WithLogging" e "WithoutLogging". Questo è un po 'più di lavoro, ma la chiarezza del risultato finale è molto utile.

2

Il concatenamento è una cosa carina da avere ed è fondamentale in alcuni framework (ad esempio, le estensioni di Linq e jQuery lo usano entrambi pesantemente).

Se si crea un nuovo oggetto o return this dipende da come vi aspettate il vostro oggetto iniziale di comportarsi:

var a = new Adder(); 

var b = a.Add(4) 
     .Remove(1) 
     .Add(7) 
     .Remove(3); 

//now - should a==b ? 

concatenamento in jQuery avranno cambiato il tuo oggetto originale - è tornato questo. Questo è il comportamento previsto - fare altrimenti sarebbe fondamentalmente clonare elementi dell'interfaccia utente.

Il concatenamento in Linq ha lasciato la tua collezione originale invariata. Anche questo è un comportamento previsto: ogni funzione concatenata è un filtro o una trasformazione e la raccolta originale è spesso immutabile.

Quale modello è più adatto a quello che stai facendo?

+0

Buone osservazioni sul pensiero dietro Linq e jQuery =) Sulla questione di "dovrebbe a == b", in questo esatto caso sciocco, dovrebbero ovviamente essere considerati uguali poiché hanno lo stesso numero Ma a seconda se si usasse il metodo 2 o 3, i riferimenti non avrebbero davvero bisogno di essere lo stesso. – Svish

+0

Se si usa 'new' ogni volta dopo che le operazioni completano a.Number == 1 while b.Number == 7. Se si usa' this' in seguito, successivamente, a.Number == 7. – Keith

0

La differenza principale tra la seconda e la terza soluzione è che restituendo una nuova istanza anziché questa, è possibile "catturare" l'oggetto in un determinato stato e continuare da quello.

var a = nuovo addetto() .Aggiungi (4);

var b = a.Rimuovere (1);

var c = a.Add (7) .Rimuovere (3);

In questo caso, sia b che c hanno lo stato catturato in a come punto di partenza. Mi sono imbattuto in questo idioma durante la lettura di un pattern per la creazione di oggetti del dominio di prova in Growing Object-Oriented Software, Guided by Tests by Steve Freeman; Nat Pryce.

Sulla tua domanda riguardante la durata delle tue istanze: vorrei che fossero eleggibili per la garbage collection non appena il richiamo di Remove o Add ritornava.