Sunday 24 August 2008

Behold!, Strategy Pattern will take care of it..!

We developers often have the common problem of programming the implementation and NOT the interface when we hurriedly develop software. Now, there are instances where 'test first code later' approach also might fail. This might happen when we misjudge the design issues and jump straight into what we want to achieve, but failing to consider issues like scalability, asking questions as to how easy will it be to accommodate changes in the future etc. Now, I believe, this is where experience comes for a cost !

Hmm.. until the day comes when your code needs to prove it's worth when changes or features are requested. It might seem rather easy at first until you start breaking feature after feature just by trying to add a tiny little feature.

Problems!!, After all, How would the world run without them!

Now, consider the following class hierarchy. I've taken a really trivial example here. The idea is to focus on the pattern and not on the problem!, remember we're building software that is ready for change in other words easily maintainable. maintenance is cost!.

ClassHeirarchy_Original

The abstract class 'Graph' has an abstract 'DrawGraph()' method. It also has a protected member List<int>> data. This is intended to be used by the DrawGraph() method. The abstract class also has implementations for SetUpGraphics(), which sets up the graphics device with double buffering mechanism let's say and a SetData() method which initialises the member variable 'data'.

The subclasses (sub-types: according to the design principle - sub class it iff the subclass is a subtype) PieGraph, LineGraph and BarGraph have their own implementations for 'DrawGraph()'. Each of which has it's own algorithm to draw the graph.

------------------------------------------------------------------------------------------------

For instance, the PieGraph's DrawGraph() method will draw a 360 degrees pie chart and map every value of data to an angle.

For example

data[0] maps to a 60 degrees spread.

data[1] maps to a 45 degrees spread and so on..

------------------------------------------------------------------------------------------------

The LineGraph's DrawGraph() method will draw a line graph indicating points on the xy-plane for every value of 'List<int>data'.

------------------------------------------------------------------------------------------------

The BarGraph's DrawGraph() method will draw a bar graph (column) for every value in 'List <int> data'. Let's also assume that it has flexibility to show graphs as columns or rows.

------------------------------------------------------------------------------------------------

It's clear that the 'DrawGraph()' method varies across all three implementations and every implementation has it's own algorithm. Now, Time to ask yourself questions.. Assume this structure has met the requirements and is working as expected. That's where most of us would stop the design and go home singing 'sweet home alabama', only to come back to it a few months later when we cannot escape the inevitable, CHANGE !!, It would be a new feature request by the client or porting it to a different platform, you never know, it's the business which grows and impacts the software or brings about change. (another favourite topic of mine ). Now, let's say you need to put in 3 new features

1] A mechanism to draw XY Axis scale dynamically.

Here X and Y unit values depend on a provided data set. Let's say the data set used to plot the graph are the values of used homes in the UK. ( in thousands) data set = {115.8,113,183.5,142.7,144.2,85.9,170.1,129.3}. Now, Given the data set, the graph should generate 8 Units on the X-Axis and a proportionate scale on Y-Axis (0-200 would be fine in this case).

2] Calculate Range,Variance and Standard Deviation of the data set.

3] Users should be able to resize the graph dynamically - Automating Scaling.

Ok,let's start reworking our class structure to add these features.

Let's start with feature 1]. Auto generating Units for XY based on Data. By looking at the structure, It's very clear that by adding a method in Graph Class (Virtual Method), the classes LineGraph and BarGraph can override it and implement automatic generating of XY units. But what would happen to PieGraph ?. It will have to do nothing. In other words, override to give no implementation.

PieGraph.AutoGenerateXYUnits() { //no implementation }

Now, to overcome this **side effect ** , we can think of sub classing LineGraph and BarGraph further. i.e, it can be derived from a class called CordinateGraph. CoordinateGraph derives from Graph. AutoGenerating XY Units feature can then be implemented in the Co-ordinateGraph class as a virtual method. So that it can be overridden in the LineGraph and BarGraph Classes. Notice as we try to do such modifications, the behaviours become very tightly coupled to it's implementations and it will eventually become harder and harder to maintain such code simply because adding a feature to the base class Graph will break the system, introduce side effects as we saw above.

Ok,

How about 'interfacing' the AutoScaleXYUnit behaviour ?. This might sound interesting as only the required classes implement the behaviour.

ClassHeirarchy_Mod1

But, notice carefully, you will have to repeat implementations for all the classes which implements the IAutoGenXYUnits interface. Lets say in the future, if there were to arise a situation where 10 different classes need to implement this behaviour , then there will be 10 implementations, and you introduced another maintenance nightmare.. no code reuse. I'll come back to this.

Think of the possible implementations for feature 2] Calculating Range,Variance and Standard Deviation for the data set. It could be a very straight forward implementation. add it to the base class {as a feature} ?, Yes, it would do the job for all the 3 Subclasses and if the method is virtual, it would be even better. Let's agree that this is pretty straight forward virtual method implementation in the Class 'Graph'. (and it would do it's job )

