Saturday, December 3, 2011

The Adapter Pattern

In this post we'll the talking about the Adapter Pattern.
The Adapter pattern, in short, makes one class look like another. We've all seen adapters in the real world: many electrical plugs in the U.S. have three prongs while many of the older outlets or extension cords only have two slots for it. We'd still like to be able to use them, after all there's still a plug, an outlet and they both are made for running an electrical current through it.

To get around this we can buy an adapter for the plug. It's got three slots on one side and two prongs in the other. We simply plug it into the adapter and the outlet will accept it.

Let's say there's a client class that expects a certain type of object to work but we want to use an object of a type that, while similar, just doesn't fit. All we have to do is write an adapter for it and pass our client the adapter in place of our original class and we're set. The adapter sits in between the client and the vendor class and acts as sort of a translator between the two.

Let's take a look at some simple classes to illustrate. Going back to car examples...
//First we'll need a Vehicle interface.
interface Vehicle
{
    void TurnOn();
    void TurnOff();
    void Drive();
    void FillGasTank();
}

class Car : Vehicle
{
    public void TurnOn()
    {
        Console.WriteLine("Starting ignition.")
    }

    public void TurnOff()
    {
        Console.WriteLine("Shutting off engine.")
    }

    public void Drive()
    {
        Console.WriteLine("Vroom!");
    }

    public void Break()
    {
        Console.WriteLine("Screetch!");
    }

    public void FillGasTank()
    {
        Console.WriteLine("Filling tank.");
    }
}
Now let's say we have a client class with methods that accepts vehicles.
class VehicleTester
{
    public void TestVehicle(Vehicle vehicle)
    {
        vehicle.TurnOn();
        vehicle.Drive();
        vehicle.Break();
        vehicle.FillGasTank();
        vehicle.TurnOff();
    }
}
Seems like a fine vehicle tester but let's take a look at another class, a LawnMower.
class LawnMower
{
    public void TurnOn()
    {
        Console.WriteLine("Pulling cord to start engine.")
    }

    public void TurnOff()
    {
        Console.WriteLine("Push off button");
    }

    public void Push()
    {
        Console.WriteLine("Pushing mower around.");
    }

    public void FillGasTank()
    {
        Console.WriteLine("Filling the tank");
    }
}
We would like to be able to test our new LawnMower with the tester we've already implemented but we shouldn't inherit from Vehicle. It's not exactly a vehicle and there's no Break method.
We could write a separate TestMower method, but that's more coding and there will be code duplication. And if we come up with similar classes in the future that we'd like to test, the code will become messy and confusing quickly as we add these tester methods.

We can fix this by making a class called an adapter which will inherit from the target interface and hold a reference to the object we are adapting. In the adapter's overridden methods, we simply call the corresponding methods on the adaptee.
Let's look at the adapter code to help illustrate.
//The adapter inherits the Vehicle class so that our 
//VehicleTester class method will accept it as a parameter
class LawnMowerAdapter : Vehicle
{
    //The adapter contains a LawnMower reference.
    LawnMower _mower;

    //The constructor is passed a LawnMower to adapt.
    public LawnMowerAdapter(LawnMower mower)
    {
        _mower = mower
    }

    //The adapter must implement all the Vehicle methods
    //and there we'll call the appropriate methods on the mower.
    public void TurnOn()
    {
        _mower.TurnOn();
    }

    public void TurnOff()
    {
        _mower.TurnOff();
    }

    public void Drive()
    {
        _mower.Push()
    }

    public void Break()
    {
        //Traditional lawn mowers don't have a break, you just stop pushing.
        //This method can be left blank.
        //One could code some logic for stopping however this would
        //increase the coupling between Adapter and LawnMower. 
    }

    public void FillGasTank()
    {
        _mower.FillGasTank();
    }
}
Now if we have a VehicleTester that we'd like to use to test our LawnMower, we have a neat way to do that. With an adapter.
We first create the adapter giving it the mower we want to test, then we pass the adapter object off to the VehicleTester's TestVehicle method, who accepts it happily since the adapter implements the Vehicle interface. It tests the Mower with no problems or even the knowledge that it's really dealing with a LawnMower (wrapped in an adapter).

The adapter links the client, the VehicleTester, to the adaptee, the LawnnMower. It does this by tricking the client into thinking that it's dealing with the target when it's really just calling methods on the adaptee.

Which methods being called is completely up the adapter. It acts as a translator for the client.

Let's see this in action
public class Program
{
    public void Main(string[] args)
    {
        VehicleTester tester = new VehicleTester();

        //Create the car.
        Car car = new Car();
        
        //Test the car.
        tester.TestVehicle(car);

        //We now create the LawnMower.
        LawnMower mower = new LawnMower();

        //Although we'd like to we can't do this:
        //tester.TestVehicle(mower);
        
        //so we create an adapter object.
        //passing the mower to its constructer.
        LawnMowerAdapter adapter = new LawnMowerAdapter(mower);

        //Now we can test it via the adapter.
        tester.TestVehicle(adapter);
    }
}
Now the console should print something like this out:

Starting ignition.
Vroom!
Screetch!
Filling tank.
Shutting off engine.
Pulling cord to start engine.
Pushing mower around.
Filling the tank.
Push off button.

Perfect!
The Adapter pattern, turning one class into another.

We'll wrap up with a quick look at the class diagram.



That's all for now. Hope you enjoyed learning about the adapter pattern and thanks for reading.

No comments :

Post a Comment