(Originally posted at http://mcory.wordpress.com on 9/25/07)
When I interviewed for my current job, one of the interview questions that still stands out in my mind was “Name some Principles of Object-Oriented Design.” I, being the ever-knowledgeable developer I am, gave them what I’ve since seen to be a variation on the standard answer they’ve always recieved: inheritance, polymorphism, encapsulation, etc. The “principles” of OOD that I’ve picked up on my journeys through the industry, more “concepts” than principles, though I guess that’s splitting hairs in a way. I thought I answered it well at the time; after they hired me, I quickly learned I was way off base.
If, like me, your first response would’ve been the same and you would’ve answered with the old buzzwords that oo’ers have touted as their reason for shunning their old procedural ways, I hope to enlighten you some what. This starts what I hope to be a series of articles on some of the principles as I’ve come to understand them — the ones I feel comfortable enough to explain, at least.
Note: I know myself, and I know I will probably loose interest in this little series long before it’s done. If that’s the case, I offer http://www.ootips.org as a great resource for finding more about these. Wikipedia has rather decent coverage of them as well; just search for “object oriented design principles” (or individual principles by name.)
Liskov Substitution Principle
Wikipedia Article
This is probably one of the easiest to explain, and, at least when starting a project from scratch, is one of the easiest to follow. Here’s the gist of it: an instance of a class should be able to be used where ever an instance of it’s parent class is used without modification and without breaking the code that uses it. I find it’s normally easier to see how not to do something in order to learn how to do something right, so here’s an example of a violation of LSP:
[sourcecode language="csharp"]
public abstract class Car
{
public abstract void Drive();
}
public class AutomaticTransmissionCar : Car
{
public override void Drive() { /* blah */ }
}
public class ManualTransmissionCar : Car
{
public void ShiftGears(char gear) { /* blah */ }
public override void Drive() { /* blah */ }
}
public class CarProgram
{
private static Car CreateCarInstance()
{
// something that gets us a car...
}
[STAThread]
public static void Main(string[] args)
{
Car theCar = CreateCarInstance();
if (theCar is ManualTransmissionCar)
{
(theCar as ManualTransmissionCar).ShiftGears('1');
}
theCar.Drive();
}
}[/sourcecode]
This probably isn’t quite the best example of an LSP violation, as a lot of times they’re much more subtle, but it should suffice for the purpose of this little article. (Of course, having different types of a “Car” based on different types of “Transmissions” — automatic vs. manual — should probably raise some OOD warning bells too, but we’ll ignore that for now).
By checking to see if the car is of a particular type — ManualTransmissionCar, in this case — we’re violating LSP (and another principle as well, the “Open/Closed Principle”, but we’ll cover that one some other time). Our program is set up to act differently for a specific type of car, and isn’t relying strictly on what a generic “Car” object could do. What happens if we want to add a different car type? Perhaps some engineer somewhere comes up with a new transmission; then what? We’ll most likely need to modify the Main method to add support for it, in addition well as where ever we’re loading the car from (CreateCarInstance). It’s a lot nicer to just add a new class and change the loading method, wouldn’t you agree?
It’s not always easy to break away from an LSP violation, at least not without reworking your entire project. Sometimes it’s a hell of a lot more trouble than it’s worth (in the short term, at least). In this particular example though, we can change things to work a bit easier:
[sourcecode language="csharp"]
public abstract class Car
{
public abstract void Drive();
public virtual void ShiftGears(char gear) { /* do nothing -- leave for derived classes. */ }
}
public class AutomaticTransmissionCar : Car
{
public override void Drive() { /* blah */ }
}
public class ManualTransmissionCar : Car
{
public override void ShiftGears(char gear) { /* blah */ }
public override void Drive() { /* blah */ }
}
public class CarProgram
{
private static Car CreateCarInstance()
{
// something that gets us a car...
}
[STAThread]
public static void Main(string[] args)
{
Car theCar = CreateCarInstance();
theCar.ShiftGears('1');
theCar.Drive();
}
}[/sourcecode]
See, it’s a little cleaner, and now we don’t need to change anything in Main to support a new Car class (well, as long as we’re only using ShiftGears and Drive).
I’m running late — check out the ootips site, it’s got some great stuff on it.
Posted by: mcory
Posts that may or may not be similar to the above:
- LSP Take 2: A (Hopefully) Better Example
- Visitor Pattern: One Fix for LSP Violations
- LSP Revisited: Quick Violation Check
- Generic Collection Builder w/ Delegates
- Ground-Up ASP.Net Development I: At the…Well…Ground.
Categories: