In my last article I described using composition in the
design of an object based system. In that article I included a simplified UML
class diagram of some work I did to a fictitious Product object to display the
use of composition. Please see Figure 1 below. At the top of that diagram
sits the class ProductBase. It is an abstract class (a class that has
abstract methods and cannot be instantiated). But why is it abstract?
Figure 1 - Product UML
Let us first ask what could the class be marked as if it
were not an abstract class? Here, there are two options. One, it
could be a concrete class and two, it could be an interface. Let us look
at the concrete class first.
A concrete class is any class that is not abstract; it is
just a class. The following code snippet contains a concrete class followed by
an abstract class.
Listing 1 - Concrete and Abstract
public class StockStatus
{}
public abstract class ProductBase
{
abstract public StockStatus InventoryStatus{get;};
}
When a class is concrete that means the only way to extend
the class is by inheriting from it. That is often referred to
as implementation inheritance or class inheritance because you are in essence
inheriting all of the code and logic that went into the base class. That
is not something I wanted to do with the Product object because it blows up
encapsulation and is very inflexible.
The second option was to use an interface. Here, we
would define an interface and then have ProductBase implement that
interface. In the future, we could then substitute any class that
implemented the interface.
Listing 2 - Using an interface
public interface IProduct
{
int GetNumberInStock();
}
public class ProductBase : IProduct
{
public int GetNumberInStock()
{
return 3;
}
}
In my first go around, this is basically what I had.
Of course, that is why design reviews are important. It was pointed out
by a coworker during a review that using an interface in this instance does not
buy you anything more than using an abstract class, and if an abstract class
were in fact used, we could mix in a little bit of implementation. Sounds good
right? At first I did not like it at all.
"That breaks encapsulation!" I bellowed across the
table, and let it reverberate off the walls and deaf ears. No one wanted
to hear it. Why? Because using an abstract class here was simply a better
design decision.
In this instance, a Product, no matter how many times subclassed,
is a product. It has properties and some core methods. So in our abstract
class we defined all of our entity properties implementation but left the
"Load" (of data) methods as abstract so that the implementing class
could define those. It ended up as an unintentional use of the template
method, which is a good side effect of paying attention to your design. I
believe that the decision is a mix of the best of both worlds.