2013-03-28 14 views
11

Ho letto quasi tutte le domande contrassegnate da Law-of-Demeter. La mia domanda specifica non trova risposta in nessuna di queste altre domande, sebbene sia molto simile. Principalmente la mia domanda è quando hai un oggetto con strati di composizione, ma la necessità di recuperare i valori delle proprietà dai vari oggetti, come ottieni questo e perché prendere un approccio rispetto all'altro?Come funzionano la Legge di Demetra e la composizione con le collezioni?

Diciamo che dispone di un oggetto piuttosto standard composto da altri oggetti, in questo modo:

public class Customer { 
    private String name; 
    private ContactInfo primaryAddress; 
    private ContactInfo workAddress; 
    private Interests hobbies; 
    //Etc... 

    public getPrimaryAddress() { return primaryAddress; } 
    public getWorkAddress() { return workAddress; } 
    public getHobbies() { return hobbies; } 
    //Etc... 
} 

private ContactInfo { 
    private String phoneNumber; 
    private String emailAddress; 
    //Etc... 

    public getPhoneNumber() { return phoneNumber; } 
    public getEmailAddress() { return emailAddress; } 
    //Etc... 
} 

private Interests { 
    private List listOfInterests; 
} 

Il seguente sarebbe sia violare la legge di Demetra:

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber()); 
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString()); 

Questo anche possa violare la legge di Demetra, penso (chiarimento?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress(); 
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber()); 

Quindi presumibilmente, tu ld quindi aggiungere un "getPrimaryPhoneNumber()" metodo per clienti:

public getPrimaryPhoneNumber() { 
    return primaryAddress.getPhoneNumber(); 
} 

E poi semplicemente chiamare: System.out.println ("Telefono:" + customer.getPrimaryPhoneNumber());

Ma farlo nel tempo sembra che in realtà fornirebbe un sacco di problemi e lavorare contro l'intenzione della legge di Demetra. Rende il cliente classe in un enorme bagaglio di getter e setter che ha troppa conoscenza delle proprie classi interne. Ad esempio, sembra possibile che l'oggetto Cliente abbia un giorno diversi indirizzi (non solo un indirizzo "primario" e un indirizzo "di lavoro"). Forse anche la classe Customer avrà semplicemente una lista (o altra raccolta) di oggetti ContactInfo piuttosto che oggetti specifici ContactInfo con nome. In che modo continui a seguire la legge di Demetra? Sembrerebbe sconfiggere lo scopo dell'astrazione. Ad esempio, questo sembra ragionevole in un caso del genere in cui un cliente ha una lista di elementi ContactInfo:

Customer.getSomeParticularAddress(addressType).getPhoneNumber(); 

Questo mi sembra si può ottenere ancora più pazzo quando si pensa di alcune persone che hanno un telefono cellulare e un telefono fisso, e quindi ContactInfo deve avere una raccolta di numeri di telefono.

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber(); 

In questo caso, non solo ci riferiamo ad oggetti all'interno di oggetti all'interno di oggetti, ma abbiamo anche sapere quali sono le AddressType validi di e di PHONETYPE sono. Posso sicuramente vedere un problema con questo, ma non sono sicuro di come evitarlo. Soprattutto quando qualsiasi classe lo chiama, probabilmente sa che vogliono estrarre il numero di telefono "mobile" per l'indirizzo "primario" del cliente in questione.

Come potrebbe essere effettuato il refactoring per conformarsi alla legge di Demeter e perché ciò andrebbe bene?

+7

Penso che si sta prendendo troppo alla lettera, è solo una linea guida per un eventuale codice di odori. Quello che stai facendo va bene. – Esailija

+2

D'accordo con Esailija, la legge di demeter non ha realmente bisogno di essere applicata ai getter, sono troppo banali. Infatti, facendo ciò che suggerisci (Customer.getPrimaryPhoneNumber()) porterebbe al Cliente a conoscere gli interni di ContactInfo. Il vantaggio di avere oggetti tipo Composizione è che limiti chi deve sapere su ContactInfo, ad es. – Taylor

