5

I've realized that since I've been more and more strict about the SOLID principles, my code tends to consist of more pure data objects and a lot of "operator" classes which seems to go against oop-principles. Specifically I often end up with "type dispatchers" that maps a specific object type to a suitable operator class.

Let me take an example:

In the old world I would have a base class FinancialInstrument with a .CalculatePrice() and .CalculateRisk() method. I would derive that to OptionInstrument and EquityInstrument with specific implementations of risk and price calculations.

But due to Single Responsibility (and since there are lots of different metrics to calculate, not just prices and risks), I now have a separate RiskCalculator class and a PriceCalculator class with derived specific implementations such as OptionPriceCalculator and EquityPriceCalculator etc.

When I want to iterate over instruments and calculate prices, I need have some kind of type-dispatcher class or factory that maps instruments to correct price calculation class, which just feels plain wrong and not very object oriented. I've seen this kind of pattern emerge in my projects more and more.

Am I doing something wrong or is there a better way to avoid these "type-dispatcher" factories?

9
  • Inventing separate FooManipulator classes instead of overwritten methods doesn't seem like a good consequence of SRP to me in the first place. Could you elaborate? – Kilian Foth Dec 11 '14 at 12:40
  • In this example, I would end up with 25 different metrics that need to be calculated. I would like to avoid "polluting" the FinancialInstrument class with lots of Calculate-methods (which signatures might change), hence moving it to separate classes with a single responsibility (Calculating Risk) – Bjorn Dec 11 '14 at 12:48
  • I understand that, but isn't this deliberately avoiding the idea of dynamic dispatch, i.e. the very thing that one wants OOP for in the first place? – Kilian Foth Dec 11 '14 at 13:02
  • Well, that is sort of my problem. The typical example of SRP is a book class that "prints" itself (bad), which should be moved into a "Printer" class that prints books (good). Which seems like a simpler version of my problem above? – Bjorn Dec 11 '14 at 13:18
  • 2
    @Bjorn Why do you assume dispatching on type is bad under every circumstance? Sometimes things have a finite number of alternatives. E.g. a tree can be empty, or it can be a node that contains other trees. You won't be adding new alternatives to that. Checking which one it is works fine when the set of alternatives is fixed and you have a way of enforcing that all alternatives are handled. – Doval Dec 11 '14 at 14:37
1

If your classes should know how to calculate a risk, a price or other metrics, depends on what these are dependent on.

E.g. to calculate a price, if you only need all the properties of FinancialInstrument then it should be implemented by the class itself. If you however need some external dependencies like the tax of a specific country, then the calculation (or at least this specific part of the calculation) should be provided by another class. If you try to explain what is a simple plain financial instrument to a third person and you don't use taxes as the part of the explaination then this is a good sign that taxes should not be part of the class.

You also don't need type dispatchers to do this. because the common parts should be implemented in the classes itself and the parts that are class dependent should be implemented in another class. If you use type dispatchers then chances are high that either your classes have something in common which should be modeled by an interface so that you can use 1 additional class which can handle both these classes in the same way without having to use something like instanceof or both these classes are not the same so you need 2 additional (and semantical different) classes.

If you give additional semantic information about your mentioned classes then I can also give a concrete example how I would implement it. =)

1
  • Thanks. The calculations definitely needs other dependencies, such as access to current risk-free-rates etc, and some calculations can be rather complex, which is why it feels right to have them in separate classes. The way to calculate the price of an Option is very complex and you need access to its underlying instruments etc, while calculating the price for an Equity is very simple. – Bjorn Dec 11 '14 at 15:02
3

This sounds like the Visitor Pattern could be helpful. It does not get rid of the dispatching code, but it is a fairly elegantly and standard way to implement dispatching. In Java-style OOP:

// 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) { ... }
}

Example usage:

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

The visitor pattern is great whenever you want to be free to easily add new operations, but the set of objects these operations work on is fairly fixed. Note that adding one type of financial instrument requires an overload to be added to the FinancialInstrumentCalculator interface, which is not backwards compatible – all consumers of that interface will have to be updated in order to support that method. This is not the case if the new financial instrument is a subtype of an existing class, as the existing handler can be used then.

If new financial instruments turn up more often than new operations, then using the visitor pattern is probably a bad idea. Note that whatever strategy you use to match n operations to m financial instruments, you will always (subtyping ignored for a moment) end up with n·m methods to code.

If your language allows this, I highly recommend to use traits or similar constructs to provide defaults in the FinancialInstrumentCalculator interface. E.g. when I have three types

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

then the visitor methods for B and C can implemented by the visitor method for A by default:

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); }
}
1
  • 1
    Yes, I'm aware of the visitor pattern and maybe its the right choice here. However, I personally dislike this pattern, since its difficult to work with and sort of breaks the Open-Closed Principle. Thanks for your answer anyway! – Bjorn Dec 11 '14 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.