And finally, possible implementations for 3] Automatic scaling of the graph at runtime. This behaviour varies across all there sub classes. If you were to think that adding a method to scale the graph in the Graph Class would be sufficient. What if PieChart needed no scaling at all ? , LineGraph and BarGraph needed vertical and horizontal scaling respectively ?, Side effects would occur, code would break..

A good solution to 1] and 3] would be to use the strategy pattern. Some general design principles that I've considered when re factoring this to use a strategy pattern. And most of them would work for behavioural patterns***

- The open closed principle: open to extension but closed for modification.

- Program the interface

- Use Composition over inheritance

- Cohesion

- Tight coupling vs Loose coupling

Caution: It might be argued that some of the design principles do not apply to every kind of a problem. For e.g. Huge Enterprise Applications with millions of users. See Enterprise Application Patterns. Here, the design would primarily focus on issues like Scalable architecture, Service oriented design, concurrency, minimizing round trips to the server etc. and it can be noticed that what seems a perfect solution for a stand alone app might be dreadful and disastrous one for Enterprise Apps.

Identifying what 'changes' and what 'remains'. A general trick is to encapsulate what changes and keep what stays. The Draw method clearly varies across all three implementations. So, Let's make a 'IDrawable' interface out of it. Similarly for scaling the graphs dynamically, we could think of an 'IScalable' interface. We go ahead and provide concrete implementations to them. The concrete implementations to the IDrawable interface would be

--o IDrawable { DrawBar, DrawLine , DrawPoint, DrawPie }, This also takes care of feature request 1]. Similarly, the IScalable would have

--o IScalable { VScale, HScale, VHScale, NoScale } concrete implementations. Now, We can add the interface references to the 'Graph' class so that the classes BarGraph, LineGraph and PieGraph are free to chose their implementations dynamically at runtime. Cool innit ? . ( This is the open closed principle). We are giving the interface, but not the modifiable implementation to the 'Graph' class.

The strategy pattern oriented solution will look like the following.

The IScalable interface and it's concrete implementations

IScalable

The IDrawable and it's concrete implementations

IDrawable

The Graph Class hierarchy

MainClasses

The Final Solution

Strategy

The conclusion.

------------------------------------------------------------------------------------------------

I'll leave the Pros and Cons to the reader to decide and evaluate. But the strategy pattern's promise for sure is that it will allow your algorithms to evolve independently of your main class hierarchy. So there won't be any side effects or breaking up of the hierarchy.

------------------------------------------------------------------------------------------------

Finally some generalizations..

------------------------------------------------------------------------------------------------

If you are not inviting change, I'm sure you're not in software development. This is where experience comes for a cost!!! it counts where it matters most!!! I've always felt there are 2 kind of developers in this world. Developers who know what to do AND the incapable or often 'confused' mutts. The incapable mutts fall into a zillion multi-disciplinary categories. So, think of the design principles while writing or re factoring code. Keep it flexible, easy to maintain and above all watch out for patterns.

------------------------------------------------------------------------------------------------

*** Behavioural Patterns: Patterns that are most concerned with communication between objects. They are also concerned with encapsulating behaviours in an object and delegating requests to it.

------------------------------------------------------------------------------------------------

Some Links:

1] A claim that Java's extend is evil

2] Classes in the .NET BCL. Also take a look at .NET 3.5 LINQ Classes.

3] Java 2 API: Take a look at Line,Line2D.Double and Line2D.Float Classes. A more comprehensive study (to make life hell) would be to consider

java.lang.Object

-java.util.AbstractCollection

-java.util.AbstractList

-java.util.AbstractSequentialList

- java.util.LinkedList

SourceCode: Here

Comments / Criticisms / Discussions are welcome.

No comments:

Post a Comment