Шаблон адаптера - Adapter pattern

В программной инженерии , то шаблон адаптера является разработка программного обеспечения модель (также известная как обертки , альтернативное именование совместно с рисунком декоратора ) , что позволяет интерфейс существующего класса для использования в качестве другого интерфейса. Он часто используется для того, чтобы существующие классы работали с другими без изменения их исходного кода .

Пример представляет собой адаптер , который преобразует интерфейс из объектной модели документа в качестве XML - документа в виде древовидной структуры , которые могут быть отображены.

Обзор

Шаблон проектирования адаптера - один из двадцати трех хорошо известных шаблонов проектирования « Банда четырех», которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, протестировать. и повторно использовать.

Шаблон проектирования адаптера решает такие проблемы, как:

  • Как можно повторно использовать класс, не имеющий интерфейса, который требуется клиенту?
  • Как могут работать вместе классы с несовместимыми интерфейсами?
  • Как можно предоставить альтернативный интерфейс для класса?

Часто (уже существующий) класс нельзя повторно использовать только потому, что его интерфейс не соответствует интерфейсу, который требуется клиентам.

В шаблоне проектирования адаптера описано, как решать такие проблемы:

  • Определите отдельный adapterкласс, который преобразует (несовместимый) интерфейс класса ( adaptee) в другой интерфейс ( target), который требуется клиентам.
  • Поработайте над тем, adapterчтобы работать (повторно использовать) классы, у которых нет необходимого интерфейса.

Ключевая идея в этом шаблоне - работать через отдельный adapter, адаптирующий интерфейс (уже существующего) класса без его изменения.

Клиенты не знают, работают ли они с targetклассом напрямую или через adapterкласс, у которого нет targetинтерфейса.

См. Также диаграмму классов UML ниже.

Определение

Адаптер позволяет двум несовместимым интерфейсам работать вместе. Это реальное определение адаптера. Интерфейсы могут быть несовместимы, но внутренняя функциональность должна соответствовать потребностям. Шаблон проектирования адаптера позволяет несовместимым классам работать вместе, преобразовывая интерфейс одного класса в интерфейс, ожидаемый клиентами.

использование

Адаптер можно использовать, когда оболочка должна учитывать определенный интерфейс и поддерживать полиморфное поведение. В качестве альтернативы декоратор позволяет добавлять или изменять поведение интерфейса во время выполнения, а фасад используется, когда требуется более простой или более простой интерфейс для базового объекта.

Шаблон Намерение
Адаптер или обертка Преобразует один интерфейс в другой, чтобы он соответствовал ожиданиям клиента.
Декоратор Динамически добавляет ответственности к интерфейсу, упаковывая исходный код
Делегация Поддержка «композиции над наследованием»
Фасад Обеспечивает упрощенный интерфейс

Структура

Диаграмма классов UML

Пример диаграммы классов UML для шаблона проектирования адаптера.

На приведенной выше диаграмме классов UML класс , clientкоторому требуется targetинтерфейс, не может повторно использовать adapteeкласс напрямую, потому что его интерфейс не соответствует targetинтерфейсу. Вместо этого он clientработает через adapterкласс, реализующий targetинтерфейс с точки зрения adaptee:

  • object adapterСпособ реализует targetинтерфейс путем передачи к adapteeобъекту во время выполнения ( adaptee.specificOperation()).
  • В class adapterспособ реализует targetинтерфейс путем наследования от adapteeкласса во время компиляции ( specificOperation()).

Шаблон адаптера объекта

В этом шаблоне адаптера адаптер содержит экземпляр класса, который он обертывает. В этой ситуации адаптер вызывает экземпляр обернутого объекта .

Шаблон объектного адаптера, выраженный в UML
Шаблон объектного адаптера, выраженный в LePUS3

Шаблон адаптера класса

Этот шаблон адаптера использует несколько полиморфных интерфейсов, реализующих или наследующих как ожидаемый, так и уже существующий интерфейс. Обычно ожидаемый интерфейс создается как чистый интерфейсный класс, особенно в таких языках , как Java (до JDK 1.8), которые не поддерживают множественное наследование классов.

Шаблон адаптера класса, выраженный в UML .
Шаблон адаптера класса, выраженный в LePUS3

Еще одна форма шаблона адаптера среды выполнения

Мотивация от решения во время компиляции

Желательно classAпредоставить classBкакие-то данные, допустим, какие-то Stringданные. Решение времени компиляции:

classB.setStringData(classA.getStringData());

Однако предположим, что формат строковых данных должен быть изменен. Решение во время компиляции - использовать наследование:

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

