Адаптер (шаблон)

Устройство ,спомагащо връзката между отделни части на електрониката и механизми.

Адаптер (на английски: Adapter) е структурен шаблон за дизайн, който се използва в обектно-ориентираното програмиране. Той позволява интерфейсът на даден клас да бъде използван от друг интерфейс.[1] Често се ползва при необходимост съществуващи класове да работят с други, без да се променя техният изходен код.

UML диаграма на шаблона Адаптер, приложим за класове.

Определение

редактиране

Най-простото определение за адаптер е инструмент, който прави възможна работата на два несъвместими интерфейса. Интерфейсите могат да бъдат несъвместими, но вътрешните функционалности трябва да отговарят на приложението. Адаптерът позволява иначе несъвместими класове да работят заедно чрез превръщането на интерфейса на един от класовете в интерфейс с вид, очакван от клиента.

Структура

редактиране

Съществуват два вида адаптери:

  1. Адаптер на обект. В този случай адаптерът съдържа инстанция на класа, който обвива, като адаптерът извиква инстанцията на обвития обект
  2. Адаптер на класове. Този тип адаптер използва няколко полиморфни интерфейса за имплементиране или наследяване както на интерфейса, който се очаква, така и на интерфейса, който е вече наличен. Характерно за очаквания интерфейс е той да бъде създаден като чист интерфейс клас, особено в езици като Java (преди JDK 1.8), които не поддържат множествено наследяване на класове.

Приложение

редактиране

Адаптерът е полезен в ситуации, в които един вече съществуващ клас осигурява някои или всички необходими услуги, но не използва необходимия интерфейс. Един добър пример от реалния живот е адаптер, който преобразува интерфейса на Document Object Model на XML документ в дървовидна структура, която може да бъде показана.

Допълнителна форма на изпълнение на Адаптер

редактиране

Съществува и допълнителна форма на изпълнение на Адаптер както следва:

Казано е за клас А да подаде някакви данни на клас B. Да допуснем, че това са данни от типа низ. Решението за компилация е:

classB.setStringData(classA.getStringData());

Въпреки това, да предположим, че форматът на данните от тип низове трябва да се промени. Решението е да се използва наследяване:

public class Format1ClassA extends ClassA {
    @Override
    public String getStringData() {
        return format(toString());
    }
}

и да се създаде правилно „форматиране“ на обекта по време на изпълнение с помощта на адаптера Factory.

Решение използващо „шаблони“ се изпълнява по следния начин:

(i) дефиниране на посредник „Доставчик“ интерфейс, изписване имплементация за изпълнение на този „Доставчик“ интерфейс, който обвива източника на данните (Class A В този пример) и извеждане данните форматирани по целесъобразност:

public interface StringProvider {
    public String getStringData();
}
public class ClassAFormat1 implements StringProvider {
    private ClassA classA = null;
    public ClassAFormat1(final ClassA A) {
        classA = A;
    }
    public String getStringData() {
        return format(classA.getStringData());
    }
    private String format(String sourceValue) {
        //манипулира низът-източник във
        //формат, който се изисква от обекта, нуждаещ се от информацията на
        //обекта-източникreturn sourceValue.trim();
    }
}

(ii) изписване на адаптер клас, който връща характерната имплементация на „Доставчика“:

public class ClassAFormat1Adapter extends Adapter {
    public Object adapt(final Object OBJECT) {
    return new ClassAFormat1((ClassA) OBJECT);
    }
}

(iii) регистриране на адаптера с глобален регистър, така че адаптерът да може да бъде видян по време на изпълението:

AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");

(iv) Пренасяне на данни от клас А в клас Б:

Adapter adapter =
    AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
        StringProvider provider = (StringProvider) adapter.adapt(classA);
        String string = provider.getStringData();
        classB.setStringData(string);

или по-добре формулирано:

classB.setStringData(
    ((StringProvider)
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format1")
            .adapt(classA))
        .getStringData());

(v) предимството може да бъде видяно в това, ако решим да пренесем данните във втори формат. Тогава да погледнем различния адаптер/доставчик:

Adapter adapter =
    AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");

или ако желаем да изведем данните от клас А, да кажем, като снимки в клас С:

Adapter adapter =
    AdapterFactory.getInstance()
        .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
ImageProvider provider = (ImageProvider) adapter.adapt(classA);
classC.setImage(provider.getImage());

(vii) По този начин използването на адаптери и доставчици позволява множество „изгледи“ от клас C и клас A към клас B, без да се налага да се променя йерархията на класа. Като цяло, това позволява механизма за произволни потоци данни между обекти да бъдат монтирани на съществуваща йерархия.

Изпълнение на адаптер модела

редактиране

При прилагането на адаптер за по-голяма яснота може да се прилага името на класа [име на класа] до [Interface] адаптера за прилагане на имплементацията. За пример – DAOToProviderAdapter. Тя трябва да има метод конструктор с променлива от тип adaptee като параметър. Този параметър ще бъде приет за член на инстанция на [име на класа] до [Interface] адаптер. Когато clientMethod се извика той ще има достъп до adaptee инстанцията, която дава възможност за достъп до необходимите данни на adaptee и извършване на операции по тези данни, което генерира желания изход.

public class AdapteeToClientAdapter implements Adapter {
    private final Adaptee instance;
    public AdapteeToClientAdapter(final Adaptee instance) {
        this.instance = instance;
    }
    @Overridepublic void clientMethod() {
    }
}

Имплементация в Scala:

