Online Book Reader

Home Category

Programming Microsoft ASP.NET 4 - Dino Esposito [296]

By Root 5802 0
By “significant class” here, I mean most real-world classes or, better, all classes except those you write just for demo purposes!

If closure of a given class can’t realistically be complete, it should then be strategic. It is a precise architect’s responsibility to identify the most likely changes in the class body and close the class design against them. In other words, designing a class that can support all possible future extensions is a utopian pursuit. However, identifying just one specific abstraction or two and making the class work against them is, most of the time, an excellent compromise. Aiming for fully pluggable classes is over designing; even a partial application of OCP is beneficial and rewarding.

If you ignore OCP, at some point you might catch yourself downcasting to a specific subclass to compile the code and avoid run-time exceptions. If this happens, it’s a clear sign that something is wrong in the design.

Liskov’s Substitution Principle


Of the five SOLID principles, Liskov’s principle is probably the only one that should be taken literally, because it serves you a detailed list of dos and don’ts and it isn’t limit to being a generic guidance on design. The formulation of the principle couldn’t be simpler. To some extent, it also seems a bit obvious:

Subclasses should always be substitutable for their base classes.

When a new class is derived from an existing one, it should always be possible to use the derived class in any place where the parent class is accepted. Wait a moment! Isn’t this something you get out of the box with any object-oriented language? Well, not exactly!

What you really get from OOP is just the mere promise that derived classes can be used wherever their base class is accepted. However, OOP still lets you write hierarchies of classes where this basic requirement isn’t met; hence, the principle.

Substitution Principle Canonical Example


Suppose you have a Rectangle class and a method that works with that. The method just receives a parameter of type Rectangle and, of course, it takes advantage of the logical contract this class exposes. For example, the Rectangle class exposes a Width and Height pair of properties that can be independently set.

Suppose that after doing this, you then need to introduce a Square object. How would you do that? Logically speaking, you see the Square entity as a special case of the Rectangle entity. Therefore, the natural step is deriving Square from Rectangle and overriding Width and Height so that their values are always in sync.

If you do this, you potentially break the original code written against the contract of the Rectangle class. The violation of the principle here doesn’t necessarily result in a run-time exception or a compile error. Your code might still work just fine, despite the Liskov violation. However, your code is inherently fragile because there’s the possibility of introducing bugs during maintenance. The violation has to be considered in the mathematical sense—you can find a counterexample that shows you can’t use a Square where a Rectangle is expected. Here’s a code snippet that illustrates this point:

public class Rectangle

{

public virtual Int32 Width { get; set; }

public virtual Int32 Height { get; set; }

}

public class Square : Rectangle

{

public override Int32 Width

{

get {return base.Width; }

set {base.Width = value; base.Height = value; }

}

public override Int32 Height

{

get {return base.Height; }

set {base.Height = value; base.Width = value; }

}

}

Here’s some client code that consumes the Rectangle class:

public void Process(Rectangle rect)

{

rect.Width = 100;

rect.Height = 2* rect.Width;

Debug.Assert(rect.Height == 2*rect.Width);

}

This code works fine if a real Rectangle is passed, but it violates the assertion if a Square is passed. The easiest way to fix it—the workaround that increases viscosity—is simply the following:

public void Process(Rectangle rect)

{

if (rect is Rectangle)

{

rect.Width = 100;

rect.Height = 2* rect.Width;

Debug.Assert(rect.Height ==

Return Main Page Previous Page Next Page

®Online Book Reader