ENTERPRISE

Microsoft .NET : Design Principles and Patterns - Object-Oriented Design (part 2) - Advanced Principles

1/28/2013 6:25:08 PM

2. Advanced Principles

You cannot go to a potential customer and sing the praises of your software by mentioning that it is modular, well designed, and easy to read and maintain. These are internal characteristics of the software that do not affect the user in any way. More likely, you’ll say that your software is correct, bug free, fast, easy to use, and perhaps extensible. However, you can hardly write correct, bug-free, easy-to-use, and extensible software without paying a lot of attention to the internal design.

Basic principles such as low coupling, high cohesion (along with the single responsibility principle), separation of concerns, plus the first two principles of OOD give us enough guidance about how to design a software application. As you might have noticed, all these principles are rather old (but certainly not outdated), as they were devised and formulated at least 15 years ago.

In more recent years, some of these principles have been further refined and enhanced to address more specific aspects of the design. We like to list three more advanced design principles that, if properly applied, will certainly make your code easier to read, test, extend, and maintain.

The Open/Closed Principle

We owe the Open/Closed Principle (OCP) to Bertrand Meyer. The principle addresses the need of creating software entities (whether classes, modules, or functions) that can happily survive changes. In the current version of the fictional product "This World," the continuous changes to software requirements are a well-known bug. Unfortunately, although the team is working to eliminate the bug in the next release, we still have to face reality and deal with frequent changes of requirements the best we can.

Essentially, we need to have a mechanism that allows us to enter changes where required without breaking existing code that works. The OCP addresses exactly this issue by saying the following:

A module should be open for extension but closed for modification.

Applied to OOD, the principle recommends that we never edit the source code of a class that works in order to implement a change. In other words, each class should be conceived to be stable and immutable and never face change—the class is closed for modification.

How can we enter changes, then?

Every time a change is required, you enhance the behavior of the class by adding new code and never touching the old code that works. In practical terms, this means either using composition or perhaps safe-and-clean class inheritance. Note that OCP just reinforces the point that we made earlier about the second principle of OOD: if you use class inheritance, you add only new code and do not modify any part of the inherited context.

Today, the most common way to comply with the OCP is by implementing a fixed interface in any classes that we figure are subject to changes. Callers will then work against the interface as in the first principle of OOD. The interface is then closed for modification. But you can make callers interact with any class that, at a minimum, implements that interface. So the overall model is open for extension, but it still provides a fixed interface to dependent objects.

Liskov’s Substitution Principle

When a new class is derived from an existing one, the derived class can be used in any place where the parent class is accepted. This is polymorphism, isn’t it? Well, the Liskov Substitution Principle (LSP) restates that this is the way you should design your code. The principle says the following:

Subclasses should be substitutable for their base classes.

Apparently, you get this free of charge from just using an object-oriented language. If you think so, have a look at the next example:

public class ProgrammerToy
{
    private int _state = 0;

    public virtual void SetState(int state)
    {
        _state = state;
    }

    public int GetState()
    {
       return _state;
    }
}

The class ProgrammerToy just acts as a wrapper for an integer value that callers can read and write through a pair of public methods. Here’s a typical code snippet that shows how to use it:

static void DoSomeWork(ProgrammerToy toy)
{
    int magicNumber = 5;
    toy.SetState(magicNumber);
    Console.WriteLine(toy.GetState());
    Console.ReadLine();
}

The caller receives an instance of the ProgrammerToy class, does some work with it, and then displays any results. So far, so good. Let’s now consider a derived class:

public class CustomProgrammerToy : ProgrammerToy
{
    public override void SetState(int state)
    {
        // It inherits the context of the parent but lacks the tools
        // to fully access it. In particular, it has no way to access
        // the private member _state.
        // As a result, this class MAY NOT be able to
        // honor the contract of its parent class. Whether or not, mostly
        // depends on your intentions and expected goals for the overridden
        // SetState method. In any case, you CAN'T access directly the private member
        // _state from within this override of SetState.

        // (In .NET, you can use reflection to access a private member,
        // but that's a sort of a trick.)
        .
        .
        .
     }
}

From a syntax point of view, ProgrammerToy and CustomProgrammerToy are just the same and method DoSomeWork will accept both and successfully compile.

From a behavior point of view, though, they are quite different. In fact, when CustomProgrammerToy is used, the output is 0 instead of 5. This is because of the override made on the SetState method.

This is purely an example, but it calls your attention to Liskov’s Principle. It doesn’t go without saying that derived classes (subclasses) can safely replace their base classes. You have to ensure that. How?

