В разработке программного обеспечения шаблоны структурного проектирования имеют дело с отношениями между объектом и классами, то есть тем, как объект и классы взаимодействуют или выстраивают отношения в соответствии с ситуацией. Структурные шаблоны проектирования упрощают структуру, выявляя взаимосвязи. В этой статье Структурные шаблоны проектирования мы собираемся взглянуть на шаблон проектирования адаптера в современном C ++, который используется для преобразования интерфейса существующего класса в другой интерфейс, который ожидает клиент / пользователь API. . Шаблон проектирования адаптера заставляет классы работать вместе, что иначе было бы невозможно из-за несовместимых интерфейсов.

/! \: Эта статья изначально была опубликована в моем блоге. Если вы заинтересованы в получении моих последних статей, подпишитесь на мою рассылку.

Кстати, если вы не читали другие мои статьи о шаблонах проектирования конструкций, то вот список:

  1. Адаптер
  2. Мост
  3. Составное
  4. Декоратор
  5. Фасад
  6. Легковес
  7. Прокси

Фрагменты кода, которые вы видите в этой серии статей, упрощены, а не сложны. Поэтому вы часто видите, что я не использую такие ключевые слова, как 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 в нашем случае), ассоциация продолжается до тех пор, пока не будет назначен другой набор методов.
  • Что характеризует сменный адаптер, так это то, что он будет иметь конструкторы для каждого из типов, которые он адаптирует. В каждом из них он выполняет назначения делегата (одно или несколько, если есть дополнительные методы для перенаправления).
  • Съемный адаптер дает два основных преимущества:
  1. Вы можете привязать интерфейс (минуя лямбда-функцию в аргументе конструктора), в отличие от объекта, который мы сделали в приведенном выше примере.
  2. Это также помогает, когда у адаптера и адаптера другой номер аргумента.

Преимущества шаблона проектирования адаптера

  1. Принцип открытого-закрытого: одним из преимуществ шаблона адаптера является то, что вам не нужно изменять существующий класс или интерфейс. Вводя новый класс, который действует как адаптер между интерфейсом и классом, вы избегаете любых изменений существующего кода.
  2. Это также ограничивает объем ваших изменений в вашем программном компоненте и позволяет избежать любых изменений и побочных эффектов в других компонентах или приложениях.
  3. По указанным выше двум пунктам, то есть отдельным классом (например, Принцип единой ответственности) для особой функциональности и меньшего количества побочных эффектов, очевидно, что нам действительно требуется меньше обслуживания, обучения и тестирования.
  4. AdapterDesing Pattern также придерживается Принципа инверсии зависимостей, благодаря которому вы можете сохранить двоичную совместимость между несколькими выпусками.

Резюме по часто задаваемым вопросам

Когда использовать шаблон проектирования адаптера?

- Используйте класс Adapter, если вы хотите использовать какой-либо существующий класс, но его интерфейс несовместим с остальной частью вашего кода.
- Если вы хотите повторно использовать несколько существующих подклассов, у которых отсутствуют некоторые общие функции, которые не могут быть добавленным в суперкласс.
- Например, допустим, у вас есть функция, которая принимает объект погоды и печатает температуру в градусах Цельсия. Но теперь вам нужно вывести температуру в градусах Фаренгейта. В этом случае несовместимой ситуации вы можете использовать шаблон проектирования адаптера.

Реальный и практический пример шаблона проектирования адаптера?

- В STL stack, queue и priority_queue - это адаптеры от deque и vector. Когда stack выполняет stack :: push (), базовый вектор выполняет vector::push_back().
- Устройство чтения карт, которое действует как адаптер между картой памяти и ноутбуком.
- Ваш мобильный телефон и ноутбук. Заряды - это своего рода адаптер, который преобразует стандартное напряжение и ток в требуемые для вашего устройства.

В чем разница между шаблоном проектирования моста и адаптера?

- Адаптер обычно используется с существующим приложением, чтобы некоторые несовместимые классы работали вместе.
- Мост обычно разрабатывается заранее, что позволяет вам разрабатывать части приложения независимо друг от друга.

В чем разница между декоратором и шаблоном проектирования адаптера?

- Адаптер преобразует один интерфейс в другой, не добавляя дополнительных функций. \
- Декоратор добавляет новые функции в существующий интерфейс.

В чем разница между шаблоном проектирования прокси и адаптера?

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