ENTERPRISE

Microsoft .NET : Design Principles and Patterns - Object-Oriented Design (part 1) - Basic OOD Principles

1/28/2013 6:24:01 PM

Before object orientation (OO), any program resulted from the interaction of modules and routines. Programming was procedural, meaning that there was a main stream of code determining the various steps to be accomplished.

OO is a milestone in software design.

OO lets you envision a program as the result of interacting objects, each of which holds its own data and behavior. How would you design a graph of objects to represent your system? Which principles should inspire this design?

We can recognize a set of core principles for object-oriented design (OOD) and a set of more advanced and specific principles that descend from, and further specialize, the core principles.

1. Basic OOD Principles

To find a broadly accepted definition of OOD, we need to look at the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) and their landmark book Design Patterns: Elements of Reusable Object-Oriented Software, (Addison-Wesley, 1994).

The entire gist of OOD is contained in this sentence:

You must find pertinent objects, factor them into classes at the right granularity, define class interfaces and inheritance hierarchies, and establish key relationships among them.

In GoF, we also find another excerpt that is particularly significant:

Your design should be specific to the problem at hand but also general enough to address future problems and requirements.

The basics of OOD can be summarized in three points: find pertinent objects, favor low coupling, and favor code reuse.

Find Pertinent Objects First

The first key step in OOD is creating a crisp and flexible abstraction of the problem’s domain. To successfully do so, you should think about things instead of processes. You should focus on the whats instead of the hows. You should stop thinking about algorithms to focus mostly on interacting entities. Interacting entities are your pertinent objects.

Where do you find them?

Requirements offer the raw material that must be worked out and shaped into a hierarchy of pertinent objects. The descriptions of the use cases you receive from the team of analysts provide the foundation for the design of classes. Here’s a sample use case you might get from an analyst:

To view all orders placed by a customer, the user indicates the customer ID. The program displays an error message if the customer does not exist. If the customer exists, the program displays name, address, date of birth, and all outstanding orders. For each order, the program gets ID, date, and all order items.

A common practice for finding pertinent objects is tagging all nouns and verbs in the various use cases. Nouns originate classes or properties, whereas verbs indicate methods on classes. Our sample use case suggests the definition of classes such as User, Customer, Order, and OrderItem. The class Customer will have properties such as Name, Address, and DateOfBirth. Methods on the class Customer might be LoadOrderItems, GetCustomerByID, and LoadOrders.

Note that finding pertinent objects is only the first step. As recommended in the statement that many consider to be the emblem of OOD, you then have to factor pertinent objects into classes and determine the right level of granularity and assign responsibilities.

In doing so, two principles of OOD apply, and they are listed in the introduction of GoF.

Favor Low Coupling

In an OO design, objects need to interact and communicate. For this reason, each object exposes its own public interface for others to call. So suppose you have a logger object with a method Log that tracks any code activity to, say, a database. And suppose also that another object at some point needs to log something. Simply enough, the caller creates an instance of the logger and proceeds. Overall, it’s easy and effective. Here’s some code to illustrate the point:

class MyComponent
{
  void DoSomeWork()
  {
    // Get an instance of the logger
    Logger logger = new Logger();

    // Get data to log
    string data = GetData();

    // Log
     logger.Log(data);
  }
}

The class MyComponent is tightly coupled to the class Logger and its implementation. The class MyComponent is broken if Logger is broken and, more importantly, you can’t use another type of logger.

You get a real design benefit if you can separate the interface from the implementation.

What kind of functionality do you really need from such a logger component? You essentially need the ability to log; where and how is an implementation detail. So you might want to define an ILogger interface, as shown next, and extract it from the Logger class:

interface ILogger
{
    void Log(string data);
}

class Logger : ILogger
{
    .
    .
    .
}

At this point, you use an intermediate factory object to return the logger to be used within the component:

class MyComponent
{
  void DoSomeWork()
  {
    // Get an instance of the logger
    ILogger logger = Helpers.GetLogger();
    // Get data to log
    string data = GetData();

    // Log
    logger.Log(data);
  }
}

class Helpers
{
  public static ILogger GetLogger()
  {
    // Here, use any sophisticated logic you like
    // to determine the right logger to instantiate.

    ILogger logger = null;
    if (UseDatabaseLogger)
    {
        logger = new DatabaseLogger();
    }
    else
    {
        logger = new FileLogger();
    }
    return logger;
  }
}
class FileLogger : ILogger
{
    .
    .
    .
}

class DatabaseLogger : ILogger
{
    .
    .
    .
}

The factory code gets you an instance of the logger for the component to use. The factory returns an object that implements the ILogger interface, and the component consumes any object that implements the contracted interface.

The dependency between the component and the logger is now based on an interface rather than an implementation.

If you base class dependencies on interfaces, you minimize coupling between classes to the smallest possible set of functions—those defined in the interface. In doing so, you just applied the first principle of OOD as outlined in GoF:

Program to an interface, not an implementation.

This approach to design is highly recommended for using with the parts of your code that are most likely to undergo changes in their implementation.

Note

Should you use an interface? Or should you perhaps opt for an abstract base class? In object-oriented languages that do not support multiple inheritance—such as Java, C#, and Visual Basic .NET—an interface is always preferable because it leaves room for another base class of your choice. When you have multiple inheritance, it is mostly a matter of preference. You should consider using a base class in .NET languages in all cases where you need more than just an interface. If you need some hard-coded behavior along with an interface, a base class is the only option you have. ASP.NET providers, for example, are based on base classes and not on interfaces.