You should handle keywords such as sealed and virtual with extreme care. Virtual (overridable) methods, for example, should never gain access to private members. Access to private members can’t be replicated by overrides, which makes base and derived classes not semantically equivalent from the perspective of a caller. You should plan ahead of time which members are private and which are protected. Members consumed by virtual methods must be protected, not private.

Generally, virtual methods of a derived class should work out of the same preconditions of corresponding parent methods. They also must guarantee at least the same postconditions.

Classes that fail to comply with LSP don’t just break polymorphism but also induce violations of OCP on callers.

Note

OCP and LSP are closely related. Any function using a class that violates Liskov’s Principle violates the Open/Close Principle. Let’s reference the preceding example again. The method DoSomeWork uses a hierarchy of classes (ProgrammerToy and CustomProgrammerToy) that violate LSP. This means that to work properly DoSomeWork must be aware of which type it really receives. Subsequently, it has to be modified each time a new class is derived from ProgrammerToy. In other words, the method DoSomeWork is not closed for modification.

The Dependency Inversion Principle

When you create the code for a class, you represent a behavior through a set of methods. Each method is expected to perform a number of actions. As you specify these actions, you proceed in a top-down way, going from high-level abstractions down the stack to more and more precise and specific functions.

As an illustration, imagine a class, perhaps encapsulated in a service, that is expected to return stock quotes as a chunk of HTML markup:

public class FinanceInfoService
{
  public string GetQuotesAsHtml(string symbols)
  {
    // Get the Finder component
    IFinder finder = ResolveFinder();
    if (finder == null)
      throw new NullReferenceException("Invalid finder.");

    // Grab raw data
    StockInfo[] stocks = finder.FindQuoteInfo(symbols);

    // Get the Renderer component
    IRenderer renderer = ResolveRenderer();
    if (renderer == null)
       throw new NullReferenceException("Invalid renderer.");

    // Render raw data out to HTML
    return renderer.RenderQuoteInfo(stocks);
  }

  .
  .
  .
}

The method GetQuotesAsHtml is expected to first grab raw data and then massage it into an HTML string. You recognize two functionalities in the method: the finder and the renderer. In a top-down approach, you are interested in recognizing these functionalities, but you don’t need to specify details for these components in the first place. All that you need to do is hide details behind a stable interface.

The method GetQuotesAsHtml works regardless of the implementation of the finder and renderer components and is not dependent on them. (See Figure 2.) On the other hand, your purpose is to reuse the high-level module, not low-level components.

Lower layers are represented by an interface

Figure 2. Lower layers are represented by an interface

When you get to this, you’re in full compliance with the Dependency Inversion Principle (DIP), which states the following:

High-level modules should not depend upon low-level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

The inversion in the name of the principle refers to the fact that you proceed in a top-down manner during the implementation and focus on the work flow in high-level modules rather than focusing on the implementation of lower level modules. At this point, lower level modules can be injected directly into the high-level module. Here’s an alternative implementation for a DIP-based module:

public class FinanceInfoService
{
  // Inject dependencies through the constructor. References to such external components
  // are resolved outside this module, for example by using an inversion-of-control
  // framework (more later).
  IFinder _finder = null;
  IRenderer _renderer = null;

  public FinanceInfoService(IFinder finder, IRenderer renderer)
  {
    _finder = finder;
    _renderer = renderer;
  }

  public string GetQuotesAsHtml(string symbols)
  {
    // Get the Finder component
    if (_finder == null)
      throw new NullReferenceException("Invalid finder.");
    // Grab raw data
    StockInfo[] stocks = _finder.FindQuoteInfo(symbols);

    // Get the Renderer component
    if (_renderer == null)
      throw new NullReferenceException("Invalid renderer.");

    // Render raw data out to HTML
    return _renderer.RenderQuoteInfo(stocks);
  }

  .
  .
  .
}

In this case, the lower level modules are injected through the constructor of the DIP-based class.

Other  
 
Top 10
Free Mobile And Desktop Apps For Accessing Restricted Websites
MASERATI QUATTROPORTE; DIESEL : Lure of Italian limos
TOYOTA CAMRY 2; 2.5 : Camry now more comely
KIA SORENTO 2.2CRDi : Fuel-sipping slugger
How To Setup, Password Protect & Encrypt Wireless Internet Connection
Emulate And Run iPad Apps On Windows, Mac OS X & Linux With iPadian
Backup & Restore Game Progress From Any Game With SaveGameProgress
Generate A Facebook Timeline Cover Using A Free App
New App for Women ‘Remix’ Offers Fashion Advice & Style Tips
SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th
Popular Tags
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone