Secondo la legge di Demetra, una classe può restituire uno dei suoi membri?


13

Ho tre domande sulla legge di Demetra.

A parte le classi che sono state appositamente designate per restituire oggetti - come le classi factory e builder - va bene per un metodo restituire un oggetto, ad esempio un oggetto detenuto da una delle proprietà della classe o che viola la legge di demeter (1) ? E se viola la legge di demeter, importerebbe se l'oggetto restituito è un oggetto immutabile che rappresenta un pezzo di dati e non contiene altro che getter per questi dati (2a)? O tale ValueObject è un anti-pattern in sé, perché tutto ciò che viene fatto con i dati in quella classe viene fatto al di fuori della classe (2b)?

In pseudo codice:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Sospetto che la legge di Demetra proibisca un modello come quello sopra. Cosa posso fare per assicurarmi che doSomethingElse()possa essere chiamato senza violare la legge (3)?


9
Molte persone (in particolare i programmatori funzionali) vedono la Legge di Demetra come un anti-schema. Altri lo vedono come "Il suggerimento occasionalmente utile di Demetra".
David Hammen,

1
Valuterò questa domanda perché illustra le assurdità della Legge di Demetra. Sì, LoD è "occasionalmente utile", ma di solito è stupido.
user949300,

Come si sta xpreparando?
candied_orange,

@CandiedOrange xè solo una proprietà diversa il cui valore è solo un oggetto che è in grado di invocare doSomethingElse.
user2180613

1
Il tuo esempio non viola LOD. LOD consiste nell'impedire ai client di un oggetto di accoppiarsi ai dettagli interni o ai collaboratori di tali oggetti. vuoi evitare cose del genere user.currentSession.isLoggedIn(). perché esso coppie clienti usernon solo user, ma anche il suo collaboratore, session. Invece vuoi essere in grado di scrivere user.isLoggedIn(). ciò si ottiene di solito aggiungendo un isLoggedInmetodo a usercui delegare l'implementazione currentSession.
Giona il

Risposte:


7

In molti linguaggi di programmazione, tutti i valori restituiti sono oggetti. Come altri hanno già detto, non essere in grado di utilizzare i metodi degli oggetti restituiti ti costringe a non restituire nulla. Dovresti chiederti: "Quali sono le responsabilità delle classi A, B e C?" Questo è il motivo per cui l'uso di nomi di variabili metasintattici come A, B e C sollecita sempre la risposta "dipende" perché non vi sono responsabilità ereditarie in questi termini.

Potresti voler esaminare Domain Driven Design, che ti fornirà un insieme più euristico di sfumature per ragionare su dove dovrebbe andare la funzionalità e chi dovrebbe invocare cosa.

La tua seconda domanda relativa agli oggetti immutabili parla della nozione di un
oggetto valore rispetto a un oggetto entità. in DDD, puoi trasferire oggetti valore senza quasi restrizioni. Questo non è vero per gli oggetti entità.

Il LOD semplicistico è molto meglio espresso in DDD come regole per l'accesso agli aggregati . Nessun oggetto esterno deve contenere riferimenti a membri di aggregati. Dovrebbe essere tenuto solo un riferimento radice.

DDD a parte, almeno vuoi sviluppare i tuoi set di stereotipi per le tue classi che sono ragionevoli per il tuo dominio. Applicare regole su quegli stereotipi durante la progettazione del sistema.

Inoltre, ricorda sempre che tutte queste regole devono gestire la complessità, non il tendine del ginocchio.


1
DDD fornisce delle regole su quando i membri della classe come i dati possono essere esposti (vale a dire che dovrebbero esistere getter)? Tutte le discussioni intorno Law of Demeter, tell, don't ask, getters are evil, object encapsulatione single responsibility principlesembrano ridursi a questa domanda: quando dovrebbe delega a un oggetto di dominio essere usato invece di ottenere il valore dell'oggetto e fare qualcosa con esso? Sarebbe molto utile essere in grado di applicare qualifiche sfumate alle situazioni in cui tale decisione deve essere presa.
user2180613

