Адаптер (шаблон проектирования)
Из Википедии, бесплатной энциклопедии
Адаптер | |
---|---|
Adapter | |
| |
Тип | структурный |
Назначение | для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс (приводит интерфейс класса (или нескольких классов) к интерфейсу требуемого вида) |
Применяется в случаях | система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Адаптер применяется если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса. |
Плюсы |
|
Родственные шаблоны | Фасад, Декоратор |
Описан в Design Patterns | Да |
Адаптер (англ. Adapter) — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс. Другими словами — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Основные характеристики[править | править код]
Задача[править | править код]
Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс.
Способ решения[править | править код]
Адаптер предусматривает создание класса-оболочки[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 Circle (radius) { this.getRadius = function () { return radius; }; }; function SquareCircleAdapter (adaptee) { this.getSide = function () { return 2 * adaptee.getRadius(); }; }; const circle = new Circle(3.14); const square = new SquareCircleAdapter(circle); const side = square.getSide();
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 (Адаптер) — назначение, описание, реализация на C++, достоинства и недостатки