C# Design Patterns: Adapter
- Adapting to an Interface
- Class and Object Adapters
- Adapting Data in .NET
- Summary
An object is a client if it needs to call your code. In some cases, client code will be written after your code exists and the developer can mold the client to use the interfaces of the objects that you provide. In other cases, clients may be developed independently of your code. For example, a rocket simulation program might be designed to use rocket information that you supply, but such a simulation will have its own definition of how a rocket should behave. In such circumstances, you may find that an existing class performs the services that a client needs but with different method names. In this situation, you can apply the ADAPTER pattern. The intent of ADAPTER is to provide the interface that a client expects while using the services of a class with a different interface.
Adapting to an Interface
When you need to adapt your code, you may find that the client developer planned well for such circumstances. This is evident when the developer provides an interface that defines the services that the client code needs, as the example in Figure 3.1 shows. A client class makes calls to a RequiredMethod() method that is declared in an interface. You may have found an existing class with a method that can fulfill the client's needs, with a name such as UsefulMethod(). You can adapt the existing class to meet the client's needs by writing a class that extends ExistingClass, implements RequiredInterface, and overrides RequiredMethod() so that it delegates its requests to UsefulMethod().
Figure 3.1. When a developer of client code thoughtfully defines the client's needs, you may be able to fulfill the interface by adapting existing code.
The NewClass class in Figure 3.1 is an example of ADAPTER. An instance of this class is an instance of RequiredInterface. In other words, the NewClass class meets the needs of the client.
For a more concrete example, suppose you are working with a package that simulates the flight and timing of rockets such as those you manufacture at Oozinoz. The simulation package includes an event simulator that explores the effects of launching several rockets, along with an interface that specifies a rocket's behavior. Figure 3.2 shows this package.
Figure 3.2. The Simulation package clearly defines its requirements for simulating the flight of a rocket.
Suppose that at Oozinoz you have a PhysicalRocket class that you want to plug into the simulation. This class has methods that supply, approximately, the behavior that the simulator needs. In this situation, you can apply ADAPTER, creating a subclass of PhysicalRocket that implements the IRocketSim interface. Figure 3.3 partially shows this design.
Figure 3.3. When complete, this diagram shows the design of a class that adapts the Rocket class to meet the needs of the IRocketSim interface.
The PhysicalRocket class has the information that the simulator needs, but its methods do not exactly match those that the simulation declares in the IRocketSim interface. Most of the differences occur because the simulator keeps an internal clock and occasionally updates simulated objects by calling a SetSimTime() method. To adapt the PhysicalRocket class to meet the simulator's needs, an OozinozRocket object can maintain a _time instance variable that it can pass to the methods of the PhysicalRocket class as needed.
Complete the class diagram in Figure 3.3 to show the design of an OozinozRocket class that lets a PhysicalRocket object participate in a simulation as an IRocketSim object.
Challenge 3.1
A solution appears on page 350.
The code for PhysicalRocket is somewhat complex as it embodies the physics that Oozinoz uses to model a rocket. However, it is exactly that logic we want to reuse without reimplementing. The OozinozRocket class simply translates calls to use its superclass's methods. The code for this new subclass will look something like:
public class OozinozRocket : PhysicalRocket, IRocketSim { private double _time; public OozinozRocket( double burnArea, double burnRate, double fuelMass, double totalMass) : base (burnArea, burnRate, fuelMass, totalMass) { } public double GetMass() { // challenge! } public double Thrust() { // challenge! } public void SetSimTime (double time) { _time = time; } }
Complete the code for the OozinozRocket class, including methods GetMass() and GetThrust().
Challenge 3.2
A solution appears on page 351.
When a client defines its expectations in an interface, you can apply ADAPTER by supplying a class that implements a provided interface and that subclasses an existing class. You may also be able to apply ADAPTER even if no interface exists to define a client's expectations. In this situation, you must use an “object adapter.”