Come evitare "tipi di dispatcher" quando si lavora con SOLID

5

Mi sono reso conto che dal momento che sono stato sempre più severo sui principi SOLID, il mio codice tende a consistere in più oggetti di dati puri e un sacco di classi di "operatori" che sembrano andare contro i principi di oop. Nello specifico, spesso finisco con "tipo dispatcher" che associa un tipo di oggetto specifico a una classe operatore appropriata.

Fammi fare un esempio:

Nel vecchio mondo avrei una classe base FinancialInstrument con un metodo .CalculatePrice () e .CalculateRisk (). Lo farei a OptionInstrument e EquityInstrument con implementazioni specifiche dei calcoli del rischio e del prezzo.

Ma a causa della Single Responsibility (e poiché ci sono molte metriche diverse da calcolare, non solo prezzi e rischi), ora ho una classe RiskCalculator separata e una classe PriceCalculator con implementazioni specifiche derivate come OptionPriceCalculator e EquityPriceCalculator, ecc.

Quando voglio scorrere gli strumenti e calcolare i prezzi, ho bisogno di un qualche tipo di classe di dispatcher di tipo o fabbrica che mappa gli strumenti per correggere la classe di calcolo dei prezzi, che è semplicemente sbagliata e non molto orientata agli oggetti. Ho visto questo tipo di pattern emergere sempre più nei miei progetti.

Sto facendo qualcosa di sbagliato o c'è un modo migliore per evitare queste fabbriche di "tipo dispatcher"?

    
posta Bjorn 11.12.2014 - 13:21
fonte

2 risposte

1

Se le tue classi dovrebbero sapere come calcolare un rischio, un prezzo o altre metriche, dipende da cosa dipendono da questi.

es. per calcolare un prezzo, se hai solo bisogno di tutte le proprietà di FinancialInstrument , allora dovrebbe essere implementato dalla classe stessa. Se tuttavia avete bisogno di alcune dipendenze esterne come la tassa di un paese specifico, il calcolo (o almeno questa parte specifica del calcolo) dovrebbe essere fornito da un'altra classe. Se provi a spiegare che cos'è un semplice strumento finanziario a una terza persona e non utilizzi le tasse come parte della spiegazione, allora questo è un buon segno che le tasse non dovrebbero far parte del classe.

Non hai nemmeno bisogno di dispatcher di tipi per farlo. perché le parti comuni dovrebbero essere implementate nelle classi stesse e le parti dipendenti dalla classe dovrebbero essere implementate in un'altra classe. Se utilizzi dispatcher di tipi, è probabile che le tue classi abbiano qualcosa in comune che dovrebbe essere modellato da un'interfaccia in modo che tu possa utilizzare 1 classe aggiuntiva che può gestire entrambe le classi nello stesso modo senza dover usare qualcosa come instanceof o entrambe queste classi non sono le stesse, quindi hai bisogno di 2 classi aggiuntive (e semantiche diverse).

Se fornisci ulteriori informazioni semantiche sulle tue classi menzionate, posso anche dare un esempio concreto di come implementarlo. =)

    
risposta data 11.12.2014 - 15:27
fonte
3

Sembra che il pattern Visitor possa essere utile. Non si sbarazza del codice di spedizione, ma è un modo abbastanza elegante e standard per implementare il dispacciamento. In OOP in stile Java:

// an interface for anything calculatable
interface FinancialInstrument {
    // acceptVisitor makes the pattern obvious,
    // but you might want to pick a more domain-specific name.
    <T> T acceptVisitor(FinancialInstrumentVisitor<T> v);
}

class Option implements FinancialInstrument {
    <T> T acceptVisitor(FinancialInstrumentVisitor<T> v) {
        return v.visit(this);
    }
}

class Equity implements FinancialInstrument {
    <T> T acceptVisitor(FinancialInstrumentVisitor<T> v) {
        return v.visit(this);
    }
}

// an interface for all calculators
// basically, this consists of "visit" overloads that handle each type
interface FinancialInstrumentVisitor<T> {
    T of(FinancialInstrument fi); // convenience wrapper
    T visit(Option o);
    T visit(Equity e);
}

class PriceCalculator implements FinancialInstrumentVisitor<Price> {
    Price of(FinancialInstrument fi) {
        return fi.acceptVisitor(this);
    }
    Price visit(Option o) { ... }
    Price visit(Equity e) { ... }
}

class RiskCalculator implements FinancialInstrumentVisitor<Risk> {
    Price of(FinancialInstrument fi) {
        return fi.acceptVisitor(this);
    }
    Risk visit(Option o) { ... }
    Risk visit(Equity e) { ... }
}

Esempio di utilizzo:

PriceCalculator price = new PriceCalculator();
for (FinancialInstrument fi : portfolio) {
   doSomethingWith(price.of(fi));
   ...
}

Il modello di visitatore è ottimo quando vuoi essere libero di aggiungere facilmente nuove operazioni, ma l'insieme di oggetti su cui queste operazioni funzionano è abbastanza fisso. Si noti che l'aggiunta di un tipo di strumento finanziario richiede un sovraccarico da aggiungere all'interfaccia FinancialInstrumentCalculator , che non è retrocompatibile: tutti i consumatori di tale interfaccia dovranno essere aggiornati per supportare tale metodo. Questo non è il caso se il nuovo strumento finanziario è un sottotipo di una classe esistente, in quanto può essere utilizzato il gestore esistente.

Se i nuovi strumenti finanziari si presentano più spesso delle nuove operazioni, l'utilizzo del modello di visitatori è probabilmente una cattiva idea. Nota che qualsiasi strategia tu usi per abbinare n operazioni a m strumenti finanziari, sarai sempre (sottotitoli ignorati per un momento) e finirai con n·m metodi per codificare.

Se la lingua lo consente, consiglio vivamente di utilizzare tratti o costrutti simili per fornire valori predefiniti nell'interfaccia FinancialInstrumentCalculator . Per esempio. quando ho tre tipi

class A implements FinancialInstrument
class B extends A
class C extends A

quindi i metodi visitatore per B e C possono essere implementati dal metodo visitor per A per impostazione predefinita:

interface FinancialInstrumentVisitor<T> {
    default T of(FinancialInstrument fi) { return fi.acceptVisitor(this); }
    T visit(A a);
    default T visit(B b) { return visit((A) b); }
    default T visit(C c) { return visit((A) c); }
}
    
risposta data 11.12.2014 - 15:27
fonte

Leggi altre domande sui tag