- Adapting to an Interface
- Class and Object Adapters
- Adapting Data in .NET
- Summary
Class and Object Adapters
The designs in Figures 3.1 and 3.3 show class adapters that adapt through subclassing. In a class adapter design, the new adapter class implements the desired interface and subclasses an existing class. This approach will not always work, particularly when the set of methods that you need to adapt is not specified in a C# interface. In such a case, you can create an object adapter, an adapter that uses delegation rather than subclassing. Figure 3.4 shows this design.
Figure 3.4. You can create an object adapter by subclassing the class that you need, fulfilling the required methods by relying on an object of an existing class.
The NewClass class in Figure 3.4 is an example of ADAPTER. An instance of this class is an instance of the RequiredClass class. In other words, the NewClass class meets the needs of the client. The NewClass class can adapt the ExistingClass class to meet the client's needs by using an instance of ExistingClass.
For a more concrete example, suppose that the simulation package worked directly with a Skyrocket class, without specifying an interface to define the behaviors the simulation needs. Figure 3.5 shows this class.
Figure 3.5. In this alternative design, the Simulation package does not specify the interface it needs for modeling a rocket.
The Skyrocket class uses a fairly primitive model of the physics of a rocket. For example, it assumes that the rocket is entirely consumed as its fuel burns. Suppose that you want to apply the more sophisticated physical model that the Oozinoz PhysicalRocket class uses. To adapt the logic in the PhysicalRocket class to the needs of the simulation, you can create an OozinozSkyrocket class as an object adapter that subclasses Skyrocket and that uses a PhysicalRocket object, as Figure 3.6 shows.
Figure 3.6. When complete, this diagram shows an object adapter design that uses information from an existing class to meet the needs that a client has of a Skyrocket object.
Notice that the OozinozSkyrocket class subclasses from Skyrocket, not PhysicalRocket. This will allow an OozinozSkyrocket object to serve as a substitute wherever the simulation client needs a Skyrocket object. The Skyrocket class supports subclassing by making its _time variable protected (as shown in the UML diagram) and by making its methods virtual (not shown in the diagram).
Complete the class diagram in Figure 3.6 to show a design that allows OozinozRocket objects to serve as Skyrocket objects.
Challenge 3.3
A solution appears on page 351.
The code for the OozinozSkyrocket class might be as follows:
public class OozinozSkyrocket : Skyrocket { private PhysicalRocket _rocket; public OozinozSkyrocket(PhysicalRocket r) : base (r.GetMass(0), r.GetThrust(0), r.GetBurnTime()) { _rocket = r; } public override double GetMass() { return _rocket.GetMass(_simTime); } public override double GetThrust() { return _rocket.GetThrust(_simTime); } }
The OozinozSkyrocket class lets you supply an OozinozSkyrocket object anywhere the simulation package requires a Skyrocket object. In general, object adapters partially overcome the problem of adapting an object to an interface that is not expressly defined.
Name one reason why the object adapter design that the OozinozSkyrocket class uses may be more fragile than a class adapter approach.
Challenge 3.4
Solutions appear on page 352.
The object adapter for the Skyrocket class is a more dangerous design than the class adapter that implements the IRocketSim interface. But we should not complain too much. At least the Skyrocket designer marked the methods as virtual. Suppose that this were not the case. Then the OozinozSkyrocket class could not override the GetMass() or GetThrust() methods. A subclass cannot force adaptability onto its superclass.
The best way to plan for adaptation is to define the needs of a client program in an interface. If you do not foresee a specific type of adaptation, you may still want other developers to be able to create object adapters for your class. In this case, place a virtual modifier on any methods that you want to let subclasses override.