+0

OK, vedo questo punto, ma nella mia mente ci saranno scenari non getter/setter per le cose all'interno di ContactInfo. Supponi di voler inviare via email al cliente da un link o un pulsante. Potresti scriverlo come customer.getSomeParticularContactInfo (addressType) .sendEmail() o customer.sendEmail() che poi (all'interno del cliente) chiama getSomeParticularContactInfo ("primary"). SendEmail(). Oppure ci sono altre opzioni come una classe manager che gestisce l'azione. L'esempio quindi non riguarda i getter o setter e mi confondo su come procedere per prendere decisioni di progettazione in quel contesto. –

risposta

2

Nella mia esperienza, l'esempio Customer mostrato non è un "oggetto standard composto da altri oggetti", perché questo esempio prende i passaggi aggiunti di implementare i suoi pezzi di composizione come classi interne e inoltre rende private quelle classi interne. Questa non è una brutta cosa

In generale, il modificatore di accesso privato aumenta l'occultamento delle informazioni, che costituisce la base della legge di Demeter. Esporre le classi private è contraddittorio. L'IDE NetBeans include in realtà un avviso del compilatore predefinito per "Esportazione di tipo non pubblico tramite API pubblica".

Direi che esporre una classe privata al di fuori della sua classe di chiusura è sempre male: riduce le informazioni nascondendo e violando la Legge di Demetra. Quindi, per rispondere alla domanda di chiarimento sulla restituzione di un'istanza di ContactInfo al di fuori di Customer: sì, quella è una violazione.

La soluzione proposta di aggiungere un metodo getPrimaryPhoneNumber() a Customer è un'opzione valida. La confusione è qui: "Il cliente ... ha troppa conoscenza delle proprie classi interne." Questo non è possibile; ed è per questo che è importante che questo esempio non sia un esempio di composizione standard.

Una classe che include ha il 100% di conoscenza di tutte le classi nidificate. Sempre. Indipendentemente dal modo in cui le classi nidificate vengono utilizzate nella classe di inclusione (o in qualsiasi altro luogo). Ecco perché una classe che acclude ha accesso diretto ai campi privati ​​e ai metodi delle sue classi nidificate: la classe che la include conosce intrinsecamente tutto su di loro, perché sono implementate al suo interno.

Dato l'esempio assurdo di una classe Foo, che ha una classe nidificata Bar, che ha una classe nidificata Baz, che ha una classe nidificata Qux, non sarebbe una violazione di Demeter for Foo (internamente) per chiamare la barra .baz.qux.method(). Foo sa già tutto quello che c'è da sapere su Bar, Baz e Qux; perché il loro codice si trova all'interno di Foo, quindi non c'è alcuna conoscenza aggiuntiva passata attraverso la lunga catena di metodi.

La soluzione quindi, per la legge di Demeter, è per Customer non restituire oggetti intermedi, indipendentemente dalla sua implementazione interna; Ad esempio, se lo Customer viene implementato utilizzando diverse classi nidificate o nessuna, deve restituire solo ciò di cui le sue classi client necessitano in definitiva.

Per esempio l'ultimo frammento di codice potrebbe essere riscritta come, customer.getPhoneNumber(addressType, phoneType);

o se ci sono solo un piccolo numero di opzioni, customer.getPrimaryMobilePhoneNumber();

Sia risultati approccio nel utenti della classe Customer ignorando la sua implementazione interna e assicura che quegli utenti non debbano chiamare attraverso oggetti a cui non sono direttamente interessati.

+0

: https: //javadevguy.wordpress.com/2017/05/14/the-genius-of-the-law-of-demeter/ – jaco0646

0

