Labels

Tuesday, November 16, 2010

Adapter Design Pattern: Structural

1.1          Definition


The Adapter design pattern (sometimes referred to as the Wrapper Pattern or simply a Wrapper) 'adapts' one interface for a class into another that a client expects.
An adapter allows classes to work together that normally could not because of incompatible interfaces by wrapping its own interface around that of an already existing class.

The Adapter pattern maps the interface of one class onto another so that they can work together. These incompatible classes may come from different libraries or frameworks.

There are two types of adapter patterns:

·         The Object Adapter pattern - In this type of adapter pattern, the adapter contains an instance of the class it wraps. In this situation, the adapter makes calls to a physical instance of the wrapped object.



·         The Class Adapter pattern - This type of adapter uses multiple inheritance to achieve its goal. The adapter is created inheriting interfaces from both the interface that is expected and the interface that is pre-existing. This pattern is less often used as some popular languages, such as C#, Java, do not support true multiple inheritance as the designers of these languages consider it a dangerous practice.



The adapter pattern is useful in situations where an already existing class provides some or all of the services you need but does not use the interface you need. A good real life example is an adapter that converts the interface of a Document Object Model of an XML document into a tree structure that can be displayed. 

1.1          Intent

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
For instance, if multiple boolean values are stored as a single integer but your consumer requires a 'true'/'false', the adapter would be responsible for extracting the appropriate values from the integer value.

1.2          Motivation

  • Sometimes a toolkit or class library can not be used because its interface is incompatible with the interface required by an application.
  • We can not change the library interface, since we may not have its source code.
  • Even if we did have the source code, we probably should not change the library for each domain-specific application

1.3          Applicability

  • You want to use an existing class, and its interface does not match the one you need.
  • You want to create a reusable class that cooperates with unrelated classes with incompatible interfaces.

1.4          Structure



1.5          Participants

The classes and/or objects participating in this pattern are:

  •  Target   (ChemicalCompound)
    • defines the domain-specific interface that Client uses.
  •  Adapter   (Compound)
    • adapts the interface Adaptee to the Target interface.
  •  Adaptee   (ChemicalDatabank)
    • defines an existing interface that needs adapting.
  •  Client   (AdapterApp)
    • collaborates with objects conforming to the Target interface.

1.6          Sample Code

This code demonstrates the Adapter pattern which maps the interface of one class onto another so that they can work together. These incompatible classes may come from different libraries or frameworks.




using System;

namespace Xpanxion.Adapter
{

    class MainApp
    {
        static void Main()
        {
            Target target = new Adapter();
            target.Request();
            Console.Read();
        }
    }

    class Target
    {
        public virtual void Request()
        {
            Console.WriteLine("Called Target Request()");
        }
    }

    class Adapter : Target
    {
        private Adaptee adaptee = new Adaptee();

        public override void Request()
        {
            adaptee.SpecificRequest();
        }
    }

    class Adaptee
    {
        public void SpecificRequest()
        {
            Console.WriteLine("Called SpecificRequest()");
        }
    }
}



1.7          Sample Code2

This real-world code demonstrates the use of a legacy chemical databank. Chemical compound objects access the databank through an Adapter interface.




namespace Xpanxion.Adapter2
{
    class MainApp
    {
        static void Main()
        {
            // Non-adapted chemical compound
            Compound stuff = new Compound("Unknown");
            stuff.Display();

            // Adapted chemical compounds
            Compound water = new RichCompound("Water");
            water.Display();

            Compound benzene = new RichCompound("Benzene");
            benzene.Display();

            Compound alcohol = new RichCompound("Alcohol");
            alcohol.Display();

            // Wait for user
            Console.Read();
        }
    }

    // "Target"

    class Compound
    {
        protected string name;
        protected float boilingPoint;
        protected float meltingPoint;
        protected double molecularWeight;
        protected string molecularFormula;

        // Constructor
        public Compound(string name)
        {
            this.name = name;
        }

        public virtual void Display()
        {
            Console.WriteLine("\nCompound: {0} ------ ", name);
        }
    }

    // "Adapter"

    class RichCompound : Compound
    {
        private ChemicalDatabank bank;

        // Constructor
        public RichCompound(string name)
            : base(name)
        {
        }

