Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / patterns

An Introduction to Design Patterns – Part 3

4.67/5 (2 votes)
22 May 2011CPOL3 min read 10.4K  
Using delegation, you can change the behaviors of objects at run-time. A lots of Design Patterns make use of this technique.

Prerequisite

Before reading this article, you should read the first and second part of this series.

Delegation

When a client object X requests some service from an object Y, then Y may decide to pass this request to another object Z instead of handling the request by itself. When this happens, we say that receiver object Y has delegated its operation to its delegate Z. This process is known as delegation and it’s a way of making object composition as powerful for reuse as inheritance.

Let us now consider an example from this famous book on Design Patterns. Suppose that we want to create a class which will generate a window. The code for this class may look something like:

C#
public class Window
{
    // Various member variables

    public void drawWindow()
    {
        // Code to draw the window
    }

    // various other methods
}

This approach is a naive one. A better approach would be to create a Rectangle class which will contain the logic to generate a simple rectangle and then inherit this Window class from this Rectangle and thereby include its functionality.

However, there is another approach. Instead of inheriting this Window class from Rectangle, we may delegate the responsibility from this class to another class called DrawRectangleShape, like this:

C#
// We are using an interface so that all window drawing classes have a
// common Object Interface (Remember the OOP principle 1 from first part? :) )
public interface IDrawWindow  
{
    public void drawWindow(Window X);
}

public class DrawRectangleShape implements IDrawWindow
{
    // various data and methods

    public void drawWindow(Window X)
    {
        // logic to generate a rectangular-shaped window X
    }
}

public class Window
{
    IDrawWindow windowDrawer;
    // Various member variables

    public Window()
    {
        // It is OK to initialize this windowDrawer reference to a particular
        // object instance, but you should also provide setter method so that
        // this reference can be changed dynamically.
        this.windowDrawer = new DrawRectangleShape();
    }

    public void setWindowDrawer(IDrawWindow windowDrawer)
    {
        this.windowDrawer = windowDrawer;
    }

    public void drawWindow()
    {
        // Pass self-reference to the delegate
        this.windowDrawer.drawWindow(this);
    }

    public void redrawWindow()
    {
        // destroys the current window, then regenerates it
    }

    // various other methods
}

Now if you look at the code, you’ll see that the Window object is passing itself to the delegate so that the delegate can properly generate a rectangular window. This is always the case when the delegate requires a reference to the receiver.

Now why should we use delegation?

The main advantage of delegation is that it makes it easy to compose behaviors at run-time. Consider another implementation of the IDrawWindow interface:

C#
public class DrawCircularShape implements IDrawWindow
{
    // various data and methods

    public void drawWindow(Window X)
    {
        // logic to generate a circular-shaped window X
    }
}

Now consider the following piece of code:

C#
Window newWindow = new Window();
newWindow.drawWindow();

IDrawWindow newWindowDrawer = new DrawCircularShape();
newWindow.setWindowDrawer(newWindowDrawer);
newWindow.redrawWindow();

The above piece of code first generates a rectangular-shaped window. After that, it replaces its default rectangular shape drawer with a circular shape drawer and then redraws itself. As a result, the window will become circular from rectangular at run-time!!!

So using delegation, you can change the behaviors of objects at run-time in this way. A lots of Design Patterns make use of this technique.

Unfortunately, delegation has some disadvantages too. Dynamic, highly parameterized software is typically harder to understand than more static software. If you use delegation, then you will have code that changes behaviors at run-time. Understanding these varying behaviors may prove to be difficult. There are also some run-time inefficiencies since you have to create new objects and transfer responsibilities to them, but this is negligible because most of the modern processors are pretty fast and because of the flexibility it brings to a software design.

So when using delegation, always make sure that it simplifies the design more than it complicates it. Only then will you be able to leverage the true power of delegation.

Conclusion

At this point, you should have a pretty good understanding of what Design Patterns are and what they accomplish. You will find various other definitions on various books and on the web. I think you will now understand all of them. :). Read them on your own. For the time being, I am going to include the definition of Design Patterns given by Christopher Alexander who first introduced the idea:

The elements of this language are entities called patterns. Each pattern describes a problem that occurs over and over again in our environment, and then describes the core of the solution to that problem in such a way that you can use this solution a million times over, without ever doing it the same way twice.

I hope someone finds this series useful :).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)