If you pick a developer off the street and ask him what inheritance is, he will more than likely give you the correct answer. If you take that same developer and ask him what object composition is, he may give you a puzzled look and shrug his shoulders.
Fundamentally, both are ways of extending your application. Composition is preferred and inheritance is mostly just abused.
Extension By Inheritance
For our first code example, we are going to be extending a class in the .NET framework, the System.Random class. You've probably used it to build a password generator in your application. The object as it exists in the framework is only capable of choosing random numbers, but we can very easily extend it to choose pretty much anything at random.
public class ExtendedRandom : Random, IRandom
{
public T Next<T>(IList<T> items)
{
int randomIndex = Next(0, items.Count - 1);
return items[randomIndex];
}
}
As an interesting side note, many developers don't think to inherit from things in the framework. You can actually get some really cool stuff that way.
The System.Random class doesn't implement an interface or abstract class by default, but since we are extending it by inheritance already, we can extract an interface and create an interface adapter at the same time. Yep, it's a two-for-one special. The interface buys us Test-Driven goodness.
public interface IRandom
{
T Next<T>(IList<T> items);
int Next();
int Next(int minValue, int maxValue);
int Next(int maxValue);
double NextDouble();
void NextBytes(byte[] buffer);
}
Yes, it was that simple, but now what about composition?
Extension by Delegation (aka Object Composition)
Now that we have a nice random implementation, building a password generator is a cinch. Our generator will use an IRandom to do its job. This delegation of responsibility (random selection of numbers and characters) is at the heart of object composition.
Here is what we want for our end product:
public interface IGenerator
{
string Generate();
}
Here's our [very simple] implementation:
public class RandomGenerator : IGenerator
{
public RandomGenerator(int minLength, int maxLength, IRandom random, IEnumerable<char> characters)
{
_minLength = minLength;
_maxLength = maxLength;
_random = random;
_characters = new List<char>(characters);
}
private readonly int _minLength;
private readonly int _maxLength;
private readonly IRandom _random;
private readonly IList<char> _characters;
public string Generate()
{
int length = _random.Next(_minLength, _maxLength);
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++ )
{
builder.Append(_random.Next(_characters));
}
return builder.ToString();
}
}
Viola! We now have a very simple password generator which clearly exposes the difference between these two fundamental concepts in object-orientation. If we check our classes against the other basic principles (Single Responsibility, Open/Closed Principle, etc), we see that we are in the clear.
Where do we go from here?
Let's build simple Password and PasswordFactory classes. For this demonstation, we won't add any behavior to the Password class, but you can bet that we would for our real application. Encapsulation is one of the fundamental concepts of object-orientation. In a nutshell, encapsulation is the grouping of data and the methods which manipulate it into a single object. Data without behavior is an object-wannabe.
Here's our first run of the Password:
public interface IPassword
{
string Value { get; }
}
And the [non-OOP] implementation:
public class Password : IPassword
{
public Password(string value)
{
_value = value;
}
private string _value;
public string Value
{
get { return _value; }
}
}
Pretty simple stuff, but how do we connect our IGenerator to our Password? Enter the factory. Here's our end goal:
public interface IPasswordFactory
{
IPassword Generate();
IPassword Create(string value);
}
And now we tie it all together with composition:
public class PasswordFactory : IPasswordFactory
{
public PasswordFactory(IGenerator generator)
{
_generator = generator;
}
private readonly IGenerator _generator;
public IPassword Generate()
{
return Create(_generator.Generate());
}
public IPassword Create(string value)
{
return new Password(value);
}
}
Bingo! We check off our principles of OOP, make sure all our unit tests are passing and call it a day! Since all of our implementations depend on interfaces (and we didn't couple any implementation to another implementation), we now have a loosely-coupled subsystem for our application. We can lean on a dependency-injection tool (such as StructureMap, Castle Windsor, or Spring.NET) to wire things together for us at runtime.
If you look closely, you might also notice the Strategy Pattern. Yep! The IGenerator is a strategy!
If you look even closer, you might notice that the IPasswordFactory gives us a boundary to this subsystem in our application. Any other part of the application which deals with passwords will only have to deal with this one type. This simplification of relationships inside our application makes our application easier to maintain and understand.