mentis vulgaris
simple thoughts | jason smith
Is Your Code SOLID: The Liskov Substitution Principal
Posted by Jason Smith - 20/12/09 at 05:12:57 pmIf 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:
- Is IFoo NOT a published interface?
- 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
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
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.
Powered by WordPress with GimpStyle Theme design by Horacio Bella.
Entries and comments feeds.
Valid XHTML and CSS.