Design Patterns in Java: Adapter
- Adapting to an Interface
- Class and Object Adapters
- Adapting Data for a JTable
- Identifying Adapters
- 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 has 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 with a name such as usefulMethod() that can fulfill the client’s needs. 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 that 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 RocketSim interface. Figure 3.3 partially shows this design.
Figure 3.3 When completed, this diagram will show the design of a class that adapts the Rocket class to meet the needs of the RocketSim 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 RocketSim 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 Oozi nozRocket object can maintain a time instance variable that it can pass to the methods of the PhysicalRocket class as needed.
The code for PhysicalRocket is somewhat complex, as it embodies the physics that Oozinoz uses to model a rocket. However, that is exactly the logic that we want to reuse. The OozinozRocket adapter class simply translates calls to use its superclass’s methods. The code for this new subclass will look something like this:
package com.oozinoz.firework; import com.oozinoz.simulation.*; public class OozinozRocket extends PhysicalRocket implements RocketSim { private double time; public OozinozRocket( double burnArea, double burnRate, double fuelMass, double totalMass) { super(burnArea, burnRate, fuelMass, totalMass); } public double getMass() { // Challenge! } public double getThrust() { // Challenge! } public void setSimTime(double time) { this.time = time; } }
When a client defines its expectations in an interface, you can apply Adapter by supplying a class that implements that 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."