Паттерн Адаптер (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
).