Consider the TimedDoor again. Here is an object that has two separate interfaces used by two separate clients: Timer and the users of Door. These two interfaces must be implemented in the same object, since the implementation of both interfaces manipulates the same data. How can we conform to ISP? How can we separate the interfaces when they must remain together? The answer lies in the fact that clients of an object do not need to access it through the interface of the object. Rather, they can access it through delegation or through a base class of the object. Separation Through DelegationOne solution is to create an object that derives from TimerClient and delegates to the TimedDoor. Figure 12-2 shows this solution. When it wants to register a timeout request with the Timer, the TimedDoor creates a DoorTimerAdapter and registers it with the Timer. When the Timer sends the TimeOut message to the DoorTimerAdapter, the DoorTimerAdapter delegates the message back to the TimedDoor. Figure 12-2. Door timer adapter
This solution conforms to ISP and prevents the coupling of Door clients to Timer. Even if the change to Timer shown in Listing 12-3 were to be made, none of the users of Door would be affected. Moreover, TimedDoor does not have to have the exact same interface as TimerClient. The DoorTimerAdapter can translate the TimerClient interface into the TimedDoor interface. Thus, this is a very general-purpose solution. (See Listing 12-4.) Listing 12-4. TimedDoor.cs
However, this solution is also somewhat inelegant. It involves the creation of a new object every time we wish to register a timeout. Moreover, the delegation requires a very small, but still nonzero, amount of runtime and memory. In some application domains, such as embedded real-time control systems, runtime and memory are scarce enough to make this a concern. Separation Through Multiple InheritanceFigure 12-3 and Listing 12-5 show how multiple inheritance can be used to achieve ISP. In this model, TimedDoor inherits from both Door and TimerClient. Although clients of both base classes can make use of TimedDoor, neither depends on the TimedDoor class. Thus, they use the same object through separate interfaces. Figure 12-3. Multiply inherited TimedDoor
Listing 12-5. TimedDoor.cpp
This solution is my normal preference. The only time I would choose the solution in Figure 12-2 over that in Figure 12-3 is if the translation performed by the DoorTimerAdapter object were necessary or if different translations were needed at different times. |