Che ne dici di fare un'altra classe con il nome CustomerInformationProvider. È possibile passare l'oggetto Customer come parametro costruttore alla nuova classe. E poi puoi scrivere tutti i metodi specifici per ottenere telefoni, indirizzi ecc all'interno di questa classe, nel frattempo lasciando la classe Customer pulita.

+0

Questa è una buona opzione ma comporta troppe classi di provider – swapyonubuntu

0

E 'importante ricordare che la Legge di Demetraè, nonostante il nome, una linea guida e non una legge vera e propria. Abbiamo bisogno di esaminare il suo scopo ad un livello leggermente più profondo per determinare qual è la cosa giusta da fare qui.

Lo scopo della legge di Demeter è di impedire agli oggetti esterni di accedere agli interni di un altro oggetto. L'accesso agli interni ha due problemi: 1) fornisce troppe informazioni sulla struttura interna dell'oggetto e 2) consente anche agli oggetti esterni di modificare gli interni della classe.

La risposta corretta a questo problema è a separare l'oggetto restituito dal metodo Cliente dalla rappresentazione interna.In altre parole, invece di restituire un riferimento all'oggetto interno privato di tipo ContactInfo, definiamo invece una nuova classe UnmodifiableContactInfo, e hanno getPrimaryAddress restituiscono UnmodifiableContactInfo, creandolo e riempiendolo secondo necessità.

Questo ci porta entrambi i vantaggi della legge di Demeter. L'oggetto restituito non è più un oggetto interno del Cliente, il che significa che il Cliente può modificare la sua memoria interna a suo piacimento e che nulla di ciò che facciamo su UnmodifiableContactInfo riguarda gli interni del Cliente.

(In realtà vorrei rinominare la classe interna e lasciare quella esterna come ContactInfo, ma questo è un punto minore)

Quindi questo raggiunga gli obiettivi della legge di Demetra, ma sembra ancora come stiamo rompendo esso. Il modo in cui penso a questo è che il metodo getAddress non sta restituendo un oggetto ContactInfo, ma istanzialo. Questo significa che sotto le regole di Demeter possiamo accedere ai metodi di ContactInfo, e il codice che hai scritto sopra non è una violazione.

Si deve naturalmente notare che, sebbene la "violazione della legge di Demeter" si sia verificata nel codice che accede al Cliente, la correzione deve essere effettuata nel Cliente. In generale, la correzione è una buona cosa: fornire accesso agli oggetti interni è negativo, indipendentemente dal fatto che siano accessibili o meno con più di un "punto".

Alcune note. E 'ovvio che un'applicazione eccessivamente rigorosa della legge di Demetra porta a idiozie, vietando ad esempio:

int nameLength = myObject.getName().length() 

essendo una violazione tecnica che la maggior parte di noi fa ogni giorno. Lo fanno anche tutti:

mylist.get(0).doSomething(); 

che è tecnicamente una violazione. Ma la realtà è che nessuno di questi è un problema a meno che non permettiamo effettivamente al codice esterno di influenzare il comportamento dell'oggetto principale (Cliente) in base agli oggetti che ha recuperato.

Sommario

Ecco quello che il codice dovrebbe essere simile:

public class Customer { 
    private class InternalContactInfo { 
     public ContactInfo createContactinfo() { 
      //creates a ContactInfo based on its own values... 
     } 
     //Etc.... 
    } 
    private String name; 
    private InternalContactInfo primaryAddress; 
    //Etc... 

    public Contactinfo getPrimaryAddress() { 
     // copies the info from InternalContactInfo to a new object 
     return primaryAddress.createContactInfo(); 
    } 
    //Etc... 
} 

public class ContactInfo { 
    // Not modifiable 
    private String phoneNumber; 
    private String emailAddress; 
    //Etc... 

    public getPhoneNumber() { return phoneNumber; } 
    public getEmailAddress() { return emailAddress; } 
    //Etc... 
} 

}

Problemi correlati