An interesting possibility beyond base classes and interfaces are mixins, but they are an OOP feature not supported by .NET languages. A mixin is a class that provides a certain functionality that other classes can inherit, but it is not meant to be a standalone class. Put another way, a mixin is like an interface where some of the members might contain a predefined implementation. Mixins are supported in some dynamic languages, including Python and Ruby. No .NET languages currently support mixins, but mixins can be simulated using ad hoc frameworks such as Castle. DynamicProxy. With this framework, you first define a class that contains all the methods you want to inject in an existing class—the mixin. Next, you use the framework to create a proxy for a given class that contains the injected methods. Castle.DynamicProxy uses Reflection.Emit internally to do the trick.

Real-World Example: IButtonControl in ASP.NET

In ASP.NET 1.x, there was no support for cross-page postbacks. Every time the user clicked a button, he could only post to the same page. Starting with ASP.NET 2.0, buttons (and only buttons) were given the ability to trigger the post of the current form to an external page.

To support this feature, the Page class needs to know whether the control that caused the postback is a button or not. How many types of buttons do you know? There’s the Button class, but also LinkButton and finally ImageButton. Up until ASP.NET 2.0, these classes had very little in common—just a few properties, but nothing that could be officially perceived as a contract or a formal link.

Having the Page class check against the three types before posting would have limited the extensibility of the framework: only those three types of control would have ever been able to make a cross-page post.

The ASP.NET team extracted the core behavior of a button to the IButtonControl interface and implemented that interface in all button classes. Next, they instructed the Page class to check the interface to verify the suitability of a posting control to make a cross-page post.

In this way, you can write custom controls that implement the interface and still add the ability to make your own cross-page posts.

Favor Code Reuse

Reusability is a fundamental aspect of the object-oriented paradigm and one of the keys to its success and wide adoption. You create a class one day, and you’re happy with that. Next, on another day, you inherit a new class, make some changes here and there, and come up with a slightly different version of the original class.

Is this what code reuse is all about? Well, there’s more to consider.

With class inheritance, the derived class doesn’t simply inherit the code of the parent class. It really inherits the context and, subsequently, it gains some visibility of the parent’s state. Is this a problem?

For one thing, a derived class that uses the context it inherits from the parent can be broken by future changes to the parent class.

In addition, when you inherit from a class, you enter into a polymorphic context, meaning that your derived class can be used in any scenarios where the parent is accepted. It’s not guaranteed, however, that the two classes can really be used interchangeably. What if the derived class includes changes that alter the parent’s context to the point of breaking the contract between the caller and its expected (base) class? (Providing the guarantee that parent and derived classes can be used interchangeably is the goal of Liskov’s principle, which we’ll discuss later.)

In GoF, the authors recognize two routes to reusability—white-box and black-box reusability. The former is based on class inheritance and lends itself to the objections we just mentioned. The latter is based on object composition.

Object composition entails creating a new type that holds an instance of the base type and typically references it through a private member:

public CompositeClass
{
  private MyClass theObject;

  public CompositeClass()
  {
    // You can use any lazy-loading policy you want for instantiation.
    // No lazy loading is being used here ...
    theObject = new MyClass();
  }

  public object DoWork()
  {
    object data = theObject.DoSomeWork();

    // Do some other work
    return Process(data);
  }
  private object Process(object data)
  {
    .
    .
    .
  }
}

In this case, you have a wrapper class that uses a type as a black box and does so through a well-defined contract. The wrapper class has no access to internal members and cannot change the behavior in any way—it uses the object as it is rather than changing it to do its will. External calls reach the wrapper class, and the wrapper class delegates the call internally to the held instance of the class it enhances. (See Figure 1.)

Object composition and delegation

Figure 1. Object composition and delegation

When you create such a wrapper object, you basically apply the second principle of OOD:

Favor object composition over class inheritance.

Does all this mean that classic class inheritance is entirely wrong and should be avoided like the plague? Using class inheritance is generally fine when all you do is add new functions to the base class or when you entirely unplug and replace an existing functionality. However, you should never lose track of the Liskov principle. (We’ll get to the details of the Liskov principle in a moment.)

In many cases, and especially in real-world scenarios, object composition is a safer practice that also simplifies maintenance and testing. With composition, changes to the composite object don’t affect the internal object. Likewise, changes to the internal object don’t affect the outermost container as long as there are no changes to the public interface.

By combining the two principles of OOD, you can refer to the original object through an interface, thus further limiting the dependency between composite and internal objects. Composition doesn’t provide polymorphism even if it will provide functionality. If polymorphism is key for you, you should opt for a white-box form of reusability. However, keep the Liskov principle clearly in mind.

Note

In addition to composition, another approach is frequently used to contrast class inheritance—aggregation. Both aggregation and composition refer to a has-a relationship between two classes, whereas inheritance implies an is-a relationship. The difference between composition and aggregation is that with composition you have a static link between the container and contained classes. If you dispose of the container, the contained classes are also disposed of. With aggregation, the link is weaker and the container is simply associated with an external class. As a result, when the container is disposed of, the child class blissfully survives.

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