poniedziałek, 10 maja 2010

Adapter pattern

Adapter pattern jak można przeczytać w Wikipedii: "Opakowanie, wraper; umożliwia współpracę dwom klasom o niekompatybilnych interfejsach", 

Koncepcje adaptera jest bardzo prosta i łatwa w zrozumieniu. Tak samo jak w życiu korzystamy z różnych przejściówek (adapterów) np. zmiany wtyczki do kontaktu z tej stosowanej w USA na polską, czy też zmiany PS2 na USB poprzez odpowiednią przejściówkę w myszce czy klawiaturze. W oprogramowaniu także może dojść do takiej potrzeby. Wyobraźmy sobie sytuację kiedy to do dawno napisanego oprogramowania chcemy dołożyć nowo napisany moduł, który korzysta z innego interfejsu niż stara część kodu. Przy dużym projekcie zmiana starego kodu na interfejs obsługiwany przez nowy moduł mogłaby pochłonąć bardzo dużo czasu. Dlatego łatwiej zastosować "przejściówkę" pomiędzy starym a nowym.

Całość pracy podczas tworzenia Adaptera można wykonać w trzech krokach:
  1. Implementujemy interfejs, który zamierzamy adaptować.
  2. Tworzymy referencję do obiektu który zamierzamy adaptować
  3. Implementujemy metody adaptowanego interfejsu.
Zobaczmy na przykładowy program:

    public interface Circle
    {
        void Draw();
    }

    public class RegularCircle : Circle
    {
        public void Draw()
        {
            Console.WriteLine("I'm drawing a regular circle");
        }
    }

    public interface Rectange
    {
        void DrawRectange();
    }

    public class RegularRectange : Rectange
    {
        public void DrawRectange()
        {
            Console.WriteLine("I'm drawing a regular rectangle");
        }
    }

    public class RectangleAdapter : Circle
    {
        private Rectange rectangle;

        public RectangleAdapter(Rectange rectangle)
        {
            this.rectangle = rectangle;
        }

        public void Draw()
        {
            rectangle.DrawRectange();
        }
    }



    class Program
    {
        static void Main(string[] args)
        {
            Circle c = new RegularCircle();
            c.Draw();
            Rectange r = new RegularRectange();
            Circle c2 = new RectangleAdapter(r);
            c2.Draw();
        }
    }

Mamy tutaj prosty przykład zastosowania adaptera. Klasa RegularCircle i interfejs Circle są kodem który kiedyś powstał. Dodajemy nowy moduł który służy do rysowania prostokątów. Chcemy, aby tak jak poprzednio można było używać dobrze znanego nam interfejsu, który oferował prostą metodą Draw do malowania kształtów. Poprzez klasę RectangleAdapter tworzymy tzw. wraper, który opakowuje Rectangle i implementuje interfejs Circle. Oczywiście metoda Draw w RectangleAdapter mogłaby być zaimplementowana inaczej i wykonywać jeszcze jakieś dodatkowe operacje po drodze.

Ogólny diagram UML dla Adaptera wygląda następująco:


Istnieją dwie odmiany adaptera: obiektowy (przedstawiony powyżej) oraz klasowy.
Diagram dla wersji klasowej przedstawia się następująco:


Różnica polega na tym, że w przypadku wersji klasowej wykorzystany jest mechanizm wielodziedziczenia. W językach takich jak C# czy Java nie można stosować tej techniki. W C++ dla odmiany jest to możliwe.

Można także tworzyć Adaptery dwukierunkowe. W tym celu klasa Adaptera zawiera referencje do obu interfejsów:

    public class RectangleCircleAdapter : Circle, Rectange
    {
        private Rectange rectangle;
        private Circle circle;

        public RectangleCircleAdapter(Rectange rectangle)
        {
            this.rectangle = rectangle;
        }

        public RectangleCircleAdapter(Circle circle)
        {
            this.circle = circle;
        }

        public void Draw()
        {
            rectangle.DrawRectange();
        }

        public void DrawRectange()
        {
            circle.Draw();
        }
    }

Adapter w wielu sytuacjach może zaoszczędzić nam wiele czasu, który możemy wykorzystać na dodawanie nowej funkcjonalności do naszego oprogramowania, zamiast zastanawiania się jak przerobić stary kod aby pasował do nowego.

Brak komentarzy:

Prześlij komentarz