В разработке программного обеспечения шаблоны структурного проектирования имеют дело с отношениями между объектом и классами, то есть тем, как объект и классы взаимодействуют или выстраивают отношения в соответствии с ситуацией. Структурные шаблоны проектирования упрощают структуру, выявляя взаимосвязи. В этой статье Структурные шаблоны проектирования мы собираемся взглянуть на шаблон проектирования адаптера в современном C ++, который используется для преобразования интерфейса существующего класса в другой интерфейс, который ожидает клиент / пользователь API. . Шаблон проектирования адаптера заставляет классы работать вместе, что иначе было бы невозможно из-за несовместимых интерфейсов.
/! \: Эта статья изначально была опубликована в моем блоге. Если вы заинтересованы в получении моих последних статей, подпишитесь на мою рассылку.
Кстати, если вы не читали другие мои статьи о шаблонах проектирования конструкций, то вот список:
Фрагменты кода, которые вы видите в этой серии статей, упрощены, а не сложны. Поэтому вы часто видите, что я не использую такие ключевые слова, как override
, final
, public
(при наследовании), просто чтобы сделать код компактным и потребляемым (большую часть времени) на одном стандартном размере экрана. Я также предпочитаю struct
вместо class
, просто чтобы сохранить строку, не записывая иногда public:
, а также пропускаю виртуальный деструктор, конструктор, конструктор копирования, префикс std::
, намеренно удаляя динамическую память. Я также считаю себя прагматичным человеком, который хочет передать идею максимально простым способом, а не стандартным способом или с использованием жаргонов.
Примечание.
- Если вы случайно наткнулись здесь, то я бы посоветовал вам пройти через Что такое шаблон проектирования? во-первых, даже если это банально. Я считаю, что это побудит вас больше исследовать эту тему.
- Весь этот код, с которым вы сталкиваетесь в этой серии статей, скомпилирован с использованием C ++ 20 (хотя в большинстве случаев я использовал функции Modern C ++ вплоть до C ++ 17). Поэтому, если у вас нет доступа к последней версии компилятора, вы можете использовать https://wandbox.org/, в котором также есть предустановленная библиотека ускорения.
Намерение
Чтобы получить желаемый интерфейс от имеющегося у вас интерфейса.
- Адаптер позволяет двум несовместимым классам работать вместе, преобразовывая интерфейс одного класса в интерфейс, ожидаемый клиентом / пользователем API, без их изменения. По сути, добавление промежуточного класса, то есть адаптера.
- Если вы оказались в ситуации использования адаптера, возможно, вы работаете над совместимостью между библиотеками, модулями, надстройками и т. Д. Если нет, то у вас могут возникнуть серьезные проблемы с дизайном, потому что, если вы следовали Принципу инверсии зависимостей на ранней стадии проектирования . Использование шаблона проектирования адаптера не будет.
Примеры шаблонов проектирования адаптера на C ++
- Реализовать шаблон проектирования адаптера легко, просто определите API, который у вас есть, и API, который вам нужен. Создайте компонент, который объединяет (имеет ссылку на…) адаптируемого объекта.
Классический адаптер
struct Point { int32_t m_x; virtual void draw(){ cout<<"Point\n"; } };
struct Point2D : Point { int32_t m_y; void draw(){ cout<<"Point2D\n"; } };
void draw_point(Point &p) { p.draw(); }
struct Line { Point2D m_start; Point2D m_end; void draw(){ cout<<"Line\n"; } };
struct LineAdapter : Point { Line& m_line; LineAdapter(Line &line) : m_line(line) {} void draw(){ m_line.draw(); } };
int main() { Line l; LineAdapter lineAdapter(l); draw_point(lineAdapter); return EXIT_SUCCESS; }
- Вы также можете создать универсальный адаптер, используя шаблон C ++ следующим образом:
template<class T>
struct GenericLineAdapter : Point {
T& m_line;
GenericLineAdapter(T &line) : m_line(line) {}
void draw(){ m_line.draw(); }
};
- Надеемся, что полезность универсального подхода станет более очевидной, если учесть, что, когда вам нужно сделать другие вещи
Point
-подобными, неуниверсальный подход быстро становится очень избыточным.
Шаблон проектирования подключаемого адаптера с использованием современного C ++
- Адаптер должен поддерживать адаптеров (которые не связаны между собой и имеют разные интерфейсы), используя тот же старый целевой интерфейс, который известен клиенту / пользователю API. В приведенном ниже примере это свойство удовлетворяется за счет использования лямбда-функции и функционального заголовка C ++ 11.
/* Legacy code -------------------------------------------------------------- */ struct Beverage { virtual void getBeverage() = 0; };
struct CoffeeMaker : Beverage { void Brew() { cout << "brewing coffee" << endl;} void getBeverage() { Brew(); } };
void make_drink(Beverage &drink){ drink.getBeverage(); // Interface already shipped & known to client } /* --------------------------------------------------------------------------- */
struct JuiceMaker { // Introduced later on void Squeeze() { cout << "making Juice" << endl; } };
struct Adapter : Beverage { // Making things compatible function<void()> m_request;
Adapter(CoffeeMaker* cm) { m_request = [cm] ( ) { cm->Brew(); }; } Adapter(JuiceMaker* jm) { m_request = [jm] ( ) { jm->Squeeze(); }; }
void getBeverage() { m_request(); } };
int main() { Adapter adp1(new CoffeeMaker()); make_drink(adp1);
Adapter adp2(new JuiceMaker()); make_drink(adp2); return EXIT_SUCCESS; }
- Подключаемый адаптер определяет, какой объект подключается в данный момент. После того, как объект был подключен и его методы были назначены объектам-делегатам (то есть
m_request
в нашем случае), ассоциация продолжается до тех пор, пока не будет назначен другой набор методов. - Что характеризует сменный адаптер, так это то, что он будет иметь конструкторы для каждого из типов, которые он адаптирует. В каждом из них он выполняет назначения делегата (одно или несколько, если есть дополнительные методы для перенаправления).
- Съемный адаптер дает два основных преимущества:
- Вы можете привязать интерфейс (минуя лямбда-функцию в аргументе конструктора), в отличие от объекта, который мы сделали в приведенном выше примере.
- Это также помогает, когда у адаптера и адаптера другой номер аргумента.
Преимущества шаблона проектирования адаптера
- Принцип открытого-закрытого: одним из преимуществ шаблона адаптера является то, что вам не нужно изменять существующий класс или интерфейс. Вводя новый класс, который действует как адаптер между интерфейсом и классом, вы избегаете любых изменений существующего кода.
- Это также ограничивает объем ваших изменений в вашем программном компоненте и позволяет избежать любых изменений и побочных эффектов в других компонентах или приложениях.
- По указанным выше двум пунктам, то есть отдельным классом (например, Принцип единой ответственности) для особой функциональности и меньшего количества побочных эффектов, очевидно, что нам действительно требуется меньше обслуживания, обучения и тестирования.
- AdapterDesing Pattern также придерживается Принципа инверсии зависимостей, благодаря которому вы можете сохранить двоичную совместимость между несколькими выпусками.
Резюме по часто задаваемым вопросам
Когда использовать шаблон проектирования адаптера?
- Используйте класс Adapter, если вы хотите использовать какой-либо существующий класс, но его интерфейс несовместим с остальной частью вашего кода.
- Если вы хотите повторно использовать несколько существующих подклассов, у которых отсутствуют некоторые общие функции, которые не могут быть добавленным в суперкласс.
- Например, допустим, у вас есть функция, которая принимает объект погоды и печатает температуру в градусах Цельсия. Но теперь вам нужно вывести температуру в градусах Фаренгейта. В этом случае несовместимой ситуации вы можете использовать шаблон проектирования адаптера.
Реальный и практический пример шаблона проектирования адаптера?
- В STL stack, queue и priority_queue - это адаптеры от deque и vector. Когда stack выполняет stack :: push (), базовый вектор выполняет vector::push_back()
.
- Устройство чтения карт, которое действует как адаптер между картой памяти и ноутбуком.
- Ваш мобильный телефон и ноутбук. Заряды - это своего рода адаптер, который преобразует стандартное напряжение и ток в требуемые для вашего устройства.
В чем разница между шаблоном проектирования моста и адаптера?
- Адаптер обычно используется с существующим приложением, чтобы некоторые несовместимые классы работали вместе.
- Мост обычно разрабатывается заранее, что позволяет вам разрабатывать части приложения независимо друг от друга.
В чем разница между декоратором и шаблоном проектирования адаптера?
- Адаптер преобразует один интерфейс в другой, не добавляя дополнительных функций. \
- Декоратор добавляет новые функции в существующий интерфейс.
В чем разница между шаблоном проектирования прокси и адаптера?
- Шаблон проектирования адаптера переводит интерфейс для одного класса в совместимый, но другой интерфейс.
- Прокси предоставляет такой же, но простой интерфейс или некоторое время действует как единственная оболочка.