RR: Avoiding Artificial Class Hierarchies

Polymorphism is a cornerstone of object oriented programing. It allows run-time resolution of the class function (method) to be called, depending on concrete type of the object. This is of course extremely useful, but requirement for run-time resolution of function called should not be by itself the reason for deciding on a deep and complex class hierarchy. If run-time resolution is all that is required, then much simpler solutions based on function pointers, or boost::function object are much better suited.

Example

Here is a very simplified example from financial engineering of how one could easily get into complex hierarchies:

class Opt {
public:
  /// Returns true if a barrier has been hit
  virtual bool barrier(double S) =0;
  /// Returns payoff at maturity for underlying price S
  virtual double pay(double S)   =0;
};

I.e., a simple abstract virtual class that defines two functions, barrier and pay. To define a concrete class you need to override the two pure virtual definitions with appropriate definitions. Again a very simple example of a concrete class:

class Vanilla:
  public Opt
{
  /// Strike
  double K;
public:
  bool barrier(double S)
  {
    return false;
  }
  double pay(double S)
  {
    return (S-K)>0 ? S-K : 0;
  }
};

Problem

The above classes work fine, and in this particular example may be the best way to describe the problem. But, let us see what the potential problems with this approach are.

Imagine you wish to maximise code re-use and minimise the type of copy-and-paste programming that sometimes blights large project. Also imagine there is a number of types of Opt that have no barrier, i.e., barrier should always return false. To prevent code duplication, it is nice to solve explicitly through inheritance:

class NoBarrier:
  public Opt
{
public:
  bool barrier(double S)
  {
    return false;
  }
};

class Vanilla2:
  public NoBarrier
{
  /// Strike
  double K;
public:
  double pay(double S)
  {
    return (S-K)>0 ? S-K : 0;
  }
};

But thinking along the same, there are also a number of sub-types of Opt that have the pay of the type that Vanilla has. One would then do:

class PlainPay:
  public Opt
{
  /// Strike
  double K;
public:
  double pay(double S)
  {
    return (S-K)>0 ? S-K : 0;
  }
};

class Vanilla3:
  public NoBarrier,
  public PlainPay
{

};

This resolves the code duplication issues, but at a cost of having a fairly complex class inheritance hierarchy. In this problem this may be optimal solution, but sometimes what is required is something that is simpler.

Alternative

There are two basic requirements which lead to the hierarchy above:

  • We wished to keep two functions together that weren't necessarily closely related (in this example barrier and pay)
  • We wanted to minimise code duplication and maximise testability

If this is really all that is required, that it can be implemented directly very simply and without a complex class hierarchy:

#include <boost/function.hpp>
#include <boost/bind.hpp>

struct Opt {
  /// Returns true if a barrier has been hit
  boost::function< bool (double x) >  barrier;
  /// Returns payoff at maturity for underlying price S
  boost::function< double (double x) >  pay;
};

bool noBarrier(double S)
{
  return false;
}

double plainPay(double S, double K)
{
  return (S-K)>0 ? S-K : 0;
}

/// Return a plain vanilla option
Opt Vanilla(double K)
{
  Opt o;
  o.barrier=noBarrier;
  o.pay=boost::bind(plainPay , _1,K);
  return o;
}

Explanation

The basic requirement that we want to keep two functions together is easily implemented as a structure of two function objects. In many ways this is making explicit what the virtual function mechanism in C++ does behind the scenes. The implementation:

struct Opt {
  /// Returns true if a barrier has been hit
  boost::function< bool (double x) >  barrier;
  /// Returns payoff at maturity for underlying price S
  boost::function< double (double x) >  pay;
};

is based on boost::function objects rather than plain pointer because of the extra flexibility they provide (see boost manual page for more details). The run time resolution of which function to call is extremely explicit, in that at any point in the program the value of barrier or pay members can be assigned a different function or object.

Next up are the definitions of the pay off and barrier functions. Because they can now be free functions, the are extremely simple and self contained.

Finally, there is a function that which creates an object with equivalent functionality to the example we had at the start:

/// Return a plain vanilla option
Opt Vanilla(double K)
{
  Opt o;
  o.barrier=noBarrier;
  o.pay=boost::bind(plainPay , _1,K);
  return o;
}

Clearly this approach allows easy mix and match of functions to use for the barrier and pay members. In this example, I made use of the boost::bind function to avoid having to write the trivial class which just holds the value K (c.f. PlainPay above). Note that _1 is the placeholder for the parameter which is left free by the bind function.

Summary

Classes, virtual functions and inheritance are not the only way of keeping functions together while minimising code duplication. Plain struct of boost::function members can do that very easily and sometimes more appropriately. The benefits of this later approach are:

  • No need to create artificial class hierarchies
  • Encourages writing simple, free standing functions, that can more easily be shown to be correct

Update 1

From some of the comments I have (gratefully) received I realise I have not fully explained the advantages of the function pointer approach.

Imagine that the two virtual functions in the implementation with classes need to have five different concrete implementations each.

In the simplified example that I am using, this would correspond to five different payout functions (e.g., call payout, put payout, digital payout, etc) and five different types of barriers (down-and-out, up-and-out, etc). In order to capture every combination of these possibilities it would be necessary to declare 25 different classes, e.g., CallNoBarrier, CallDownAndOut, PutNoBarrier, and so on.

This requirement is removed using the function pointer approach. Furthermore it is possible to constructs the required structure piecewise. For example one function could decide on the appropriate barrier type to insert into the structure Opt, while another function would decide on the right payout. Example declarations:

/// Based on user supplied string, decide on the correct barrier
void userBarrier(Opt &o,
                 const std::string &s)
{
    .......
}

/// Based on user supplied string, decide on the correct payout
void userPayout(Opt &o,
                const std::string &s)
{
    .......
}

 /// Return an option based on a user-supplied string specifying it
 Opt userOpt(const std::string &s)
 {
   Opt o;
   userBarrier(o,s);
   userPayout(o.s);
   return o;
 }