Is Your Code SOLID: The Liskov Substitution Principal

If you think of standardization as the best that you know today, but which is to be improved tomorrow; you get somewhere. ~ Henry Ford

I honestly think the lowly USB was one of the best inventions for Personal Computers. This one adapter allows you to connect your PC to your keyboard, mouse, cell phone, external hard drive, graphics tablet, MP3 Player or a soft drink chiller. So long as the device follows the appropriate USB standard, communication between the device and PC should simply work, no matter what device you use.

The Liskov Substitution Principal (named for Barbara Liskov, who first formalized it in 1987) works the same way – some objects define “plug types” or “protocols” (abstractions, interfaces and contracts) while others implement plugs that fit those plug types/protocols to do stuff. Clients implement sockets that fit those plugs, calling on those implementations to do their thing. So long as the implementation abides by the abstraction/interface/contract, the system will work, and the client really doesn’t need to care how.
For example, consider a simplified drawing API:

void Draw(IEnumerable<IShape> shapes, IGraphics destination)
{
    destination.Pen = new Pen( …);
    foreach( IShape s in shapes)
    {
       s.DrawOn(destination);
    }
}

So long as a type implements IGraphics, it can be used as the “destination” parameter. The Draw method doesn’t care if the implementing type is a window, a printer, or a .PNG file writer.

Of course, things aren’t just that simple.

The protocol isn’t just the “shape” of the plug (or name and parameters of the method). For example, a USB device cannot draw more power than provided over the USB interface, and the USB device cannot hope to get more data across the interface than defined by the standard. In the same vein, an LSP compliant interface cannot demand or expect more of the client than the original interface describes.
Consider an API that takes an IEnumerable:

using System;
using System.Collections.Generic;

public interface IFoo
{
    /// <summary>
    /// Take an enumeration of strings, and Frobbles
    /// </summary>
    /// <param name="items"></param>
    void Frobble(IEnumerable<string> items);
}

The following represents an implementation that violates the LSP

public class FooClass : IFoo
{
    /// <summary>
    /// Take an enumeration of strings, and Frobbles
    /// </summary>
    /// <param name="items"></param>
    public void Frobble(IEnumerable<string> items)
    {
        List<string> l = items as List<string>;
        if (l == null)
        {
            throw new ArgumentException("Items is invalid type");
        }
        // ...
    }
}

The reason FooClass violates LSP is it cannot be used in all places an IFoo says it could be used. IFoo.Frobble’s use of IEnumerable<string> says it is passed a read only enumerable (iterable) entity (i.e., you cannot add elements to or remove elements from it). FooClass, for some reason, wants List behaviors as well (adding, removing, indexing, etc). This additional requirement means FooClass can’t be used in all those situations that IEnumerables were successfully being passed before.

The way you fix this depends on a couple of things:

  1. Is IFoo NOT a published interface?
  2. you want to expose a List requirement on all clients (e.g, Add/Remove items)

If both 1 and 2 are true, you might consider changing the IFoo interface so that Frobble takes a List (or better, an IList — we’ll see why in a post on the Dependency Inversion Principle).

If either 1 or 2 is false, then FooClass.Frobble cannot be changed, and must respond without an error when it receives any type derived from IEnumerable. Furthermore, it cannot add or remove items from the enumeration.
In other words, interfaces (and abstractions) make promises you expect others to keep, and informs clients what sort of service they can expect.

Every interface or abstraction is a promise. It’s a promise you will have to live up to. One that others will have to live up to. So, be careful about what you promise, and be sure you understand what others are promising for you.

Is Your Code SOLID? OCP and Fighting the Cost Of Change

It is better to keep your mouth closed and let people think you are a fool than to open it and remove all doubt. – Depending on who you ask, Mark Twain or Abraham Lincoln

Is it a good use of time and money to periodically pull reviewed, tested and unchanged code out of source control so the team can re-review and re-test it?

Probably not. If the code hasn’t changed, we’ll quickly experience diminishing returns.

Then again, all software changes, so it’s a moot point.

