Questo viola la legge di Demeter?

7

Diciamo che ho una classe SelectableEntity<T extends Entity> che ha tre metodi, select , deselect , isSelected e count .

Per fare un esempio un po 'forzato, diciamo che sto costruendo un'applicazione di messaggistica di emergenza che mi consente di inviare messaggi ai residenti durante incendi, inondazioni e altri disastri naturali.

Posso selezionare più entità di tipi diversi (ma tutte per lo stesso motivo). Diciamo che queste entità sono House , Street e Suburb .

Ora diciamo che ho una classe chiamata MessageRecipients che contiene un SelectableEntity<House> , SelectableEntity<Street> , SelectableEntity<Suburb> .

Ci sono due modi in cui posso codificarlo:

Il primo modo è creare getter (cioè houses , streets , suburbs ) e selezionare / deselezionare i destinatari in quel modo .: messageRecipients.streets.select(aStreet) .

L'altro modo sarebbe nascondere il fatto che MessageRecipients utilizza SelectableEntity e creare semplicemente metodi 'proxy' per ogni metodo su SelectableEntity (cioè selectHouse , deselectHouse , selectStreet , ecc.)

Il primo metodo sembra violare la legge di Demeter mentre il secondo metodo richiede fondamentalmente di creare un intero gruppo di metodi su MessageRecipients che invochino direttamente l'istanza rilevante di SelectableEntity (e richiede un intero gruppo di test aggiuntivi per garantire che vengano invocati i metodi corretti).

La mia domanda è, è il primo esempio di un esempio standard di violazione della Legge di Demetra e sarebbe preferibile addormentare tutta la duplicazione e la verbosità e scendere il secondo approccio (una supposizione una domanda successiva è ciò che costituisce un valido motivo per rompere la legge di Demetra, se non altro)?

Nota: l'esempio che ho dato è truccato e sono consapevole che potrebbe essere abbattuto in determinate situazioni (ad esempio, le strade e i sobborghi contengono case - se aggiungo una strada che ha la casa XYZ e chiamo messageRecipients.houses().isSelect(houseXYZ) dovrebbe restituire true). Per il gusto di questa discussione, il caso dovrebbe essere ignorato in quanto non si applica allo scenario reale con cui ho a che fare.

    
posta NRaf 04.03.2018 - 05:20
fonte

3 risposte

2

There are two ways I could code this:

The first way is to create getters (i.e. houses, streets, suburbs) and select / deselect recipients that way.: messageRecipients.streets.select(aStreet).

Non credo che preferisco in questo modo, ma non voglio vedere applicata la legge di Demeter.

Alcuni pensano che questo sia tutto ciò che LoD ha da dire:

the Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects:

  • O itself
  • m's parameters
  • Any objects created/instantiated within m
  • O's direct component objects
  • A global variable, accessible by O, in the scope of m

In particular, an object should avoid invoking methods of a member object returned by another method. For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as "use only one dot". That is, the code a.b.Method() breaks the law where a.Method() does not. As an analogy, when one wants a dog to walk, one does not command the dog's legs to walk directly; instead one commands the dog which then commands its own legs.

Wikipedia: Law of Demeter

Questo è un monumento al pensiero strutturale. Non c'è un solo pensiero semantico o concettuale in tutto ciò. Un analizzatore statico potrebbe far rispettare questo. Bleh. Questo non è tutto quello che penso quando penso al LoD .

La legge di Demeter non è un conteggio dei punti esercizio.

Quando sei un oggetto, è meglio parlare solo con i tuoi amici, non con gli amici degli amici. Se prendi a caso qualsiasi cosa di cui hai bisogno, presto scoprirai di aver combinato tante cose che qualsiasi modifica al codice base ti farà del male qui.

Gli amici sono cose di cui possiedi le interfacce. Solo allora hai promesso di poter saltare attraverso questi punti e trovare sempre la stessa cosa. Anche se ciò può accadere, non sarà vero se prendi a caso qualunque cosa ti piaccia da un codice base.

Quindi se il client che chiama "messageRecipients.streets.select (aStreet)" possiede l'interfaccia per messageRecipients e streets , allora sta parlando ai suoi amici e non vi è alcuna violazione di LoD. Ciò significa che il cliente possiede tutte le interfacce che utilizza. Non dovrebbero cambiare senza il permesso del cliente. Questo dovrebbe essere chiaro ai programmatori di manutenzione. Questo ha bisogno di confini chiari. Ma non si tratta del conteggio dei punti.