implicit def adaptee2Adapter(adaptee: Adaptee): Adapter = {
    new Adapter {
        override def clientMethod: Unit = {
        // повиква метода/и на Adaptee, за да осъществи clientMethod на Клиента. */
        }
    }
}

Лепило код

редактиране

Терминът лепило код понякога се използва за описване на реализации на модела на адаптера. Тя не допринася на изчисленията или компилацията. Тя по-скоро служи като прокси между иначе несъвместими части на софтуер, за да ги направят съвместими. Стандартната практика е да се запази логиката на лепило кода и да се остави това на кода към който се свързва.

Java (чрез наследяване)

редактиране
// Target
public interface Chief
{
  public Object makeBreakfast();
  public Object makeLunch();
  public Object makeDinner();
}

// Adaptee
public class Plumber
{
  public Object getScrewNut()
  { ... }
  public Object getPipe()
  { ... }
  public Object getGasket()
  { ... }
}

// Adapter
public class ChiefAdapter extends Plumber implements Chief
{
  public Object makeBreakfast()
  {
    return getGasket();
  }
  public Object makeLunch()
  {
    return getPipe();
  }
  public Object makeDinner()
  {
    return getScrewNut();
  }
}

// Client
public class Client
{
  public static void eat(Object dish)
  { ... }

  public static void main(String[] args)
  {
    Chief ch = new ChiefAdapter();
    Object dish = ch.makeBreakfast();
    eat(dish);
    dish = ch.makeLunch();
    eat(dish);
    dish = ch.makeDinner();
    eat(dish);
    callAmbulance();
  }
}

Java (чрез композиция)

редактиране
// Файл Chief.java

public interface Chief {

	public Object makeBreakfast();
	public Object makeDinner();
	public Object makeSupper();

}

// Файл Plumber.java

public class Plumber {

	public Object getPipe(){
		return new Object();
	}

	public Object getKey(){
		return new Object();
	}

	public Object getScrewDriver(){
		return new Object();
	}

}

// Файл ChiefAdapter.java

public class ChiefAdapter implements Chief{

	private Plumber plumber = new Plumber();

	@Override
	public Object makeBreakfast() {
		return plumber.getKey();
	}

	@Override
	public Object makeDinner() {
		return plumber.getScrewDriver();
	}

	@Override
	public Object makeSupper() {
		return plumber.getPipe();
	}

}

// Файл Client.java

public class Client {

	public static void main (String [] args){
		Chief chief = new ChiefAdapter();

		Object key = chief.makeDinner();
	}

}

PHP5 (пример за Адаптер шаблон на PHP5)

редактиране
<?php
class IndependentDeveloper1
{
    public function calc($a, $b) {
        return $a + $b;
    }
}

class IndependentDeveloper2
{
    public function nameIsVeryLongAndUncomfortable($a, $b) {
        return $a + $b;
    }
}

interface IAdapter
{
    public function sum($a, $b);
}

class ConcreteAdapter1 implements IAdapter
{
    protected $object;

    public function __construct() {
        $this->object = new IndependentDeveloper1();
    }
    public function sum($a, $b) {
        return $this->object->calc($a, $b);
    }
}

class ConcreteAdapter2 implements IAdapter
{
    protected $object;

    public function __construct() {
        $this->object = new IndependentDeveloper2();
    }
    public function sum($a, $b) {
        return $this->object->nameIsVeryLongAndUncomfortable($a, $b);
    }
}

$adapter1 = new ConcreteAdapter1();
$adapter2 = new ConcreteAdapter2();

function sum(IAdapter $adapter) {
    echo $adapter->sum(2, 2);
}

sum($adapter1);
sum($adapter2);

JavaScript (пример за Адаптер шаблон на JavaScript)

редактиране
function Search (text, word) {
	var text = text;
	var word = word;
	this.searchWordInText = function () {
		return text;
	};
	this.getWord = function () {
		return word;
	};
};
function SearchAdapter (adaptee) {
	this.searchWordInText = function () {
		return 'Тези думи ' + adaptee.getWord()
			+ ' намерени в текста ' + adaptee.searchWordInText();
	};
};
var search = new Search("текст", "думи")
var searchAdapter = new SearchAdapter(search);
searchAdapter.searchWordInText();

Python (пример за Адаптер шаблон на Python)

редактиране
class GameConsole:
    def create_game_picture(self):
        return 'picture from console'

class Antenna:
    def create_wave_picture(self):
        return 'picture from wave'

class SourceGameConsole(GameConsole):
    def get_picture(self):
        return self.create_game_picture()

class SourceAntenna(Antenna):
    def get_picture(self):
        return self.create_wave_picture()

class TV:
    def __init__(self, source):
        self.source = source
    def show_picture(self):
        return self.source.get_picture()

g = SourceGameConsole()
a = SourceAntenna()
game_tv = TV(g)
cabel_tv = TV(a)
print game_tv.show_picture()
print cabel_tv.show_picture()

C# (пример за Адаптер шаблон на C#)

редактиране
using System;

 namespace Adapter
 {

  class MainApp
  {
    static void Main()
    {
      // Create adapter and place a request
      Target target = new Adapter();
      target.Request();

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

  // "Target"

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

  // "Adapter"

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

    public override void Request()
    {
      // Possibly do some other work
      // and then call SpecificRequest
      adaptee.SpecificRequest();
    }
  }

  // "Adaptee"

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

Източници

редактиране
  1. Freeman, Eric, Freeman, Elisabeth, Kathy, Sierra. Head First Design Patterns. O'Reilly Media, 2004. ISBN 978-0-596-00712-6. OCLC 809772256. с. 244. Посетен на 30 април 2013.