Agile Principles, Patterns, and Practices in C#

Consider a security system in which Door objects can be locked and unlocked and know whether they are open or closed. (See Listing 12-1.) This Door is coded as an interface so that clients can use objects that conform to the Door interface without having to depend on particular implementations of Door.

Listing 12-1. Security Door

public interface Door { void Lock(); void Unlock(); bool IsDoorOpen(); }

Now consider that one such implementation, TimedDoor, needs to sound an alarm when the door has been left open for too long. In order to do this, the TimedDoor object communicates with another object called a Timer. (See Listing 12-2.)

Listing 12-2.

public class Timer { public void Register(int timeout, TimerClient client) {/*code*/} } public interface TimerClient { void TimeOut(); }

When an object wishes to be informed about a timeout, it calls the Register function of the Timer. The arguments of this function are the time of the timeout and a reference to a TimerClient object whose TimeOut function will be called when the timeout expires.

How can we get the TimerClient class to communicate with the TimedDoor class so that the code in the TimedDoor can be notified of the timeout? There are several alternatives. Figure 12-1 shows a common solution. We force Door, and therefore TimedDoor, to inherit from TimerClient. This ensures that TimerClient can register itself with the Timer and receive the TimeOut message.

Figure 12-1. TimerClient at top of hierarchy

The problem with this solution is that the Door class now depends on TimerClient. Not all varieties of Door need timing. Indeed, the original Door abstraction had nothing whatever to do with timing. If timing-free derivatives of Door are created, they will have to provide degenerate implementations for the TimeOut method a potential violation of LSP. Moreover, the applications that use those derivatives will have to import the definition of the TimerClient class, even though it is not used. That smells of needless complexity and needless redundancy.

This is an example of interface pollution, a syndrome that is common in statically typed languages, such as C#, C++, and Java. The interface of Door has been polluted with a method that it does not require. It has been forced to incorporate this method solely for the benefit of one of its subclasses. If this practice is pursued, every time a derivative needs a new method, that method will be added to the base class. This will further pollute the interface of the base class, making it "fat."

Moreover, each time a new method is added to the base class, that method must be implemented or allowed to default in derived classes. Indeed, an associated practice is to add these methods to the base class, giving them degenerate, or default, implementations specifically so that derived classes are not burdened with the need to implement them. As we learned previously, such a practice can violate LSP, leading to maintenance and reusability problems.

Категории