Quando un metodo di una classe dovrebbe restituire la stessa istanza dopo aver modificato se stessa?

8

Ho una classe che ha tre metodi A() , B() e C() . Questi metodi modificano la propria istanza.

Mentre i metodi devono restituire un'istanza quando l'istanza è una copia separata (proprio come Clone() ), ho ottenuto una scelta libera per restituire void o la stessa istanza ( return this; ) quando si modifica la stessa istanza nel metodo e non restituisce alcun altro valore.

Quando decido di restituire la stessa istanza modificata, posso creare catene di metodi pulite come obj.A().B().C(); .

Questa sarebbe l'unica ragione per farlo?

Va bene anche modificare la propria istanza e restituirla? O dovrebbe restituire solo una copia e lasciare l'oggetto originale come prima? Perché quando restituisce la stessa istanza modificata l'utente forse ammetterebbe che il valore restituito è una copia, altrimenti non verrebbe restituito? Se va bene, qual è il modo migliore per chiarire queste cose sul metodo?

    
posta modiX 19.08.2014 - 15:23
fonte

4 risposte

6

Quando utilizzare il concatenamento

Il concatenamento delle funzioni è per lo più popolare con le lingue in cui un IDE con completamento automatico è un luogo comune. Ad esempio, quasi tutti gli sviluppatori C # utilizzano Visual Studio. Pertanto, se stai sviluppando con C # aggiungendo il concatenamento ai tuoi metodi puoi essere un risparmiatore di tempo per gli utenti di quella classe perché Visual Studio ti aiuterà a costruire la catena.

D'altra parte, linguaggi come PHP di natura altamente dinamica e che spesso non supportano il completamento automatico negli IDE vedranno meno classi che supportano il concatenamento. Il concatenamento sarà appropriato solo quando vengono utilizzati phpDocs corretti per esporre i metodi concatenabili.

Che cos'è il concatenamento?

Dato una classe chiamata Foo i seguenti due metodi sono entrambi concatenabili.

function what() { return this; }
function when() { return new Foo(this); }

Il fatto che uno sia un riferimento all'istanza corrente e uno crea una nuova istanza non cambia che questi sono metodi concatenabili.

Non esiste una regola d'oro secondo cui un metodo concatenabile deve fare riferimento solo all'oggetto corrente. Infatti, i metodi concatenabili possono attraversare due diverse classi. Ad esempio;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

Non c'è alcun riferimento a this in nessuno degli esempi precedenti. Il codice a.What().When() è un esempio di concatenamento. La cosa interessante è che il tipo di classe B non è mai assegnato a una variabile.

Un metodo è incatenato quando il suo valore di ritorno viene usato come componente successivo di un'espressione.

Ecco qualche altro esempio

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

Quando utilizzare this e new(this)

Le stringhe nella maggior parte delle lingue sono immutabili. Pertanto, le chiamate al metodo di concatenamento portano sempre alla creazione di nuove stringhe. Dove può essere modificato un oggetto come StringBuilder.

La coerenza è la migliore pratica.

Se disponi di metodi che modificano lo stato di un oggetto e restituiscono this , non utilizzare metodi che restituiscono nuove istanze. Invece, crea un metodo specifico chiamato Clone() che lo farà esplicitamente.

 var x  = a.Foo().Boo().Clone().Foo();

Questo è molto più chiaro su cosa sta succedendo all'interno di a .

Il passo fuori e dietro trucco

Io chiamo questo trucco di uscire e tornare , perché risolve molti problemi comuni legati al concatenamento. Significa fondamentalmente che esci dalla classe originale in una nuova classe temporanea e poi torni alla classe originale.

La classe temporanea esiste solo per fornire funzionalità speciali alla classe originale, ma solo in condizioni speciali.

Spesso ci sono momenti in cui una catena deve cambiare lo stato , ma la classe A non può rappresentare tutti quei possibili stati . Quindi durante una catena viene introdotta una nuova classe che contiene un riferimento a A . Ciò consente al programmatore di entrare in uno stato e di tornare a A .

Ecco il mio esempio, lascia che lo stato speciale sia conosciuto come B .

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

Questo è un esempio molto semplice. Se si desidera avere un maggiore controllo, B potrebbe tornare a un nuovo super-tipo di A con metodi diversi.

    
risposta data 19.08.2014 - 16:14
fonte
2

Those methods modify the own instance.

A seconda della lingua, avere metodi che restituiscono void / unit e modificare la loro istanza o i parametri non è univoco. Anche nei linguaggi che lo facevano di più (C #, C ++), sta andando fuori moda con il passaggio a una programmazione di stile più funzionale (oggetti immutabili, funzioni pure). Ma supponiamo che ci sia una buona ragione per ora.

Would this be the only reason for doing so?

Per certi comportamenti (si pensi a x++ ) ci si aspetta che l'operazione restituisca il risultato, anche se modifica la variabile. Ma questa è in gran parte l'unica ragione per farlo da solo.

Is it even okay to modify the own instance and return it, too? Or should it only return a copy and leave the original object as before? Because when return the same modified instance the user would maybe admit the returned value is a copy, otherwise it would not be returned? If it's okay, what's the best way to clarify such things on the method?

Dipende.

Nelle lingue in cui copia / nuovo e ritorno sono comuni (C # LINQ e stringhe), restituire lo stesso riferimento potrebbe creare confusione. Nelle lingue in cui la modifica e il ritorno sono comuni (alcune librerie C ++), la copiatura potrebbe creare confusione.

Rendere la firma univoca (restituendo void o usando costrutti di linguaggio come proprietà) sarebbe il modo migliore per chiarire. Dopodiché, rendere il nome chiaro come SetFoo per mostrare che stai modificando l'istanza esistente è buono. Ma la chiave è mantenere gli idiomi della lingua / libreria con cui stai lavorando.

    
risposta data 19.08.2014 - 15:40
fonte
0

(ho assunto C ++ come linguaggio di programmazione)

Per me questo è principalmente un aspetto di leggibilità. Se A, B, C sono modificatori, specialmente se si tratta di un oggetto temporaneo passato come parametro ad una funzione, ad es.

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

rispetto a

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

Riscontrando se è ok per restituire un riferimento all'istanza modificata, direi di sì e ad esempio puntare agli operatori di streaming "> >" e "< <" ( link )

    
risposta data 19.08.2014 - 15:40
fonte
0

Potresti anche eseguire il metodo concatenando qualcosa con il ritorno della copia anziché modificare il ritorno.

Un buon esempio di C # è string.Replace (a, b) che non cambia la stringa su cui è stato invocato, ma restituisce una nuova stringa che ti permette di farla allegramente.

    
risposta data 19.08.2014 - 16:13
fonte