Avoiding Inheritance Dependencies Using Generics and Lambdas

8 minutes read

Originally posted on: http://geekswithblogs.net/jolson/archive/2008/06/15/avoiding-inheritance-dependencies-using-generics-and-lambdas.aspx

One realization I have come to when talking with many other developers is that there is a good number of developers that don't realize that inheritance introduces dependencies directly into your classes. Your derived class is now dependant upon the interface and behavior of your base class. Worded another way, your derived class is now strongly coupled to your base class. I've met a number of developers that are very passionate about managing their dependencies via inversion of control, dependency injection, etc. and yet they implement solutions that have a class hierarchy 5+ layers deep.

This strong coupling can become problematic if you need to change either the interface or behavior of my base class. For instance, if I need to add another abstract method I didn't originally think of, every single class that derives from that base class now must change in order to implement that behavior. Or, if I change the behavior of one of the methods in my base class, I can find myself in a situation where one of my derived classes will break because they were relying on that base class behavior to act a given way (even more confusing when the change doesn't impact any other of the derived classes).

Over-using (read: abusing) inheritance in your code can very easily lead to brittle classes that prevent you from being as agile as you need to be. While it is easy for developers to look at dependencies as other external classes your class is dependant upon, you have to keep in mind that your class is also strongly dependant on its base class as well.

The beautiful thing is that with some of the functionality introduced into .NET in both 2.0 and 3.5, you can avoid developing these brittle classes by using Generic programming.

"Generic programming is a style of computer programming in which algorithms are written in terms of to-be-specified-later types that are then instantiated when needed for specific types provided as parameters. This permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication."

I know this description can be a bit confusing at first. Let's learn about Generic programming by example. And as an example, let's take a look at possible implementations of the Command pattern.

"In object-oriented programming, the Command pattern is a design pattern in which objects are used to represent actions. A command object encapsulates an action and its parameters."

In these examples, we are going to look at a home automation system. Specifically, let's look at the code it takes to automate lights using the Command pattern. In order to do this, we of course need a Light that we are actually automating (automation code removed for readability purposes):

    class Light
    {
        public void TurnOn()
        {
            Console.WriteLine("Light Turned On");
        }

        public void TurnOff()
        {
            Console.WriteLine("Light Turned Off");
        }
    }

So, let's dive right in.

In the "typical" way of implementing the Command pattern, you have an abstract base class Command that your detailed commands inherit from, overriding a virtual method to actually execute the command (this could also be an interface ICommand as well, instead of an abstract base class).

    abstract class Command
    {
        public abstract void Execute();
    }

    class LightOnCommand : Command
    {
        private Light _light;

        public LightOnCommand(Light theLight)
        {
            _light = theLight;
        }

        public override void Execute()
        {
            _light.TurnOn();
        }
    }

    class LightOffCommand : Command
    {
        private Light _light;

        public LightOffCommand(Light theLight)
        {
            _light = theLight;
        }

        public override void Execute()
        {
            _light.TurnOff();
        }
    }

Let's take another look at that definition of the Command pattern: "the Command pattern is a design pattern in which objects are used to represent actions". In our case, each action (turning a light on, turning a light off) is represented by an object (LightOnCommand, LightOffCommand).

Now to use these classes, all we need to do is instantiate new instances of our two commands (LightOnCommand and LightOffCommand) and execute them. Pretty simple. And because of leveraging a common base class, we can use polymorphism to reuse this code even more (like having a list of commands that we can execute in a script or elsewhere).

            var livingRoomLight = new Light();
            var lightOn = new LightOnCommand(livingRoomLight);
            var lightOff = new LightOffCommand(livingRoomLight);

            lightOn.Execute();
            lightOff.Execute();

This classic approach doesn't come without it's drawbacks though. First of all, for every home device that we wish to turn on and off, we would be introducing two new classes into our code. While this may not be that bad for a couple of devices, it can quickly get out of hand when the number of devices start to grow.

Also, the "common behavior" we get out of our base class isn't very flexible. If we change our Execute() method in our base class from abstract to virtual in order to introduce common behavior, how do you guarantee that the base method is called when it should be? In a derived class, should you call the base method before your own execution code, or after? What if I want common code in my base class that will be executed both before and after the behavior in my derived classes? While .NET has ways of achieving this, it quickly becomes complex and just introduces more code that obfuscates what the true purpose of our code is.

Not as flexible as we first though, eh?