Linee guida come "getter sono cattivi" esistono perché molte persone trattano le istanze di classe come strutture di dati (un sacco di dati da trasferire). Questa non è una programmazione orientata agli oggetti. Gli oggetti sono fasci di funzionalità / comportamento che forniscono effettivamente del lavoro. Lo scopo del lavoro è definito dalla responsabilità. Questo lavoro creerà ovviamente oggetti che vengono restituiti da metodi, ecc. Se la tua classe sta effettivamente facendo un lavoro significativo che puoi chiaramente definire oltre a "rappresentare l'oggetto dati X con gli attributi Y e Z", allora sei sulla strada giusta . Non mi preoccuperei.
Jeremy,

Espandersi su quel punto, quando si usano molti getter / asker, di solito significa che hai un metodo di grandi dimensioni da qualche parte che orchestra tutto, che Fowler chiama uno script di transazione. Prima di iniziare a utilizzare un getter, chiediti ... perché sto chiedendo questi dati dall'oggetto? Perché questa funzionalità non è presente in un metodo sull'oggetto? Se non è responsabilità di questa classe implementare questa funzionalità, vai avanti e usa un getter. Il libro [Refactoring] ( martinfowler.com/books/refactoring.html ) di Fowler è un libro sull'euristica per tali domande.
Jeremy,

Nel suo libro Implementing Domain-Driven Design, Vaughn Vernon ha menzionato la Legge di Demetra e Tell, Don't Ask in Ch 10, pp 382. Potrebbe valere la pena dare un'occhiata se si ha accesso ad esso.
Jeremy,

6

Secondo la legge di Demetra, una classe può restituire uno dei suoi membri?

Sì, lo è sicuramente.

Diamo un'occhiata ai punti principali :

  • Ogni unità dovrebbe avere una conoscenza limitata delle altre unità: solo le unità "strettamente" correlate all'unità corrente.
  • Ogni unità dovrebbe parlare solo con i suoi amici; non parlare con estranei.
  • Parla solo con i tuoi amici immediati.

Tutti e tre i quali ti lasciano fare una domanda: chi è un amico?

Quando si decide cosa restituire, La Legge di Demetra o il principio della minima conoscenza (LoD) non impone che si difenda dai programmatori che insistono nel violarlo. Determina che non costringi i programmatori a violarlo.

Essere confusi è esattamente il motivo per cui così tanti pensano che un setter debba sempre restituire il vuoto. No. È necessario consentire un modo per eseguire query (getter) che non modificano lo stato del sistema. Questa è la separazione delle query dei comandi di base .

Questo significa che sei libero di approfondire una base di codice concatenando tutto ciò che vuoi? No. Incatena solo ciò che doveva essere incatenato insieme. Altrimenti la catena potrebbe cambiare e improvvisamente le tue cose verranno rotte. Questo è ciò che si intende per amici.

Le catene lunghe possono essere progettate per. Interfacce fluide, iDSL, gran parte di Java8 e il buon vecchio StringBuilder sono tutti pensati per farti costruire catene lunghe. Non violano il LoD perché tutto nella catena è pensato per lavorare insieme e ha promesso di continuare a lavorare insieme. Violi demeter quando concatenate cose che non si sono mai sentite. Gli amici sono quelli che hanno promesso di far funzionare la tua catena. Friends of Friends no.

A parte le classi che sono state appositamente designate per restituire oggetti - come le classi factory e builder - va bene per un metodo restituire un oggetto, ad esempio un oggetto detenuto da una delle proprietà della classe o che viola la legge di demeter (1) ?

Questo crea solo un'opportunità per violare Demeter. Questa non è una violazione. Questo non è nemmeno necessariamente negativo.

