Паттерн Адаптер (Adapter) предназначен для преобразования интерфейса одного класса в интерфейс другого. Благодаря реализации данного паттерна мы можем использовать вместе классы с несовместимыми интерфейсами.
Когда необходимо использовать имеющийся класс, но его интерфейс не соответствует потребностям
Когда надо использовать уже существующий класс совместно с другими классами, интерфейсы которых не совместимы
Формальное определение паттерна на UML выглядит следующим образом:
Формальное описание адаптера объектов на C# выглядит таким образом:
class Client { public void Request(Target target) { target.Request(); } } // класс, к которому надо адаптировать другой класс class Target { public virtual void Request() {} } // Адаптер class Adapter : Target { private Adaptee adaptee = new Adaptee(); public override void Request() { adaptee.SpecificRequest(); } } // Адаптируемый класс class Adaptee { public void SpecificRequest() {} }
Target: представляет объекты, которые используются клиентом
Client: использует объекты Target для реализации своих задач
Adaptee: представляет адаптируемый класс, который мы хотели бы использовать у клиента вместо объектов Target
Adapter: собственно адаптер, который позволяет работать с объектами Adaptee как с объектами Target.
То есть клиент ничего не знает об Adaptee, он знает и использует только объекты Target. И благодаря адаптеру мы можем на клиенте использовать объекты Adaptee как Target
Теперь разберем реальный пример. Допустим, у нас есть путешественник, который путешествует на машине. Но в какой-то момент ему приходится передвигаться по пескам пустыни, где он не может ехать на машине. Зато он может использовать для передвижения верблюда. Однако в классе путешественника использование класса верблюда не предусмотрено, поэтому нам надо использовать адаптер:
class Program { static void Main(string[] args) { // путешественник Driver driver = new Driver(); // машина Auto auto = new Auto(); // отправляемся в путешествие driver.Travel(auto); // встретились пески, надо использовать верблюда Camel camel = new Camel(); // используем адаптер ITransport camelTransport = new CamelToTransportAdapter(camel); // продолжаем путь по пескам пустыни driver.Travel(camelTransport); Console.Read(); } } interface ITransport { void Drive(); } // класс машины class Auto : ITransport { public void Drive() { Console.WriteLine("Машина едет по дороге"); } } class Driver { public void Travel(ITransport transport) { transport.Drive(); } } // интерфейс животного interface IAnimal { void Move(); } // класс верблюда class Camel : IAnimal { public void Move() { Console.WriteLine("Верблюд идет по пескам пустыни"); } } // Адаптер от Camel к ITransport class CamelToTransportAdapter : ITransport { Camel camel; public CamelToTransportAdapter(Camel c) { camel = c; } public void Drive() { camel.Move(); } }
И консоль выведет:
Машина едет по дороге Верблюд идет по пескам пустыни
В данном случае в качестве клиента применяется класс Driver, который использует объект ITransport. Адаптируемым является класс верблюда Camel, который нужно использовать в качестве объекта ITransport. И адптером служит класс CamelToTransportAdapter.
Возможно, кому-то покажется надуманной проблема использования адаптеров особенно в данном случае, так как мы могли бы применить
интерфейс ITransport к классу Camel и реализовать его метод Drive(). Однако, в данном случае может случиться дублирование функциональностей:
интерфейс IAnimal имеет метод Move(), реализация которого в классе верблюда могла бы быть похожей на реализацию метода Drive() из интерфейса
ITransport. Кроме того, нередко бывает, что классы спроектированы кем-то другим, и мы никак не можем на них повлиять. Мы только используем их.
В результате чего адаптеры довольно широко распространены в .NET. В частности, многочисленные встроенные классы, которые используются для
подключения к различным системам баз данных, как раз и реализуют паттерн адаптер (например, класс System.Data.SqlClient.SqlDataAdapter
).