и, возможно, создать правильно "форматирующий" объект во время выполнения с помощью фабричного шаблона .

Решение для адаптера времени выполнения

Решение с использованием «переходников» происходит следующим образом:

  1. Определите промежуточный интерфейс «поставщика» и напишите реализацию этого интерфейса поставщика, которая обертывает источник данных ClassAв этом примере и выводит данные в соответствующем формате:
    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(final String sourceValue) {
            // Manipulate the source string into a format required 
            // by the object needing the source object's data
            return sourceValue.trim();
        }
    }
    
  2. Напишите класс адаптера, который возвращает конкретную реализацию поставщика:
    public class ClassAFormat1Adapter extends Adapter {
        public Object adapt(final Object anObject) {
            return new ClassAFormat1((ClassA) anObject);
        }
    }
    
  3. Зарегистрируйте в adapterглобальном реестре, чтобы adapterможно было искать во время выполнения:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. В коде, если вы хотите передать данные из ClassAв ClassB, напишите:
    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());
    
  5. Преимущество можно увидеть в том, что если требуется передать данные во втором формате, найдите другой адаптер / поставщик:
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
    
  6. И если желательно вывести данные ClassA, скажем, как данные изображения в : Class C
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
    ImageProvider provider = (ImageProvider) adapter.adapt(classA);
    classC.setImage(provider.getImage());
    
  7. Таким образом, использование адаптеров и поставщиков позволяет использовать несколько «мнение» от ClassBи ClassCInto ClassAбез необходимости изменять иерархию классов. В общем, он позволяет использовать механизм для произвольных потоков данных между объектами, который может быть модифицирован в существующую иерархию объектов.

Реализация шаблона адаптера

При реализации шаблона адаптера для ясности можно применить имя класса к реализации поставщика; например ,. Он должен иметь метод конструктора с переменной адаптируемого класса в качестве параметра. Этот параметр будет передан члену экземпляра . Когда вызывается clientMethod, он будет иметь доступ к экземпляру адаптируемого объекта, который позволяет получить доступ к необходимым данным адаптируемого объекта и выполнять операции с этими данными, которые генерируют желаемый результат. [ClassName]To[Interface]AdapterDAOToProviderAdapter[ClassName]To[Interface]Adapter

Джава

interface LightningPhone {
    void recharge();
    void useLightning();
}

interface MicroUsbPhone {
    void recharge();
    void useMicroUsb();
}

class Iphone implements LightningPhone {
    private boolean connector;

    @Override
    public void useLightning() {
        connector = true;
        System.out.println("Lightning connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect Lightning first");
        }
    }
}

class Android implements MicroUsbPhone {
    private boolean connector;

    @Override
    public void useMicroUsb() {
        connector = true;
        System.out.println("MicroUsb connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect MicroUsb first");
        }
    }
}
/* exposing the target interface while wrapping source object */
class LightningToMicroUsbAdapter implements MicroUsbPhone {
    private final LightningPhone lightningPhone;

    public LightningToMicroUsbAdapter(LightningPhone lightningPhone) {
        this.lightningPhone = lightningPhone;
    }

    @Override
    public void useMicroUsb() {
        System.out.println("MicroUsb connected");
        lightningPhone.useLightning();
    }

    @Override
    public void recharge() {
        lightningPhone.recharge();
    }
}

public class AdapterDemo {
    static void rechargeMicroUsbPhone(MicroUsbPhone phone) {
        phone.useMicroUsb();
        phone.recharge();
    }

    static void rechargeLightningPhone(LightningPhone phone) {
        phone.useLightning();
        phone.recharge();
    }

    public static void main(String[] args) {
        Android android = new Android();
        Iphone iPhone = new Iphone();

        System.out.println("Recharging android with MicroUsb");
        rechargeMicroUsbPhone(android);

        System.out.println("Recharging iPhone with Lightning");
        rechargeLightningPhone(iPhone);

        System.out.println("Recharging iPhone with MicroUsb");
        rechargeMicroUsbPhone(new LightningToMicroUsbAdapter (iPhone));
    }
}

Выход

Recharging android with MicroUsb
MicroUsb connected
Recharge started
Recharge finished
Recharging iPhone with Lightning
Lightning connected
Recharge started
Recharge finished
Recharging iPhone with MicroUsb
MicroUsb connected
Lightning connected
Recharge started
Recharge finished

Python

"""
Adapter pattern example.
"""
from abc import ABCMeta, abstractmethod