E se viola la legge di demeter, importerebbe se l'oggetto restituito è un oggetto immutabile che rappresenta un pezzo di dati e non contiene altro che getter per questi dati (2)?

Immutabile è buono ma irrilevante qui. Arrivare a cose attraverso una catena più lunga non lo rende migliore. Ciò che lo rende migliore è separare l'ottenere dall'uso. Se stai usando, chiedi quello che ti serve come parametro. Non andare a caccia di esso scavando nei getter di estranei.

In pseudo codice:

Sospetto che la legge di Demetra proibisca un modello come quello sopra. Cosa posso fare per garantire che doSomethingElse () possa essere chiamato senza violare la legge (3)?

Prima di parlare di x.doSomethingElse(a)capire che hai sostanzialmente scritto

b.getA().doSomething()

Ora, LoD non è un esercizio di conteggio dei punti . Ma quando crei una catena stai dicendo che sai come ottenere un A(usando B) e sai come usare un A. Bene, ora Ae Bmeglio essere amici intimi perché basta loro accoppiati.

Se avessi appena chiesto qualcosa per consegnarti una Acosa simile che potresti usare Ae non ti sarebbe importato da dove venisse e Bpotresti vivere una vita felice e libera dalla tua ossessione per arrivare Ada B.

Per quanto riguarda x.doSomethingElse(a)senza dettagli da dove xproviene, LoD non ha nulla da dire al riguardo.

LoD può incoraggiare a separare l'uso dalla costruzione . Ma farò notare che se trattate religiosamente ogni oggetto come non amichevole, sarete bloccati a scrivere codice con metodi statici. In questo modo puoi costruire un grafico a oggetti meravigliosamente complesso, ma alla fine devi chiamare un metodo per iniziare a funzionare. Devi semplicemente decidere chi sono i tuoi amici. Non c'è modo di evitarlo.

Quindi sì, una classe è autorizzata a restituire uno dei suoi membri in LoD. Quando lo fai dovresti chiarire se quel membro è amichevole con la tua classe perché se non lo è, alcuni client potrebbero provare ad associarti a quella classe usando te per ottenerla prima di usarla. Questo è importante perché ora ciò che ritorni deve sempre supportare quell'uso.

Ci sono molti casi in cui questo non è semplicemente un problema. Le collezioni possono ignorarlo semplicemente perché sono pensate per essere amiche di tutto ciò che le utilizza. Allo stesso modo gli oggetti di valore sono amichevoli con tutti. Ma se hai scritto un indirizzo che convalida l'utilità che ha richiesto un oggetto dipendente da cui ha estratto un indirizzo, piuttosto che semplicemente chiedere l'indirizzo, è meglio sperare che il dipendente e l'indirizzo provengano entrambi dalla stessa libreria.


Le interfacce fluide non fanno eccezione a LOD a causa di una certa nozione di cordialità ... In un'interfaccia fluida, ciascuno dei metodi nella catena viene chiamato sullo stesso oggetto, perché tutti restituiscono sé. settings.darkTheme().monospaceFont()è solo zucchero sintattico persettings.darkTheme(); settings.monospaceFont();
Giona il

3
@Jonah Il ritorno di sé è la massima cordialità. E non tutte le interfacce fluide lo fanno. Molti iDSL sono anche fluenti e restituiscono tipi diversi per cambiare i metodi disponibili in punti diversi. Prendi in considerazione jOOQ , step builder , builder builder e il mio monster builder da incubo . Fluente è uno stile. Non un modello.
candied_orange,

2

Oltre alle classi che sono state appositamente designate per restituire oggetti [...]

Non capisco bene questo argomento. Qualsiasi oggetto può restituire un oggetto a seguito di un invito al messaggio. Soprattutto se pensi a "tutto come oggetto".

Le classi, tuttavia, sono gli unici oggetti che possono creare un'istanza di nuovi oggetti del loro genere inviando loro il messaggio "nuovo" (o spesso sostituiti con una nuova parola chiave sui linguaggi OOP più comuni)


