Адаптер (шаблон проектирования)
Адаптер (англ. Adapter) — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс. Другими словами — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Адаптер | |
---|---|
Adapter | |
| |
Тип | структурный |
Назначение | для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс (приводит интерфейс класса (или нескольких классов) к интерфейсу требуемого вида) |
Применяется в случаях | система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Адаптер применяется если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса. |
Плюсы |
|
Родственные шаблоны | Фасад, Декоратор |
Описан в Design Patterns | Да |
Основные характеристики
Задача
Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс.
Способ решения
Адаптер предусматривает создание класса-оболочки[1] с требуемым интерфейсом.
Участники
Класс Adapter
приводит интерфейс класса Adaptee
в соответствие с интерфейсом класса Target
(который реализуется классом Adapter
). Это позволяет объекту Client
использовать объект Adaptee
(посредством адаптера Adapter
) так, словно он является экземпляром класса Target
.
Таким образом Client
обращается к интерфейсу Target
, реализованному классом Adapter
, который перенаправляет обращение к Adaptee
.
Следствия
Шаблон Адаптер позволяет включать уже существующие объекты в новые объектные структуры, независимо от различий в их интерфейсах.
Замечания и комментарии
Шаблон Адаптер позволяет в процессе проектирования не принимать во внимание возможные различия в интерфейсах уже существующих классов. Если есть класс, обладающий требуемыми методами и свойствами (по крайней мере, концептуально), то при необходимости всегда можно воспользоваться шаблоном Адаптер для приведения его интерфейса к нужному виду.
Близким Адаптеру является шаблон Фасад, не всегда можно отличить один от другого[2].
Применение шаблона
Типичным примером использования шаблона Адаптер можно назвать создание классов, приводящих к единому интерфейсу функции языка PHP обеспечивающие доступ к различным СУБД[3].
Вариант решения данной проблемы с использованием шаблона Адаптер показан на рисунке.
Реализация
Включение уже существующего класса в другой класс. Интерфейс включающего класса приводится в соответствие с новыми требованиями, а вызовы его методов преобразуются в вызовы методов включённого класса.
Шаги реализации
- Убедитесь, что у вас есть два класса с несовместимыми интерфейсами:
- полезный сервис — служебный класс, который вы не можете изменять (он либо сторонний, либо от него зависит другой код);
- один или несколько клиентов — существующих классов приложения, несовместимых с сервисом из-за неудобного или несовпадающего интерфейса.
- Опишите клиентский интерфейс, через который классы приложения смогли бы использовать класс сервиса.
- Создайте класс адаптера, реализовав этот интерфейс.
- Поместите в адаптер поле, которое будет хранить ссылку на объект сервиса. Обычно это поле заполняют объектом, переданным в конструктор адаптера. В случае простой адаптации этот объект можно передавать через параметры методов адаптера.
- Реализуйте все методы клиентского интерфейса в адаптере. Адаптер должен делегировать основную работу сервису.
- Приложение должно использовать адаптер только через клиентский интерфейс. Это позволит легко изменять и добавлять адаптеры в будущем.
Ruby
module AdapterPattern
# Allows Client to use Adaptees with incompatible interfaces via Adapters with interface Target
# Adaptee
class Twitter
def twit
puts 'Twit was published'
end
end
# Adaptee
class Facebook
def post
puts 'Facebook post was published'
end
end
# Target
module WebServiceInterface
def send_message
raise NotImplementedError
end
end
# Adapter
class TwitterAdapter
include WebServiceInterface
def initialize
@webservice = Twitter.new
end
def send_message
@webservice.twit
end
end
# Adapter
class FacebookAdapter
include WebServiceInterface
def initialize
@webservice = Facebook.new
end
def send_message
@webservice.post
end
end
# Client
class Message
attr_accessor :webservice
def send
@webservice.send_message
end
end
def self.run
puts '=> Adapter'
message = Message.new
message.webservice = TwitterAdapter.new
message.send
message.webservice = FacebookAdapter.new
message.send
puts ''
end
end
AdapterPattern.run
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();
}
}
Scala
package object adapter {
object Battlefield {
protected var redTroops : Array[Troop] = Array()
protected var blueTroops : Array[Troop] = Array()
def addTroop(troop : Troop) : Unit = {
if (troop.side == "red") {
redTroops :+= troop
} else if (troop.side == "blue") {
blueTroops :+= troop
} else {
throw new Exception(s"Invalid side ${troop.side} for troop ${troop.name}")
}
}
def getClosestEnemyTroop(side : String): Troop = {
if (side == "red") {
getTroop(blueTroops)
} else {
getTroop(redTroops)
}
}
private def getTroop(troops: Array[Troop]): Troop = {
if (troops.length == 0) {
throw new Exception("No available troops")
}
troops(0)
}
}
class Troop (
val side : String,
val name : String,
val closeWeapon : String,
val distanceWeapon : String
) {
def move(direction: String, distance: Int): Unit = {
println(s"Troop $name moves $direction on $distance yards")
}
def attack(enemyTroop: Troop, attackType: String) : Unit = {
val weapon = attackType match {
case "distance" => distanceWeapon
case "close" => closeWeapon
case _ => throw new Exception(s"Invalid attack type $attackType for troop $name")
}
println(s"Troop $name attacks enemy troop ${enemyTroop.name} with their ${weapon}s")
}
}
trait LanceKnightTroopTrait {
def moveForward(distance : Int) : Unit
def attackClosest(attackType : String) : Unit
}
class LanceKnightTroop(
override val side : String,
override val name : String,
override val closeWeapon: String,
override val distanceWeapon: String
)
extends Troop(side, name, closeWeapon, distanceWeapon)
with LanceKnightTroopTrait {
override def moveForward(distance: Int): Unit = {
move("forward", distance)
}
override def attackClosest(attackType: String): Unit = {
attack(Battlefield.getClosestEnemyTroop(side), attackType)
}
}
object AdapterTest extends AbstractTest {
override def run(): Unit = {
val troop = new Troop("blue", "Archers", "sword", "longbow")
val lanceKnightTroop = new LanceKnightTroop("red", "Lance Knights", "pike", "crossbow")
Battlefield.addTroop(troop)
Battlefield.addTroop(lanceKnightTroop)
println("Output:")
lanceKnightTroop.moveForward(300)
lanceKnightTroop.attackClosest("close")
}
}
}
// Output:
// Troop Lance Knights moves forward on 300 yards
// Troop Lance Knights attacks enemy troop Archers with their pikes
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();
/**
* Везде в коде мы не используем классы напрямую а через интерфейс
* этой функции нет разницы какой класс мы используем, так как мы опираемся на интерфейс
*
* @param IAdapter $adapter
*/
function sum(IAdapter $adapter) {
echo $adapter->sum(2, 2);
}
sum($adapter1);
sum($adapter2);
PHP5.4
<?php
class SomeClass
{
public function someSum($a, $b)
{
return $a + $b;
}
}
class AnotherClass
{
public function anotherSum($a, $b)
{
return $a + $b;
}
}
trait TAdaptee
{
public function sum(int $a, int $b)
{
$method = $this->method;
return $this->$method($a, $b);
}
}
class SomeAdaptee extends SomeClass
{
use TAdaptee;
private $method = 'someSum';
}
class AnotherAdaptee extends AnotherClass
{
use TAdaptee;
private $method = 'anotherSum';
}
$some = new SomeAdaptee;
$another = new AnotherAdaptee;
$some->sum(2,2);
$another->sum(5,2);
PHP5.4 Compact
<?php
trait TAdaptee
{
public function sum(int $a, int $b)
{
$method = $this->method;
return $this->$method($a, $b);
}
}
class SomeClass
{
use TAdaptee;
private $method = 'someSum';
public function someSum($a, $b)
{
return $a + $b;
}
}
class AnotherClass
{
use TAdaptee;
private $method = 'anotherSum';
public function anotherSum($a, $b)
{
return $a + $b;
}
}
$some = new SomeClass;
$another = new AnotherClass;
$some->sum(2,2);
$another->sum(5,2);
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
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# - композиция
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()");
}
}
}
С# - наследование
using System;
namespace Adapter
{
class MainApp
{
static void Main()
{
// Create adapter and place a request
Adapter adapter = new Adapter();
adapter.Request();
// Wait for user
Console.Read();
}
}
// "Target"
interface ITarget
{
public void Request();
}
// You can use abstract class
// "Adapter"
class Adapter : Adaptee, ITarget
{
public void Request()
{
// Possibly do some other work
// and then call SpecificRequest
SpecificRequest();
}
}
// "Adaptee"
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("Called SpecificRequest()");
}
}
}
Delphi
program Adapter;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
(*Client use interface of class TTarget realized as TAdapter*)
(*TAdapter redirects the call to TAdaptee*)
type
TTarget = class
function Request: string; virtual;
end;
TAdaptee = class
function SpecificRequest: string;
end;
TAdapter = class(TTarget)
fAdaptee: TAdaptee;
function Request: string; override;
constructor Create;
end;
{ TTarget }
function TTarget.Request: string;
begin
Result:= 'Called Target Request()';
end;
{ TAdaptee }
function TAdaptee.SpecificRequest: string;
begin
Result:= 'Called SpecificRequest()';
end;
{ TAdapter }
constructor TAdapter.Create;
begin
fAdaptee:= TAdaptee.Create;
end;
function TAdapter.Request: string;
begin
(*Possibly do some other work and when call SpecificRequest*)
Result:= fAdaptee.SpecificRequest;
end;
var target: TTarget;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
(*create adapter and place a request*)
target:= TAdapter.Create;
WriteLn(target.Request);
WriteLn(#13#10+'Press any key to continue...');
ReadLn;
target.Free;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Примечания
- Близость значений терминов оболочка и обёртка (англ. wrapper — используется как синоним декоратора) иногда приводит к путанице и Адаптер определяют как синоним шаблона Декоратор, в то время как это два разных шаблона и последний решает иную задачу, а именно: подключение дополнительных обязательств к объекту.
- Разница состоит в том, что шаблон Фасад предназначен для упрощения интерфейса, тогда как шаблон Адаптер предназначен для приведения различных существующих интерфейсов к единому требуемому виду.
- В устаревших версиях языка PHP доступ к СУБД реализован в виде набора функций, для каждой СУБД они имеют различные наименования и, иногда, различный набор используемых параметров, что приводит к значительным проблемам при переходе с одной СУБД на другую, если такой переход заранее не обеспечен использованием шаблона Адаптер.
Литература
- Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М.: «Вильямс», 2002. — С. 288. — ISBN 0-201-71594-5.
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns: Elements of Reusable Object-Oriented Software. — СПб.: «Питер», 2007. — С. 366. — ISBN 978-5-469-01136-1. (также ISBN 5-272-00355-1)
- Эрик Фримен, Элизабет Фримен. Паттерны проектирования = Head First Design Patterns. — СПб.: Питер, 2011. — 656 с. — ISBN 978-5-459-00435-9.
Ссылки
- Паттерн Adapter (Адаптер) — назначение, описание, реализация на С++, достоинства и недостатки