        public override void Display()
        {
            // Adaptee
            bank = new ChemicalDatabank();
            boilingPoint = bank.GetCriticalPoint(name, "B");
            meltingPoint = bank.GetCriticalPoint(name, "M");
            molecularWeight = bank.GetMolecularWeight(name);
            molecularFormula = bank.GetMolecularStructure(name);

            base.Display();
            Console.WriteLine(" Formula: {0}", molecularFormula);
            Console.WriteLine(" Weight : {0}", molecularWeight);
            Console.WriteLine(" Melting Pt: {0}", meltingPoint);
            Console.WriteLine(" Boiling Pt: {0}", boilingPoint);
        }
    }

    // "Adaptee"

    class ChemicalDatabank
    {
        // The Databank 'legacy API'
        public float GetCriticalPoint(string compound, string point)
        {
            float temperature = 0.0F;

            // Melting Point
            if (point == "M")
            {
                switch (compound.ToLower())
                {
                    case "water": temperature = 0.0F; break;
                    case "benzene": temperature = 5.5F; break;
                    case "alcohol": temperature = -114.1F; break;
                }
            }
            // Boiling Point
            else
            {
                switch (compound.ToLower())
                {
                    case "water": temperature = 100.0F; break;
                    case "benzene": temperature = 80.1F; break;
                    case "alcohol": temperature = 78.3F; break;
                }
            }
            return temperature;
        }

        public string GetMolecularStructure(string compound)
        {
            string structure = "";

            switch (compound.ToLower())
            {
                case "water": structure = "H20"; break;
                case "benzene": structure = "C6H6"; break;
                case "alcohol": structure = "C2H6O2"; break;
            }
            return structure;
        }

        public double GetMolecularWeight(string compound)
        {
            double weight = 0.0;
            switch (compound.ToLower())
            {
                case "water": weight = 18.015; break;
                case "benzene": weight = 78.1134; break;
                case "alcohol": weight = 46.0688; break;
            }
            return weight;
        }
    }
}


OutPut:

Compound: Unknown ------

Compound: Water ------
 Formula: H20
 Weight : 18.015
 Melting Pt: 0
 Boiling Pt: 100

Compound: Benzene ------
 Formula: C6H6
 Weight : 78.1134
 Melting Pt: 5.5
 Boiling Pt: 80.1

Compound: Alcohol ------
 Formula: C2H6O2
 Weight : 46.0688
 Melting Pt: -114.1
 Boiling Pt: 78.3


1.8          Adaptor in .NET

One of the strengths of the .NET Framework is backward compatibility. From .NET-based code you can easily call legacy COM objects and vice versa. In order to use a COM component in your project, all you have to do is add a reference to it via the Add Reference dialog in Visual Studio .NET. Behind the scenes, Visual Studio® .NET invokes the tlbimp.exe tool to create a Runtime Callable Wrapper (RCW) class, contained in an interop assembly. Once the reference has been added (and the interop assembly has been generated for you), the COM component can be used like any other class in managed code. If you were looking at code someone else had written without seeing the list of references (and without examining metadata associated with the classes or their implementation), you would be unable to tell which classes were written in a .NET-targeted language and which were COM components.

The magic that makes this happen is contained in the RCW. COM components have different error handling mechanisms and also make use of different data types. For example, strings in the .NET Framework use the System.String class while COM might use a BSTR. When calling a COM component with a string parameter from .NET-based code, though, you can pass in a System.String just like you would to any other similar managed code method. Inside the RCW, this System.String is converted into a format that the COM component expects, like a BSTR, before the COM call is made. Similarly, a method call on a COM component typically returns an HRESULT to indicate success or failure. When a COM method call returns an HRESULT that indicates that the call failed, the RCW turns this into an exception (by default), so it can be handled like all other managed code errors.

By allowing managed classes and COM components to interact despite their interface differences, RCWs are an example of the Adapter pattern. The Adapter pattern lets you adapt one interface to another. COM doesn't understand the System.String class, so the RCW adapts it to something that it can understand. Even though you can't change how a legacy component works, you can still interact with it. Adapters are frequently used like this.

The Adapter class itself wraps an Adaptee, translating all calls from the client into the appropriate format and sequence of calls. Though this sounds similar to the Decorator, there are several key differences. With a Decorator, the interfaces of the objects you're composing are the same, while the entire point of an Adapter is to allow you to change interfaces.Adapters also have a definite sequence to them; the Adaptee must be contained by the Adapter. A Decorator class doesn't need to know whether it is wrapped by 1 or 500 other classes, since the interfaces are all the same. As a result, the use of Decorators can be transparent to the application, while the use of Adapter cannot.


Hope this helps.

Thanks & Regards,
Arun Manglick

No comments:

Post a Comment