Re-design correttamente da Passa a Polymorphism (principio Apri / Chiudi)

6

Sto avendo grossi problemi nel tentativo di sbarazzarmi di questa dichiarazione di commutazione. Per mettere un po 'di contesto in primo luogo, sto lavorando con operazioni batch asincrone. Queste operazioni possono essere applicate a qualsiasi entità nel sistema. Devi solo inviare uno spazio dei nomi diverso.

Esempio:

"jobs": [
{
    namespace: "client",
    request: {"id": 10, "name": "john"}
}, 
{
    namespace: "invoice",
    request: {"id": 99, "number": "F20170101"}
}]

Dopo aver inviato quei lavori, il servizio restituisce dati simili, ma con una chiave di risposta come la seguente:

results: [
{
    namespace: "client",
    request: {id: 10, name: "john"},
    response: {success: false}
}, 
{
    namespace: "invoice",
    request: {id: 99, number: "F20170101"},
    response: {success: true, s_id: 3310}
}]

Ora arriva la parte divertente

Devo elaborare la risposta e, in base allo spazio dei nomi, devo selezionare il repository appropriato per aggiornare i nostri record del database. Finora ho questa smelly cosa:

if (namespace == 'client') {
    customerRepository.doSomething()
} elseif (namespace == 'invoice') {
    invoiceRepository.doSomethingTotallyDifferent(withDiffParams)
}

Ora questo chiaramente rompe il principio Open / Close, e non riesco a capire come risolvere questo rimuovendo il switch (in realtà un if / elseif spaghetti thingy, ma è lo stesso). Grazie in anticipo.

PS: questa non è una domanda di lingua specifica, quindi ti preghiamo di non fornire risposte valide solo per una lingua specifica.

    
posta Christopher Francisco 05.09.2017 - 21:43
fonte

4 risposte

5

I tuoi repository dovrebbero essere derivati da una classe base comune, chiamiamola baseRepository . Questa classe deve fornire due metodi virtuali

  • correspondingNamespace()

  • processReponse(responseKey)

Oltre i metodi nelle classi derivate, quindi correspondingNamespace dovrebbe restituire lo spazio dei nomi corretto per ciascun repository, ad esempio "client" per customerRepository . processReponse dovrebbe delegare alla singola logica aziendale come chiamare doSomething o chiamare doSomethingTotallyDifferent con parametri diversi.

Ora puoi facilmente inizializzare un dizionario con i repository disponibili in anticipo, in pseudo codice:

for each repo in [customerRepository, invoiceRepository]:
     repoDict[repo.correspondingNamespace()]=repo

E l'elaborazione della risposta sembrerà (approssimativamente) come:

  repoDict[responseKey.namespace].processReponse(responseKey)

Al tuo commento: se non vuoi aggiungere direttamente questi metodi ai repository, puoi usare una gerarchia separata di classi di strategia e posizionare lì i due metodi. Ricava customerResponseStrategy e invoiceResponseStrategy da responseStrategy , e lascia che ogni responseStrategy tenga un riferimento al repository corrispondente. Ciò porterà a qualcosa di simile

 for each respStrat in 
        [new customerResponseStrategy(customerRepository),
         new invoiceResponseStrategy(invoiceRepository)]:
         responseStrategyDict[respStrat .correspondingNamespace()]=respStrat 

e

  responseStrategyDict[responseKey.namespace].processReponse(responseKey)
    
risposta data 05.09.2017 - 22:11
fonte
3

Ciò che ho fatto molte volte nei progetti e che ha aiutato un sacco con nuove richieste di funzionalità è stato:

interface JobResponseWorker {
    bool canProcess(namespace);
    void process(response);
}

class ClientResponseWorker implements JobResponseWorker {

    bool canProcess(namespace) {
        return namespace == "client"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

class InvoiceResponseWorker implements JobResponseWorker {
    bool canProcess(namespace) {
        return namespace == "invoice"
    }
    void process(response) {
        //... whatever you want to do here
    }
}

workers = [new ClientResponseWorker(), new InvoiceResponseWorker()]

//your main logic
void processResponse(namespace, request, response) {

    for (JobResponseWorker worker in workers) {
        if (worker.canProcess(namespace)){
            worker.process(response)
        }
    }
}

In questo modo, la maggior parte del tuo codice principale è chiuso per le modifiche. Per i nuovi lavoratori, aggiungi un nuovo codice (aperto per l'estensione), quindi aggiungi solo un altro oggetto in un array (nessuna logica aggiuntiva nel vecchio codice).

    
risposta data 06.09.2017 - 14:47
fonte
0

Penso che quello che stai cercando qui è il tipo di approccio descritto dal modello Strategia . In poche parole, devi tradurre i tuoi spazi dei nomi in classi / oggetti. Nel tuo esempio avresti una classe Client e una classe Invoice . Ciascuno avrebbe un metodo doTheThing() .

Ciò che rimane è che è necessario disporre di una sorta di interruttore o mappa che determina il tipo da utilizzare. Questo potrebbe sembrare un po 'inutile in un semplice esempio come questo, ma con l'aumentare del numero di operazioni e classi, semplificherebbe notevolmente sia il codice che renderlo molto più comprensibile. Sarai anche più in grado di aggiungere nuovi tipi senza toccare la logica esistente per altri tipi.

    
risposta data 05.09.2017 - 21:54
fonte
0

Se hai solo un posto nel codice in cui devi fare questa discriminazione, dovresti stare con esso. Ma se hai più punti in cui è necessario applicare un comportamento diverso sul valore namespace , dovresti optare per l'approccio "polimorfico".

Il problema è che con un approccio "polimorfico" devi fare anche la discriminazione perché devi selezionare l'implementazione che appartiene al tuo valore namespace ad un certo punto.

    
risposta data 05.09.2017 - 22:40
fonte