Questo è il vero problema. A causa del modo in cui stai ottenendo questo altro oggetto, la dipendenza dalla sua interfaccia non è esplicita. La dipendenza può essere una sorpresa. Se rispetti, troverai un modo per rendere questa dipendenza chiara ad altri codificatori.

Questa non dovrebbe essere una situazione accartocciata. Questa dovrebbe essere una situazione creata da un'attenta progettazione. Quello che viene creato qui in realtà ha un nome. Si chiama DSL . Se è quello che stai facendo bene. Se questo è solo un insieme casuale di cose perché ne hai avuto bisogno, allora stai creando un problema in attesa di accadere.

L'altro nome in cui scorre il LoD è "il principio della minima conoscenza". Ti chiede di capire che quando crei queste catene di punti stai aggiungendo conoscenza al tuo cliente non solo di queste interfacce, ma di come si connettono. Più conosci e più fa male quando le cose cambiano. Non creare catene che nessuno avrebbe promesso sarebbe stato stabile.

The other way would be to hide the fact that MessageRecipients is using SelectableEntity at all and simple create 'proxy' methods for each method on SelectableEntity (i.e. selectHouse, deselectHouse, selectStreet, etc).

Mi piace questa idea, ma vorrei anche nascondere se ciò che viene selezionato è una casa, una strada o un sobborgo. Che il sobborgo sia l'unica cosa a cui importa che sia un sobborgo.

    
risposta data 04.03.2018 - 11:47
fonte
10

Nessuna delle tue soluzioni proposte corrisponde ai requisiti che hai menzionato, e sospetto che sia per questo che non si adatta (o viola LoD, o ha bisogno di metodi proxy per le cose).

Da quello che hai descritto, permettimi di proporre un design diverso. Come ho capito MessageRecipients sono Residents . Quindi dimentichiamo il termine tecnico e rimandiamo a Residents . Il punto di Residents è che possono essere alerted . Quindi scriviamolo:

public interface Residents {
    void alert(Message msg);
}

Ora, a quanto ho capito, ci sono diversi tipi di "luoghi" che contengono residenti. Queste cose rappresentano fondamentalmente una serie di residenti che possono essere allertati insieme. Quindi questi sono un tipo speciale di "residenti" ai fini della nostra applicazione .

public final class House implements Residents {
    ...
    public House(...address, etc?...) {
        ...
    }

    @Override
    public void alert(Message msg) {
        ...
    }
}

Inoltre, potresti voler avvisare insieme House , Suburb e Street residenti. In questo caso hai bisogno di un'implementazione "aggregata" come questa:

public final class AggregateResidents implements Residents {
    private final List<Residents> residents;

    public AggregateResidents(List<Residents> residents) {
        this.residents = residents;
    }

    @Override
    public void alert(Message msg) {
        residents.forEach(r -> r.alert(msg));
    }
}

Questo è tutto. Puoi collegare queste cose insieme nel modo che preferisci e puoi avvisare qualsiasi combinazione di residenti senza violare LoD o utilizzare i metodi proxy ovunque.

Lo so, probabilmente hai altri requisiti che potrebbero rendere questo disegno non valido. Il punto che sto facendo è cercare di allontanarsi da un progetto tecnico, lontano dalla modellazione dei dati, e cercare di concentrarsi sui concetti non tecnici. A volte questo è davvero difficile, ma si prenderà automaticamente cura di tutte le leggi e le regole.

    
risposta data 04.03.2018 - 23:06
fonte
2

Se capisco che il tuo Selezionabile è correttamente un oggetto collezione con sintassi per contrassegnare alcuni dei suoi elementi come selezionati.

Di per sé va bene, ma tu usi, esponendo più di una collezione sul tuo oggetto MessageRecipients, è male (tm)

Il LoD è un po 'vago, ma possiamo elencare le cose negative nell'esporre i tre tipi di lista

  1. Il codice di chiamata deve sapere che tipo di cosa sta cercando di selezionare e abbinare alla proprietà appropriata.

  2. Altri tipi aggiunti successivamente (stato?) cambieranno la firma di MessageRecipients

La tua seconda soluzione, aggiungendo metodi, soffre anche di questi problemi.

Idealmente vuoi.

MessageRecipients.Select (IAlertable recipient)

Poiché lo scopo dell'oggetto è quello di inviare avvisi, ci interessa solo che i destinatari possano farlo.

Forse internamente sappiamo dei tipi e abbiamo le tre liste. Ma poiché è interno, possiamo aggiungerli in seguito e non influire sul codice chiamante.

Nb. A seconda del dettaglio, potresti andare su ISelectable o solo su ID stringa

    
risposta data 04.03.2018 - 10:15
fonte

Leggi altre domande sui tag