Adapter


A problem with the design in Figure 33-3 is the potential violation of the Single-Responsibility Principle (SRP). We have bound together two things, Light and Switchable, that may not change for the same reasons. What if we can't add the inheritance relationship to Light? What if we purchased Light from a third party and don't have the source code? What if we want a Switch to control a class that we can't derive from Switchable? Enter the ADAPTER.[1]

[1] We've seen the ADAPTER before, in Figures 10-2 and 10-3.

Figure 33-4 shows how an Adapter can be used to solve the problem. The adapter derives from Switchable and delegates to Light. This solves the problem neatly. Now we can have any object that can be turned on or off controlled by a Switch. All we need to do is create the appropriate adapter. Indeed, the object need not even have the same turnOn and turnOff methods that Switchable has. The adapter can be adapted to the interface of the object.

Figure 33-4. Solving the table lamp problem with ADAPTER


Adapters don't come cheap. You need to write the new class, and you need to instantiate the adapter and bind the adapted object to it. Then, every time you invoke the adapter, you have to pay for the time and space required for the delegation. So clearly, you don't want to use adapters all the time. The ABSTRACT SERVER solution is quite appropriate for most situations. In fact, even the initial solution in Figure 33-1 is pretty good unless you happen to know that there are other objects for Switch to control.

The Class Form of Adapter

The LightAdapter class in Figure 33-4 is known as an object-form adapter. Another approach, known as the class-form adapter, is shown in Figure 33-5. In this form, the adapter object inherits from both the Switchable interface and the Light class. This form is a tiny bit more efficient than the object form and is a bit easier to use but at the expense of using the high coupling of inheritance.

Figure 33-5. Solving the table lamp problem with ADAPTER


The Modem Problem, Adapters, and LSP

Consider the situation in Figure 33-6. We have a large number of modem clients all making use of the Modem interface. The Modem interface is implemented by several derivatives, including HayesModem, USRoboticsModem, and ErniesModem. This is a pretty common situation. It conforms nicely to OCP, LSP, and DIP. Clients are unaffected when there are new kinds of modems to deal with. Suppose that this situation were to continue for several years. Suppose that there were hundreds of modem clients all making happy use of the Modem interface.

Figure 33-6. Modem problem


Now suppose that our customers have given us a new requirement. Certain kinds of modems, called dedicated modems,[2] don't dial. but sit at both ends of a dedicated connection. Several new applications use these dedicated modems and don't bother to dial. We'll call these the DedUsers. However, our customers want all the current modem clients to be able to use these dedicated modems, Telling us that they don't want to have to modify the hundreds of modem client applications. Those modem clients will simply be told to dial dummy phone numbers.

[2] All modems used to be dedicated; it is only in recent geological epochs that modems took on the ability to dial. In the early Jurassic period, you rented a breadbox-sized modem from the phone company and connected it to another modem through dedicated lines that you also rented from the phone company. (Life was good for the phone company in the Jurassic.) If you wanted to dial, you rented another breadbox-sized unit called an autodialer.

If we had our druthers, we might want to alter the design of our system as shown in Figure 33-7. We'd make use of ISP to split the dialing and communications functions into two separate interfaces. The old modems would implement both interfaces, and the modem clients would use both interfaces. The DedUsers would use nothing but the Modem interface, and the DedicatedModem would implement only the Modem interface. Unfortunately, this requires us to make changes to all the modem clients, something that our customers forbade.

Figure 33-7. Ideal solution to the modem problem


So what do we do? We can't separate the interfaces as we'd like, yet we must provide a way for all the modem clients to use DedicatedModem. One possible solution is to derive DedicatedModem from Modem and to implement the Dial and Hangup functions to do nothing, as follows:

class DedicatedModem : Modem {     public virtual void Dial(char phoneNumber[10]) {}     public virtual void Hangup() {}     public virtual void Send(char c)     {...}     public virtual char Receive()     {...} }


Degenerate functions are a sign that we may be violating LSP. The users of the base class may be expecting Dial and Hangup to significantly change the state of the modem. The degenerate implementations in DedicatedModem may violate those expectations.

Let's presume that the modem clients were written to expect their modems to be dormant until Dial is called and to return to dormancy when Hangup is called. In other words, they don't expect any characters to be coming out of modems that aren't dialed. DedicatedModem violates this expectation. It will return characters before Dial has been called and will continue to return them after Hangup has been called. Thus, DedicatedModem may crash some of the modem clients.

You might suggest that the problem is with the modem clients. They aren't written very well if they crash on unexpected input. I'd agree with that. But it's going to be difficult to convince the folks who have to maintain the modem clients to make changes to their software because we are adding a new kind of mode. Not only does this violate OCP, but also it's just plain frustrating. Besides, our customer has explicitly forbidden us to change the modem clients.

A kludge solution

We can simulate a connection status in Dial and Hangup of DedicatedModem. We can refuse to return characters if Dial has not been called or after Hangup has been called. If we make this change, all the modem clients will be happy and won't have to change. All we have to do is convince the DedUsers to call dial and hangup. See Figure 33-8.

Figure 33-8. Kludging DedicatedModem to simulate connection state


You might imagine that the folks who are building the DedUsers find this pretty frustrating. They are explicitly using DedicatedModem. Why should they have to call Dial and Hangup? However, they haven't written their software yet, so it's easier to get them to do what we want.

A tangled web of dependencies

Months later, when there are hundreds of DedUsers, our customers present us with a new change. It seems that all these years, our programs have not had to dial international phone numbers. That's why they got away with the char[10] in dial. Now, however, our customers want us to be able to dial phone numbers of arbitrary length. They have a need to make international calls, credit card calls, PIN-identified calls, and so on.

Clearly, all the modem clients must be changed. They were written to expect char[10] for the phone number. Our customers authorize this change because they have no choice, and hordes of programmers are put to the task. Just as clearly, the classes in the modem hierarchy must change to accommodate the new phone number size. Our little team can deal with that. Unfortunately, we now have to go to the authors of the DedUsers and tell them that they have to change their code! You might imagine how happy they'll be about that. They aren't calling dial because they need to. They are calling Dial because we told them they have to. And now they are going through an expensive maintenance job because they did what we told them to do.

This is the kind of nasty dependency tangle that many projects find themselves in. A kludge in one part of the system creates a nasty thread of dependency that eventually causes problems in what ought to be a completely unrelated part of the system.

Adapter to the rescue

We could have prevented this fiasco by using ADAPTER to solve the initial problem, as shown in Figure 33-9. In this case, DedicatedModem does not inherit from Modem. The modem clients use DedicatedModem indirectly through the DedicatedModemAdapter. This adapter implements Dial and Hangup to simulate the connection state. The adapter delegates send and recieve calls to the DedicatedModem.

Figure 33-9. Solving the modem problem with ADAPTER


Note that this eliminates all the difficulties we had before. Modem clients are seeing the connection behavior that they expect, and DedUsers don't have to fiddle with dial or hangup. When the phone number requirement changes, the DedUsers will be unaffected. Thus, by putting the adapter in place, we have fixed both LSP and OCP violations.

Note that the kludge still exists. The adapter is still simulating connection state. You may think that this is ugly, and I'd certainly agree with you. However, note that all the dependencies point away from the adapter. The kludge is isolated from the system, tucked away in an adapter that barely anybody knows about. The only hard dependency on that adapter will likely be in the implementation of some factory[3] somewhere.

[3] See Chapter 29.




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net