So what has .NET done lately to make this job easier? Well, with the introduction of Generics and Lambdas, we can do a form of Generic programming that helps us write "one Command class to rule them all." By writing more generic code, you get down to the essence of what your code needs to do, and allows you to write code that is more flexible and not as dependant on other classes as before (since we are not directly leveraging inheritance like we were before).

What might a generic solution to the Command pattern look like?

    class GenericCommand<TReceiver>
    {
        private TReceiver _receiver;
        private Action<TReceiver> _commandToExecute;

        public GenericCommand(TReceiver theReceiver, Action<TReceiver> theCommandToExecute)
        {
            _receiver = theReceiver;
            _commandToExecute = theCommandToExecute;
        }

        public void Execute()
        {
            _commandToExecute(_receiver);
        }
    }

Let's look back at that description for Generic programming: "This permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication." That's all we are doing here. We have written a common type in which only the types on which it operates differs when used.

When we use this generic type is when we actually dictate what type it operates upon. In the case of our home automation system, we are going to create an instance of our GenericCommand that operates on a Light fixture. Then, we will specify what action needs to be taken when the command is execute by leveraging lambda expressions:

            var livingRoomLight = new Light();

            var genericLightOn = new GenericCommand<Light>(livingRoomLight, theLight => theLight.TurnOn());
            var genericLightOff = new GenericCommand<Light>(livingRoomLight, theLight => theLight.TurnOff());

Pretty simple, for our light on command we turn the light on, and for our light off command we turn the light off. And in both cases, we are using the exact same common type (GenericCommand<TReceiver>) when declaring our command.

Now let's say we want to start automating our Garage as well. The class we might be given to do this automating might look like the following (with all the automation code removed again):

    class Garage
    {
        public void TurnLightOn()
        {
            Console.WriteLine("Garage Light Turned On");
        }

        public void TurnLightOff()
        {
            Console.WriteLine("Garage Light Turned Off");
        }

        public void OpenDoor()
        {
            Console.WriteLine("Garage Door Opened");
        }

        public void CloseDoor()
        {
            Console.WriteLine("Garage Door Closed");
        }
    }

Using the prior inheritance-based way, we would need to introduce two more command classes for this new Garage automation. And as stated before, when having to introduce two more command classes for every new piece of home automation, we quickly have a blow-up in the number of classes we have to maintain. However, using the generic programming-based approach that leverages Generics and Lambdas, we actually don't have to introduce any new classes as our meta-class GenericCommand<T> can handle those situations already. 

Here's how we might consume the new Garage with our GenericCommand<T>:

            var genericOpenGarage = new GenericCommand<Garage>(myGarage, theGarage => 
            { 
                theGarage.TurnLightOn(); 
                theGarage.OpenDoor(); 
            });
            var genericCloseGarage = new GenericCommand<Garage>(myGarage, theGarage => 
            { 
                theGarage.CloseDoor(); 
                theGarage.TurnLightOff(); 
            });

            genericOpenGarage.Execute();
            genericCloseGarage.Execute();

Very simple. Once again, we are using the exact same type (GenericCommand<TReceiver>) to implement the automation commands for our Garage as we were using with our Light. So, the same common type that, when used, only differs by the types it operates on. Which is exactly the definition of Generic programming we discussed before. It's not as scary as it sounds!

Now if we need to introduce logging or any other common functionality into our command class, we can introduce it in one place and have it applied to all our consuming code. Unlike the polymorphic approach outlined above, we now have the flexibility in this approach to execute any common code in relation to our command, whenever we want, however we want. It doesn't matter if that common code is before, or after, or before and after.

If you are still reading this (in which case I thank you), you should realize that you are already familiar with a pattern used to achieve parallelism in the new Parallel Extensions to the .NET Framework CTP that has been released. The programming model used when dealing with Tasks and Futures are essentially the exact same thing as the generic command pattern used above. So if you grok the GenericCommand above, you'll settle right into using Tasks and Futures within the Parallel Extensions.

In closing, by using a Generic programming approach when implementing our Command pattern, we now have just the base functionality that is absolutely needed to implement our commands. And, we avoid the explosion in the number of classes and dependencies that taking an inheritance-based approach would introduce, all made possible by the introduction of Generics (in 2.0) and Lambdas (in 3.5) in .NET.

I hope you enjoyed this walk down the "applying generics and lambdas" road. Until next time, Happy Coding!

Updated:

Leave a Comment