NOT_IMPLEMENTED = "You should implement this."

RECHARGE = ["Recharge started.", "Recharge finished."]

POWER_ADAPTERS = {"Android": "MicroUSB", "iPhone": "Lightning"}

CONNECTED = "{} connected."
CONNECT_FIRST = "Connect {} first."

class RechargeTemplate:
    __metaclass__ = ABCMeta

    @abstractmethod
    def recharge(self):
        raise NotImplementedError(NOT_IMPLEMENTED)

class FormatIPhone(RechargeTemplate):
    @abstractmethod
    def use_lightning(self):
        raise NotImplementedError(NOT_IMPLEMENTED)

class FormatAndroid(RechargeTemplate):
    @abstractmethod
    def use_micro_usb(self):
        raise NotImplementedError(NOT_IMPLEMENTED)

class IPhone(FormatIPhone):
    __name__ = "iPhone"

    def __init__(self):
        self.connector = False

    def use_lightning(self):
        self.connector = True
        print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))

    def recharge(self):
        if self.connector:
            for state in RECHARGE:
                print(state)
        else:
            print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))

class Android(FormatAndroid):
    __name__ = "Android"

    def __init__(self):
        self.connector = False

    def use_micro_usb(self):
        self.connector = True
        print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))

    def recharge(self):
        if self.connector:
            for state in RECHARGE:
                print(state)
        else:
            print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))

class IPhoneAdapter(FormatAndroid):
    def __init__(self, mobile):
        self.mobile = mobile

    def recharge(self):
        self.mobile.recharge()

    def use_micro_usb(self):
        print(CONNECTED.format(POWER_ADAPTERS["Android"]))
        self.mobile.use_lightning()

class AndroidRecharger:
    def __init__(self):
        self.phone = Android()
        self.phone.use_micro_usb()
        self.phone.recharge()

class IPhoneMicroUSBRecharger:
    def __init__(self):
        self.phone = IPhone()
        self.phone_adapter = IPhoneAdapter(self.phone)
        self.phone_adapter.use_micro_usb()
        self.phone_adapter.recharge()

class IPhoneRecharger:
    def __init__(self):
        self.phone = IPhone()
        self.phone.use_lightning()
        self.phone.recharge()

print("Recharging Android with MicroUSB recharger.")
AndroidRecharger()
print()

print("Recharging iPhone with MicroUSB using adapter pattern.")
IPhoneMicroUSBRecharger()
print()

print("Recharging iPhone with iPhone recharger.")
IPhoneRecharger()

C #

public interface ILightningPhone
{
	void ConnectLightning();
	void Recharge();
}

public interface IUsbPhone
{
	void ConnectUsb();
	void Recharge();
}

public sealed class AndroidPhone : IUsbPhone
{
	private bool isConnected;
	
	public void ConnectUsb()
	{
		this.isConnected = true;
		Console.WriteLine("Android phone connected.");
	}

	public void Recharge()
	{
		if (this.isConnected)
		{
			Console.WriteLine("Android phone recharging.");
		}
		else
		{
			Console.WriteLine("Connect the USB cable first.");
		}
	}
}

public sealed class ApplePhone : ILightningPhone
{
	private bool isConnected;
	
	public void ConnectLightning()
	{
		this.isConnected = true;
		Console.WriteLine("Apple phone connected.");
	}

	public void Recharge()
	{
		if (this.isConnected)
		{
			Console.WriteLine("Apple phone recharging.");
		}
		else
		{
			Console.WriteLine("Connect the Lightning cable first.");
		}
	}
}

public sealed class LightningToUsbAdapter : IUsbPhone
{
	private readonly ILightningPhone lightningPhone;
	
	private bool isConnected;
	
	public LightningToUsbAdapter(ILightningPhone lightningPhone)
	{
		this.lightningPhone = lightningPhone;
		this.lightningPhone.ConnectLightning();
	}
	
	public void ConnectUsb()
	{
		this.isConnected = true;
		Console.WriteLine("Adapter cable connected.");
	}

	public void Recharge()
	{
		if (this.isConnected)
		{
			this.lightningPhone.Recharge();
		}
		else
		{
			Console.WriteLine("Connect the USB cable first.");
		}
	}
}

public void Main()
{
	ILightningPhone applePhone = new ApplePhone();
	IUsbPhone adapterCable = new LightningToUsbAdapter(applePhone);
	adapterCable.ConnectUsb();
	adapterCable.Recharge();
}

Выход:

Apple phone connected.
Adapter cable connected.
Apple phone recharging.

Смотрите также

использованная литература