Shapes Don’t Draw

Shapes don’t draw. The circle on the jotter next to me didn’t draw itself, I drew it – and it’s not very round! There’s a misconception (as advocated by many Object Orientated Programming 101 courses) that shapes draw. I believe this leads to some problem designs. Here’s one such example.

Trades Don’t Price

In most financial establishments a Trade class is fairly key to most applications and it is an OO modellers dream. There are Swaps, Futures, Options, Options on Futures, Swaps on Futures, Options on Swap Futures, and so it goes on. I have no problem with the deep object hierarchy that ensues, as it reflects the domain. Unfortunately what happens next is that everything that could possibly happen to a trade gets stuffed into the Trade class and and its unfortunate descendants.

One such method I happened upon was price(). I happened upon this method because I was writing a unit test that needed a Trade object – but the constructor of Trade threw a NullPointerException! Tracing through the code it transpired that the constructor initialised some members that were references to Singleton instances of services that needed a database connection!

Here is a simplified version of the code (for brevity getters and setters are not included):

class Trade {
	private MarketPriceService priceService;

	int quantity;
	Product product;
	double currentMarketValue;

	Trade() {
		priceService = MarketPriceService.getInstance();
	}	

	void price() {
		currentMarketValue =
			priceService.getCurrentPrice(product) *
			quantity;
	}
}

class  MarketPriceService {
	private static MarketPriceService instance;

	PriceQuery priceQuery;

	private MarketPriceService() {
		priceQuery = new PriceQuery(
			GlobalProperties.getProperties());
	}

	static MarketPriceService getInstance() {
		if (instance == null) {
			instance = new MarketPriceService();
		}
		return instance;
	}

	double getCurrentPrice(Product product) {
		return priceQuery.queryDatabase(product);
	}
}

Now there is obviously a list of issues here, but they emanate from the price method and because of this method I could not write a unit test for anything that used a Trade without creating a connection to a database!

The problem with the price method is that trades don’t price – like shapes don’t draw.

Artists Draw Shapes

So how do shapes get drawn? An Artist draws a shape. The artist has an image of a shape in their mind’s eye and this ShapeInAnArtistsMindsEye guides their hand. It’s as if the artist gives over an ArtisticContext to the shape in their mind’s eye and the shape draws itself.

So shapes do draw? No – not shapes. Let’s think about what else an artist can draw. They can also draw melting clocks and sunflowers and enigmatic women and these are all ThingsInAnArtistMindsEye. It is not the Shape that does the drawing it is the thing in an artists mind’s eye.

Let’s see this with some Java interfaces.

interface Shape {
}

interface Circle extends Shape {
}

interface Artist {
	void draw(Shape shape);
}

interface Canvas {
}

interface Brush {
}

public interface ArtisticContext {
	Canvas getCanvas();
	Brush getBrush();
}

interface ThingInArtistsMindsEye {
	void draw(ArtisticContext artisticContext);
}

This is a lot of stuff to replace a Shape and a draw method! – It is worth it. By fully describing the domain of an artist that draws a shape, we force an implementation that remains true to the concepts of the domain and doesn’t take short cuts that might confuse. The resulting implementation will be much more flexible and easier to test.

Let’s see an implementation. To keep things simple our artist is going to be a two year old called Charlie.

class Paper implements Canvas {
}

class Crayon implements Brush {
}

class CharliesArtBox implements ArtisticContext {

	// Fields injected (or put) into Charlies Art Box
	private Paper paper;
	private Crayon crayon;

	@Override
	public Canvas getCanvas() {
		return paper;
	}

	@Override
	public Brush getBrush() {
		return crayon;
	}
}

class CharliesIdeaOfACircle implements ThingInArtistsMindsEye {
	@Override
	public void draw(ArtisticContext artisticContext) {
		Brush crayon = artisticContext.getBrush();
		Canvas paper = artisticContext.getCanvas();

		// draw a roundish shape on canvas (paper)
		// with brush (crayon).
	}
}

class CharlieAgedTwo implements Artist {

	private Map<Class<? extends Shape>, ThingInArtistsMindsEye>
		thingsCharlieCanDraw =
			new HashMap<Class<? extends Shape>, ThingInArtistsMindsEye>();
	{
		thingsCharlieCanDraw.put(Circle.class,
			new CharliesIdeaOfACircle());
	}

