Come si definisce un singolo metodo generico per applicare eventi sottotipi di una classe base?

7

( Nota : non sono sicuro di come inquadrare correttamente la domanda, quindi sentiti libero di modificarlo o commentare come posso migliorarlo.)

Credo che questa domanda, pur coprendo un ambito abbastanza ampio, sia piuttosto limitata ai linguaggi tipizzati staticamente (orientati agli oggetti) (sto usando Java in particolare) per l'implementazione di CQRS e Event Sourcing. Ho progettato eventi relativi a un particolare aggregato come una gerarchia, in quanto sono sottoclassi di una classe genitore. Supponiamo che io abbia un Order aggregato e che abbia una% base di classe% co_de i cui sottotipi siano eventi specifici come OrderEvent , OrderPlaced , QuantityUpdated , ecc.

Ora, all'interno di questo aggregato OrderCancelled , ho sovraccaricato i metodi Order che prendono sottotipi specifici di apply , come OrderEvent e apply(OrderCancelled) . Tuttavia, quando ricostituisce un oggetto apply(QuantityUpdated) dall'origine evento, quello che ho è un elenco di oggetti la cui interfaccia dichiarata è solo Order . Sebbene tecnicamente, il sistema sottostante tenga traccia del sottotipo specifico di ciascuno di questi oggetti, non c'è modo nel codice di implicare che io abbia fornito i metodi di OrderEvent per ogni tipo in particolare. Devo definire un metodo che prende la classe genitore apply : OrderEvent .

Esistono schemi esistenti attorno a questo di cui non sono a conoscenza? O è qualcosa come il seguente l'unica opzione (scritta in Java):

void apply(OrderEvent event) {
    if (event instanceof OrderPlaced) {
        apply((OrderPlaced) event);
    }
    if (event instanceof QuantityUpdated) {
        apply((QuantityUpdated) event);
    }
    if (event instanceof OrderCancelled) {
        apply((OrderCancelled) event);
    }
}

Ho preso in considerazione la definizione di un metodo apply(OrderEvent) nella classe / interfaccia gen di applyTo che accetta un oggetto OrderEvent a cui viene applicato il particolare oggetto Order . Ad esempio:

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        order.apply(this);
    }
}

Questo mi permetterà di definire OrderEvent in apply(OrderEvent) class come:

void apply(OrderEvent event) {
    event.applyTo(this);
}

Ne risulta un codice più conciso, ma non sono così sicuro della natura quasi circolare dell'approccio.

Qual è lo schema comune utilizzato per questo?

    
posta Psycho Punch 03.07.2017 - 16:36
fonte

5 risposte

3

Questo è semplicemente modello di strategia con un parametro aggiuntivo per il "contesto" passato alla strategia, come in questa domanda SO . Ne risulta un accoppiamento stretto e bidirezionale tra OrderEvent e Order (che potrebbe andare bene per questo caso). Se ogni oggetto OrderEvent è sempre associato allo stesso oggetto Order , passa meglio this (l'oggetto Order ) come parametro costruttore nel relativo OrderEvent , puoi salvare il parametro in applyTo metodo allora (comunque, l'accoppiamento rimarrà). Se lo stesso OrderEvent può essere applicato a diversi ordini, la soluzione va bene.

P.S: questo non è in nessun modo speciale per DDD, CQRS, o event-sourcing, quindi penso che i tuoi tag di domanda non si adattino bene.

    
risposta data 03.07.2017 - 17:30
fonte
3

Sfortunatamente Java non supporta invio multiplo ma puoi usare la riflessione per chiamare il metodo appropriato senza rompere il principio di chiusura aperta o introdurre una dipendenza accidentale tra eventi e aggregati.

Pertanto, potresti utilizzare un dispatcher che identifica il giusto metodo apply e quindi chiamarlo:

