I am working on implementing some stock order types for a financial technology application. There are six different types of stock orders – market, limit, stop_loss, stop_loss_limit, trailing_stop, and trailing_stop_limit. These orders share a minimal subset of properties like IsBuyOrder and TimeInForce. However, they each add their own unique properties as well. For example, stop_price is only relevant to stop_loss and stop_loss_limit order types, and trailing_stop_amount is only relevant to trailing_stop and trailing_stop_limit orders.
As I see it, there are two implementations I can choose from to implement this.
- I can have one relatively large order class with a lot of nullable properties. This class would exposes all of the fields of all the various order types. Many of these fields would be null as they might not apply to the specific type of order that is being created. The advantage of this solution is it’s shorter than #2 below, the disadvantage is the class isn’t as easy for consumers to understand and utilize.
- I can have an abstract base class called Order which contains the properties common to all order types, and have six child classes implementing the specific order types. The advantage of this solution is it’s more clear to the consumer of the class what properties they must set in order to get their order filled. The disadvantage is this solution is quite long as I will have seven different classes.
Is #2 better than #1? Is it better to have a large inheritance hierarchy or a single object with lots of null fields? Just looking for some guidance here…
13
The option 1 looks appealing as it could seem simpler. However it comes with numbers of drawbacks:
- The design does not conform with the open/closed principle: every time, you’d add a new order type, you’d need to modify the code and you might break things that worked well before.
- Your code will need a lot of
if/else
orswitch/case
to account for the different variations. It is error error-prone (you might forget a case) and difficult to maintain. - The code is data-driven and not object oriented: you query data and do things with it, instead of telling the order to do something and let the order deal with the specific details (tell don’t ask). Moreover, this might constraints encapsulation, and lead to hidden coupling that will also make maintenance difficult.
- Over time, due to the missing abstraction, it’ll easy to lose connections. For example it’ll be easy to forget that
stop_price
is relevant tostop_loss
andstop_loss_limit
and not the others.
In summary, you may get faster with option 1 in the short run, but you’ll pay a hard maintainenance price in the long run.
The option 2 looks more promising. In fact, I assisted in the 90’s to a series of conference about object oriented technologies in financial services, and your example was comonly used to promote inheritance and the ease it gives to innovate with new financial products.
Nowadays, we know that deep order hierarchies are in fact also coming with a cost. So you may want to consider Option 3:
- Create a class for the order with the common behavior
- Encapsulate the differences, preferring composition over inheritance, and more particularly, using a strategy pattern to isolate the pricing strategy which varies, from the order logic which is common.
This may further allow to break down the strategies in smaller pieces, i.e. stop price or not, start price or not, timing or not, and combine a few basic strategies to achieve more complex strategies without redundant code.
3
Option 1 allows nonsensical objects (combinations of properties that are meaningless) and it wastes memory. This is not good. Option 2 (use inheritance) is better, it provides guidance and does not allow nonsense.
Instead of having an IsBuyOrder property you could implement IBuy, declare it as implemented and test for that implementation when you want to know if it’s a buy order. And do something similar for other types. This allows you to have processing methods for different order types that take an interface as an argument which is as type safe as can be.
Basically what @Alexander said, only he gave up for some reason 🙂 There are no such things as objects without behavior, or methods. Data Transfer Objects do not exist in OO, nor, and this might be a better argument, in maintainable code.
Also, I did actually work on a trading middleware software, where I did something very similar to what you describe.
My polymorphic behavior was execute()
. When called, the Order
contacted the backend system and filled out the order as entered by the user. There were several order types, similar to yours, which did slightly different things. We also had a completely different market type (OTC), where we did not contact our backend, but another institution. All of this was “hidden” behind the simple polymorphic call of execute()
.
Anyway, my point is pure data structures make code brittle. You can’t change it without tracking down who uses it and for what. Use proper object design. Encapsulate data, find the behavior that you actually really need.