	// Injected (given) to Charlie
	private CharliesArtBox artBox;

	@Override
	public void draw(Shape shape) {
		thingsCharlieCanDraw.get(shape.getClass()
			).draw(artBox);
	}
}

Charlie can only draw circles at the moment – but he can draw them anywhere, and he will! He has a large number of ArtisticContexts to choose from. His favourite being walls and marker pens!

My Paint App Works Fine!

If you wrote a simple Paint application for Java 101 you’re probably thinking that you had a Shape with a draw() method and it worked just fine.

It worked because the domain is small and there is one very simple concept of what a shape is. What is a shape really? It is a GraphicallyRenderable or something else that can legitimately draw.

If I were writing my paint application today I would ensure my shape extended some kind of a GraphicallyRenderable which had the draw() method. This might seem to be unnecessary code but this code acts to document the abstraction correctly. Besides I would hate to have others think that I thought that shapes might draw.

How to Price Trades

If an Artist draws shapes then what prices trades? Some kind of TradePricer maybe. And what would this entity refer to, to price a Trade? A PricingStrategy catalogue perhaps. Pricing a trade may be more than setting a price property, it may involve setting cash flows for instance. These details we leave to our strategies, we are content to get a PricedTrade back. Our model is taking shape.

First the interfaces:

interface Trade {
	Product getProduct();
	int getQuantity();
}

interface PricedTrade extends Trade {
	double getCurrentMarketValue();
}

interface MarketPriceService {
	double getCurrentPrice(Product product);
}

interface PricingContext {
	MarketPriceService getMarketPriceService();
}

interface TradePricer {
	PricedTrade price(Trade trade);
}

interface PricingStrategy {
	public PricedTrade price(Trade trade,
		PricingContext pricingContext);
}

Note that I have introduced a PricingContext that only contains a MarketPriceService. In reality it would contain many more services and other information such as the current business date. Using a context object like this keeps the PriceingStrategy stateless and reduces the temptation on a developer to do something silly like pull in a Singleton.

Here’s a very simple implementation of trade pricing:

class SimpleThing implements Product {
}

class SimpleTrade implements Trade {
	final int quantity;

	SimpleTrade(int quantity) {
		this.quantity = quantity;
	}
	@Override
	public Product getProduct() {
		return new SimpleThing();
	}
	@Override
	public int getQuantity() {
		return quantity;
	}
}

class PricedSimpleTrade extends SimpleTrade
		implements PricedTrade {
	final double currentMarketValue;

	PricedSimpleTrade(Trade original, double currentMarketValue) {
		super(original.getQuantity());
		this.currentMarketValue = currentMarketValue;
	}

	@Override
	public double getCurrentMarketValue() {
		return currentMarketValue;
	}
}

class SimpleTradePricer implements PricingStrategy {
	@Override
	public PricedSimpleTrade price(Trade trade,
			PricingContext pricingContext) {
		double price =
			pricingContext.getPriceService().getCurrentPrice(
				trade.getProduct()) * trade.getQuantity();
		return new PricedSimpleTrade(trade, price);
	}
}

class PricingEngine implements TradePricer {

	// Set by dependency injection
	MarketPriceService marketPriceService;

	private Map<Class<? extends Trade>, PricingStrategy>
		pricingStratagyCatalogue =
			new HashMap<Class<? extends Trade>, PricingStrategy>();
	{
		pricingStratagyCatalogue.put(SimpleTrade.class,
			new SimpleTradePricer());
	}

	@Override
	public PricedTrade price(Trade trade) {
		return pricingStratagyCatalogue.get(trade.getClass()
				).price(trade, new PricingContext() {
			@Override
			public MarketPriceService getMarketPriceService() {
				return marketPriceService;
			}
		});
	}
}

Hopefully you see that, although we have more code, by following the idea that objects only do what they are supposed to do, we have an application that is easier to test and can quickly respond to changing requirements such as an improved pricing algorithm.

Dogs Bark

As developers we are always adding behaviour to ideas that don’t really make sense in the real world – Strings split, Files delete, Statements execute and these are all fine. These are fine just as cakes bake, lights dim and dogs bark but shapes do not draw!

One thought on “Shapes Don’t Draw

  1. Pingback: My 2012 in Review « The Holy Java