Ad ogni modo, riguardo alla tua domanda, sarei piuttosto sospettoso riguardo a un oggetto che restituisce una delle sue proprietà per essere usato altrove. È possibile che questo oggetto trapelasse informazioni sul suo stato o sui suoi dettagli di implementazione.

E non vorrai che l'oggetto C conoscesse i dettagli dell'implementazione di B. Questa è una dipendenza indesiderata dall'oggetto A dalla prospettiva dell'oggetto C.

Quindi potresti chiederti se C, conoscendo A, non sta conoscendo troppo per il lavoro che deve svolgere, indipendentemente dal LOD.

Ci sono casi in cui questo può essere legittimo, ad esempio, chiedere a un oggetto di raccolta uno dei suoi elementi è appropriato e non infrangere alcuna regola di Demeter. D'altro canto, chiedere a un oggetto Book il suo oggetto SQLConnection è rischioso e dovrebbe essere evitato.

Il LOD esiste perché molti programmatori tendono a chiedere informazioni agli oggetti prima di eseguirne un lavoro. LOD è lì per ricordarci che a volte (se non sempre) stiamo solo complicando troppo le cose desiderando che i nostri oggetti facciano troppo. A volte non ci resta che chiedere a un altro oggetto di fare il lavoro per noi. LOD è lì per farci riflettere due volte su dove posizioniamo il comportamento nei nostri oggetti.


0

A parte le classi che sono state appositamente designate per restituire oggetti - come le classi factory e builder - va bene per un metodo restituire un oggetto?

Perché non dovrebbe essere? Sembra che tu suggerisca che è necessario segnalare ai tuoi colleghi programmatori che la classe è specificamente pensata per restituire oggetti, o che non deve assolutamente restituire oggetti. Nelle lingue in cui tutto è un oggetto, ciò limiterebbe fortemente le tue opzioni, in quanto non potresti restituire nulla.


L'esempio riguarda l'implicito b.getA().doSomething()ex.doSomethingElse(b.getA())
user2180613

A a = b.getA(); a.DoSomething();- torna a un punto.
Robert Harvey,

2
@ user2180613 - Se volevi chiedere informazioni b.getA().doSomething()e x.doSomethingElse(b.getA())avresti dovuto chiedere esplicitamente a riguardo. Allo stato attuale, la tua domanda sembra riguardare this.b.getA().
David Hammen,

1
Come Anon è un membro di C, non creata in Ce non fornite C, sembra che l'uso Ain C(in qualsiasi modo) violi LoD. Il conteggio dei punti non è lo scopo di LoD, è solo uno strumento illustrativo. È possibile convertire Driver().getCar().getSteeringWheel().getFrontWheels().toRight()in istruzioni separate che contengono ciascuna un punto, ma la violazione di LoD è ancora lì. Ho letto in molti post su questo forum che eseguire azioni dovrebbe essere delegato all'oggetto che implementa il comportamento reale, ad es tell don't ask. Restituire valori sembra essere in conflitto con quello.
user2180613

1
Suppongo sia vero, ma non risponde alla domanda.
user2180613

0

Dipende da cosa stai facendo. Se esponi un membro della tua classe, quando non è affare di nessuno che questo sia un membro della tua classe, o quale sia il tipo di quel membro, è una brutta cosa. Se poi cambi il tipo di quel membro, e il tipo della funzione che restituisce il membro, e tutti devono cambiare il loro codice, questo è male.

D'altra parte, se l'interfaccia della tua classe afferma che fornirà un'istanza di una ben nota classe A, va bene. Se sei fortunato e puoi farlo fornendo un membro della tua classe, bravo con te. Se hai modificato quel membro da A in B, dovrai riscrivere il metodo che restituisce A, ovviamente ora dovrà costruirne uno e riempirlo con i dati disponibili, invece di restituire solo una riga un membro. L'interfaccia della tua classe rimarrebbe invariata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.