Or is it?

What if we could structure our designs in such a way that we can add features, without changing existing code?

Enter the Open/Closed Principle (OCP): software should be open to extension, but closed for modification.

To understand how this principle works, lets start with code that violates this principle. We usually find this around dynamic casts, “switch/case” and chains of if/else if/else statements.

Consider the switch/case statement. Every time we add a new case, we must modify code in the switch. Same with if/else: every time we need a new else, the original source must be modified. If behavior in a function is dependent on a successful dynamic cast of a parameter (using the as or is keywords), you’ll have to add new code to handle any new types. All this new code called in all these places must be tested in all its variations.

Here’s an example:

public class Foo
{
      private object myStuff;

      public void Bar(OutputDevice outputDevice)
      {
         switch(outputDevice)
         {
            case OutputDevice.Window:
                 DrawOnWindow(myStuff);
                 break;

            case OutputDevice.Printer:
                 PrintStuff(myStuff);
                 break;

            default:
                 throw new NotImplementedException();
         }
     }
 }

Assume we get a new feature request: users want to save their stuff to a file. With the code as it’s currently written, we must modify the original shipping code and add an OutputDevice.File case (and quite possibly a new enumeration value – potientially breaking any other code using the original enumeration). Additionally, we need to test Foo.Bar to make sure our changes didn’t break the old code, and because Foo.Bar changed, we need to test all the code that calls Foo.Bar, and we need to test the new rendering codes to make sure that works.

Way, way, too much work for a “simple” change.

An OCP solution for this will turn all these conditionals into abstractions:

public interface IRenderStuff
{
    void Render(object data);
}
public class RenderableWindow : IRenderStuff
{
    public void Render(object data)
    {
        // ...
    }
}
public class PrintRenderer: IRenderStuff
{
    public Printer Printer { get; set; }
    public void Render(object data)
    {
        Printer.Setup();
        Printer.Print(data.ToString());
        Printer.Close();
    }
}
public class PrintRenderer : IRenderStuff
{
    public void Render(object data)
    {
        using (System.IO.TextWriter writer = CreateFileWriter())
        {
            writer.Write(data.ToString());
        }
    }
}

Now, Foo.Bar can look like:

public void Bar(IRenderStuff renderer)
{
     renderer.Render(myStuff);
}

Foo.Bar is now open for extension, and closed for modification. This one adjustment insures Foo.Bar’s doesn’t have to change when we add a new renderer. If it doesn’t change, it won’t have a bug injected into it (if the only change is a new rendering destination), and no longer needs to go through testing of all the rendering cases.

In fact, Foo.Bar need only be tested against a one stub implementation of IRenderStuff to verify it is holding up its end of the contract. Of course, each IRenderStuff implementation needs testing, but we had to do that anyway.

Here’s the payoff: this change reduced our test overhead (read: ways this code could break) from

NumberOfRenderedObjects
×
NumberOfRenderingMethods
×
NumberOfBodiesOfCodeUsingTheOutputDeviceSwitch

to

NumberOfObjectsRendered + NumberOfRenderingMethods

Our cost of change just went from an exponential increase to a linear one.

Now, consider class member variables. Whenever code reaches into a class to access a member directly, we cannot change how that class is implemented without affecting (and potentially breaking) all of that class’ users. Every bit of code referencing a member variable is open (or “exposed”) to modification. Any change to our implementation means a change to all of its consumers.

To limit this effect, we encapsulate the variables behind methods and properties, closing those relationships to modifications in the code.

It’s not much of a leap to realize any member variable access violates this principle. So we minimize the area affected by making member variables private (not protected, that’s a cheap cop-out). By doing this, only member functions of the defining class are exposed to changes in those variables. All other consumers are closed to those changes.

The same thing applies to global variables. In the same way non private data members violate the OCP, so do global variables. Every module that depends on a global variable can never be closed to modification with respect to that global.

Powered by WordPress with GimpStyle Theme design by Horacio Bella.
Entries and comments feeds. Valid XHTML and CSS.