class Dispatcher {
   public void applyEvent(Object aggregate, Object event) {
      Method m = aggregate.getClass().getDeclaredMethod('apply',new Class[](event.getClass());
      m.invoke(aggregate,new Object[](event);
   }
}

Come usi questo componente? (ad esempio potresti adattarlo per essere una classe base che le radici Aggregate ereditano da esso).

    
risposta data 04.07.2017 - 09:58
fonte
2

Scusami se capisco male il contesto del tuo bisogno qui, ma quello che capisco è che devi applicare un'implementazione diversa a ciascun evento in base a quale sottoclasse è stato costruito da. In generale, ogni volta che devo ricorrere a instance of e sovraccaricare il metodo apply, ad esempio per ogni sottoclasse, mi fa tornare alla scheda di progettazione perché significa che ho fatto qualcosa di sbagliato.

Secondo me dovresti rendere le firme del metodo generico (come applicare, fare, ottenere, impostare ecc.) che tutti gli oggetti evento dell'ordine delegato dovrebbero avere, un'interfaccia o una super classe con detti metodi come abstract (per forzare le sottoclassi per implementarli). Quindi implementa questo in ogni sottoclasse OrderEvent per differenziare il comportamento nel modo desiderato. Quindi si tratta dei singoli metodi di applicazione con un oggetto Interface o super Class come argomento e quindi semplicemente chiamando il metodo generico appropriato all'interno del metodo apply, il compilatore saprà in fase di esecuzione quale tipo di oggetto si possiede e chiamerà il metodo appropriato per questo allora.

Per visualizzarlo un po 'meglio, considera questo:

interface OrderEvent{
(abstract)do();get();set();
}
class OrderCancelled implements or extends OrderEvent{
do(){ logic;} get(){logic;} set(){logic;}
}
class CompositeOrderWithDelegate(){
 apply(OrderEvent oe){
  oe.do();
 ...
 }
}
    
risposta data 04.07.2017 - 14:06
fonte
0

Il secondo approccio è buono, anche se in questo momento questo porterà a un ciclo. Quello che devi fare è inserire la vera logica dell'evento nell'evento.

Ora fai

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        order.apply(this);
    }
}

Che cosa dovresti fare imo

class OrderPlaced extends OrderEvent {
    void applyTo(Order order) {
        //real logic what happens in order class happens here now
        order.setDate(date);
        order.doThis(var1);
        order.doThat(var2);
    }
}

Quindi solo i tuoi OrderEvents sanno come vengono applicati all'Ordine e la tua classe Order non sa nulla. Ora puoi creare tutte le sottoclassi di OrderEvent che desideri senza toccare di nuovo la classe Order.

    
risposta data 03.07.2017 - 18:24
fonte
0

What's the common pattern used for this?

Hai davvero scoperto entrambi i pattern principali utilizzati.

It results in a more concise code, but I'm not so sure about the almost circular nature of the approach.

Sì, questo è un odore di codice.

In breve, hai preso la decisione che tutti questi eventi estendano una radice comune, il che va bene. Ma hai lasciato che la decisione sugli eventi si diffondesse nell'Ordine. Non è così bello.

Nascondere le decisioni nei moduli è una parte importante delle modifiche al codice facile. Vuoi ridurre al minimo il numero di luoghi che sono intimi con una decisione in modo da essere libero di cambiarlo più tardi.

Il design sotto è analogo a quello che stai facendo ora

class API {
    interface OrderEvent {
        void applyTo(Order order);
    }
    interface OrderPlaced extends OrderEvent {
        // ...
    }
}
class Data {
    class OrderPlaced implements API.OrderPlaced {
        public void applyTo(Order order) {

        }
    }
}

Ma mette in difficoltà le tue preoccupazioni; chiunque abbia bisogno di sapere su OrderPlaced deve anche conoscere applyTo , il che non ha senso dal momento che lo sono mai usare queste informazioni per niente.

Confronta con:

class API {
    interface OrderPlaced {
        // ...
    }
}
class History {
    interface OrderEvent {
        void applyTo(Order order);
    }
}
class Data {
    class OrderPlaced implements API.OrderPlaced, History.OrderEvent {
        public void applyTo(Order order) {

        }
    }
}

Il codice che conosce raccolte eterogenee di eventi dell'ordine richiede l'interfaccia History.OrderEvent, ma non ha bisogno di sapere nulla su OrderPlaced o sui dettagli di implementazione. L'ordine deve sapere su API.OrderPlaced, ma nulla sul fatto che gli ordini siano generici.

Qualsiasi cosa direttamente accoppiata all'implementazione (la classe stessa, la fabbrica che invoca il suo costruttore), ovviamente, deve conoscere l'intera verità.

    
risposta data 05.07.2017 - 00:12
fonte