Introduction

A good class design should be such that there should be a loose coupling between the different classes involved and the design must be extensible. A loose coupling between classes means that the interdependency between them should be minimal. Extensibility is the ease with which the design can take changes in functionality.

In this article, I have taken a simple real life problem and tried to solve it using three different approaches:

A simple approach

Interface based approach

Delegate based approach

The Problem Statement

An employee is assigned a certain time consuming task. He is supposed to finish this task and notify his managers about the result.

A Simple Approach

This approach is a usual developer approach which aims to solve the problem at hand and not think about future enhancements. So as per the problem, we can identify two entities namely Manager and Employee. So let us have two classes Employee and Manager. Employee will have a method which we shall name as DoWork and Manager will have a method called Notify. The implementation is as given below:

Manager:: Notify

There is a tight coupling between the two classes in this approach. What would happen if the functionality changes. Let us say that along with managers, the employee also needs to notify the customers about the status of the work. We would then have a new class called Customer. The DoWork method would then need to take an array of customers and also notify them. This would require some rework of the Employee class.

Interface Based Approach

Taking extensibility into consideration, we change the design slightly by adding an interface called IBoss. IBoss declaration is as given below:

publicinterface IBoss
{
void Notify(string status);
}

The Manager class and the newly added class called Customer would implement IBoss.

Let us change the DoWork method of the Employee and get rid of the parameter managers. So how do we notify all the bosses? For this, let us add an interface called IWorker with a declaration as given below:

This is an implementation of the Observer design pattern. Here the worker is an observable object and the bosses are the observers. This certainly seems to be a better approach as anyone who needs to get notified needs to implement the IBoss interface and register with a worker. Hence once implemented, there is absolutely no change required in the Employee class!!! Moreover the bosses are not tied to the Employee. They are free to register with any Worker.

Delegate Based Approach

Though the interface based approach seems to be the right solution, let us also try out implementing the same using delegates. Delegates and Interfaces are conceptually similar because they are both implementation contracts.

First of all, we need to declare a delegate.

publicdelegatevoid NotifierDelegate(string status);

The Register method of the Employee has to be changed to take this delegateas a parameter.

This approach is very similar to the interface approach. But here the classes are not bound to implement any interface. They only need to have methods which match the delegatesignature. The employee also need not know whom to notify and which methods to call. It just has a set of delegates which it needs to invoke. Hence this removes the tight coupling between the two classes.

Conclusion

I hope this article has given the readers an insight into how to make the class design more manageable and extensible. I also believe that it has demonstrated the application of delegates in a design and the similarities between interfaces and delegates.

Hi,
In your second example, you are using the Observer design pattern[^], but you are making the observable object (the employee) responsible for registering the observer (the boss). This couples the two classes.
Try this instead:

The third example is an extension of this but uses language specific constructs (the c# delegate) to accomplish the same thing. You could just as easily use events for this. While not requiring that Boss or Customer implement an interface, there is still a dependency.
Read Head First Design Patterns[^].
I think you'll find it useful

I'm pretty new to programming in general, and maybe I'm just missing something, but I can't really tell a difference between your example and the interface example from the article. Both use Employee.registerBoss to save a reference to a Boss object in order to register it for notification, and both Employee classes iterate through the registered Boss objects and call the Boss.notify method.

In your reply, you pointed out that the fault in the article's example was that the Employee object should not be responsible for registering the bosses, but that seems to be exactly what your class does.

I'd be grateful if you could point out what the difference is supposed to be, and maybe explain how your method decreases the coupling between the two classes.

Hmm, it seems I misread the article's code, and you are absolutely right. There is no difference in the way that the bosses get registered. I apologize.

There is a small difference in the way the bosses get updated: in the article's example, the bosses are updated directly from DoWork(). In my example, the bosses are updated from the employee's Notify method. This is more in line with the spirit of Observer. The reason really has to do with coupling: the DoWork() method in the article is very aware of what is being notified, rather than simply that it is necessary to notify something.
Note that IRL one would never do what is going on in either example - both are far too much work for so few classes, they don't extend well if for instance you wish to have different types of employees doing different types of work, or if the bosses need to observe other entities besides employees, contractors or vendors for instance.
IRL one would instead create a more generic approach where the observable type exposes methods for registration (and deregistration) of observers, and notification of observers. This can be encapsulated in the class who's state should be monitored, or better yet in a seperate interface "BossNotifier", which would support other types observables. C# events are an example of this, though they have some flaws.
FYI my example is virtually word-for-word an example of observable you might see in a reference book.