Here's something I see time and time again from new entrants to the
OO world in .NET. If you want to make your code more stable,
reliable, and extensible in certain aspects of enterprise systems you
must embrace the concepts of coding to interfaces, not inheritance to
keep yourself from digging into a nightmare down the road. To
better explain what I'm talking about, here's a very simple example to
help show the concept:
Let's say you have a system with a method that performs a
calculation. What the calculation is doesn't really matter, it
could be a sales tax calc, or anything really. The important
point is that currently your company supports this calculation in
Ohio. So let's make a theoretical function that takes two double
inputs and in Ohio the rules are that they should be added
together. So we end up with this in our Ohio calculations class:
Public Function myCalc(ByVal x As Double, ByVal y As Double) As Double
Return x + y
End Function
Now this is all well and good, but then the company expands into
Michigan. Luckily, Michigan handles this calculation the same way
Ohio does. So being a good little developer we decide to promote
myCalc to an inheritable.
Public MustInherit Class Calculations
Public Function myCalc(ByVal x As Double, ByVal y As Double) As Double
Return x + y
End Function
End Class
Wonderful, now the ohio and michigan classes can inherit from this
file and both of them can share the code. Everything is lovely
and grand and we all pat ourselves on the back for not having written
the code in two places!
However, our celebration is interrupted when the company again grows
and moves into Indiana. Indiana rules say that x + y is totally
bogus, and it needs to be x * y. Now we have a couple options:
- Start messing with our inheritance chain to try to override this function.
- Start putting conditional logic and edit the signature to take in state
- Code to an interface!
Numbers 1 and 2 have some pretty critical flaws. When you
start messing with the inheritence chain and putting in conditional
logic you run the risk of breaking existing functionality and in the
case of the signature edit we now have to modify all the consumers of
our inheritable. This doesn't seem so bad on this small scale,
but imagine if you had all 50 states and each one had a different way
of performing this calculation? This is where coding to
interfaces saves your butt! What we want to ensure is that we encapsulate
our differing logic in such a way that when a change or addition occurs
we do not have to modify our existing, working code base at all.
How do we do this? We take advantage of the power of
inheritance and interfaces. First we need an interface to enforce
the method and an inheritable class that can hold a pointer to our
interface. Like so:
Public MustInherit Class StateStuff
Protected calcs As ICalculations
End Class
Public Interface ICalculations
Function myCalc(ByVal x As Double, ByVal y As Double) As Double
End Interface
So now we have required that every state can hold an ICalculation,
the implementation of which we can define at will. Now we need a
couple classes that implement the interface that hold the differing
logic. Per our story above we have addition and
multiplication. So we'll do something like this:
Public Class AdditionCalculation
Implements ICalculations
Public Function myCalc(ByVal x As Double, ByVal y As Double) As Double Implements ICalculations.myCalc
Return x + y
End Function
End Class
Public Class MultiplicationCalculation
Implements ICalculations
Public Function myCalc(ByVal x As Double, ByVal y As Double) As Double Implements ICalculations.myCalc
Return x * y
End Function
End Class
Now for the states. Per our story above we have 3 states and 2
cases, one case is addition and one is multiplication. Therefore
we can go a couple routes. One route is to make the various
states have their own classes. This has the downside of creating
a lot of classes, but ends up being fairly clean if there are a lot of
differences between states. In the constructor of the state class
we will set the ICaculation that state should use like so:
Public Class OhioStuff
Inherits StateStuff
Public Sub New()
calcs = New AdditionCalculation
End Sub
End Class
Public Class IndianaStuff
Inherits StateStuff
Public Sub New()
calcs = New MultiplicationCalculation
End Sub
End Class
Notice how our constructor properly lets the type of calculation
that should be performed. All we would need to expose this to the
system is an accessor method! Even more powerful, we can now
create methods elsewhere in the system that take in the generic
StateStuff as a parameter, and inside that method we can call
calcs.myCalc and it will always perform the proper calculation
regardless of what state is in use. No conditional logic
needed! That's flexibility! In addition, we can guarantee that if we add new types of calculations, like subtraction, and new states that the changes will never break existing code!
The second way to approach this from the end class model is to have
one generic class and a factory that assigns the various interface
classes based on whatever conditional logic you have. The factory
method would look something like this:
Public Class StateStuff
Public calcs As ICalculations
End Class
Public Class StateFactory
Public Shared Function GetState(ByVal stateName As String) As StateStuff
Dim s As New StateStuff
Select Case stateName
Case "OH" Or "MI"
s.calcs = New AdditionCalculation
Case "IN"
s.calcs = New MultiplicationCalculation
End Select
Return s
End Function
End Class
So at the end of the call you get a state class with its custom
calculation implementation. A new state or interface class would
require editing the factory, but all the other code should be unchanged
and protected from breakage!