Skip to content

ООП Лекция 13. Структурные паттерны.

Vladislav Mansurov edited this page May 10, 2022 · 8 revisions

Структурный паттерны

Структурные паттерны предлагают структурные решения. Это какая-либо декомпозиция классов с использованием схем наследования, включения...

Адаптер (Adapter)

Если одна сущность выполняет несколько ответственностей, надо разделять задачи => проблема дальнейшего развития. В случае если какая-то ответственность меняется, а другая нет, что делать? Надо разделять как в фабричном методе, любой порождающий паттерн надо выделять ответственного за принятия решений. Как быть?

Причины использования

Проблема, с которой мы сталкиваемся - это когда нам надо создавать объекты, и эти объекты в разных местах программы мы используем по-разному. Грубо говоря, один объект выступает в нескольких ролях. Из этого следует:

  • Для каждой роли используется свой интерфейс. В принципе, эти интерфейсы могу пересекаться. Но, если мы будем создавать такой класс, мы получим избыточный интерфейс.
  • Как правило, эти роли завязаны еще под ответственность. Один и тот же объект будет иметь несколько ответственностей, что с точки зрения ООП очень плохо. Каждый объект должен иметь только одну ответственностей.

Идея: У нас один и тот же объект выполняет несколько ролей, физически это один объект. В разных местах мы работаем с ним по-разному, т.е. разные интерфейсы. Выделить простой класс, а посредники будет представлять нужный класс для работы.

  1. Несколько ответственностей плохо, невозможно дальнейшее развитие ответственностей
  2. Объект выполняет несколько ответственностей, то одну ответственность выносим на адаптер, то есть за одну ответственность отвечает сам объект, тем самым не нарушаем принцип ООП, за другую же адаптер.
  3. используем Адаптер, когда нужно подменять сущность, то есть если у нас был программа из каких-то библиотек, подсистем или что-то свое, но нам требуется другой интерфейс как раз чтобы использовать другой класс, то очень удобен адаптер для внедрения через него.
  4. Должны использовать обобщение - базовое понятие, класс абстрактный должен нам предоставлять интерфейс, при этом мы не должны его расширять или сужать.

image

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

  • ConAdapterA - это одна роль объекта
  • ConAdapterB - это другая роль того же самого объекта

и при изменении какой из них, другая не меняется. В разных частях программы по-разному работаем с эти объектом.

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

Примечание: Solution подобный => вырожденный Адаптер

Преимущества:

  • Он позволяет нам класс с любым интерфейсом использовать в нашей программе.
  • Позволяет не создавать нам классы с несколькими ответственностями. Разносим это по другим классам.

Недостатки:

  • Просто не понять без UML.
  • Дублирование кода (Кучу классов), нет мертвых классов.

Пример 1. Адаптер (Adapter).

# include <iostream>
# include <memory>

using namespace std;

class Adapter
{
public:
	virtual ~Adapter() = default;

	virtual void request() = 0;
};

class BaseAdaptee
{
public:
	virtual ~BaseAdaptee() = default;

	virtual void specificRequest() = 0;
};

class ConAdapter : public Adapter
{
private:
	shared_ptr<BaseAdaptee>  adaptee;

public:
	ConAdapter(shared_ptr<BaseAdaptee> ad) : adaptee(ad) {}

	virtual void request() override;
};

class ConAdaptee : public BaseAdaptee
{
public:
	virtual void specificRequest() override { cout << "Method ConAdaptee;" << endl; }
};

#pragma region Methods
void ConAdapter::request()
{
	cout << "Adapter: ";

	if (adaptee)
	{
		adaptee->specificRequest();
	}
	else
	{
		cout << "Empty!" << endl;
	}
}

#pragma endregion


int main()
{
	shared_ptr<BaseAdaptee> adaptee =  make_shared<ConAdaptee>();
	shared_ptr<Adapter> adapter = make_shared<ConAdapter>(adaptee);

	adapter->request();
}

Декоратор (Decorator)

Пусть у нас есть какое-то абстрактное понятие, draw() и есть какая-то реализация, начинаем развивать, т.е. от класса получаем производные чтобы подменить одно на другое. По идем нам надо нарисовать, что и рисуют выше стоящие классы и добавить что-то свое.

image

void C::draw()
{
    B::draw();
    _draw();
}

image

Проблемы:

  • Дублирование кода при добавлении одного и того же. (Речь о конкретных компонентов).
  • Большая иерархия классов, в случае что смешивать в конкретных компонентах

Идея: Добавление выделить в отдельный класс. - самый первый пример с draw(), _draw() выделить отдельно.

Задача Декоратора: дополнить функционал интерфейса сущности, которые существуют, чтобы избежать дублирования кода.

image

Есть класс Component. От него производные классы - какие-то конкретные компоненты: ComponentA и ComponentB. Мы создаем класс Decorator, который обладает таким же интерфейсом, но, кроме всего прочего, он имеет ссылку на базовый класс Component. От него мы уже можем порождать конкретные декораторы - Decorator1 и Decorator2. Decorator имеет ссылку на компонент, и вызывая метод компонента, на который держится ссылка, добавляет свой функционал. Таким образом, мы можем добавить к любому компоненту единый функционал.

Таким образом, мы можем отддекорировать сам декоратор...

Преимущества:

  • Мы получаем крайне гибкую систему, избавляясь от колоссального количества классов. Резко уменьшается иерархия.
  • Декорировать можем во время выполнения работы программы.
  • Избавляемся от дублирования кода. Этот код уходит в конкретный декоратор, не дублируясь.

Примечание: Во время выполнения программы мы можем просто продекорировать. У нас возможно изменение поведения объекта во время выполнения программы.

Недостатки:

  • Долгий вызов виртуальных методов в декораторе
  • Проблемы вычленения декораторов при наличии огромного количества обёрток
  • Нет контроля со стороны компилятора над обёртками (кто-то должен за них отвечать). Примечание: Когда мы сделали сложную обертку, нам, к сожалению, убрать какую-то обёртку будет проблематично. Нам придется заново создавать компонент с обёртками. Мы не можем её вычеркнуть.

Пример 2. Декоратор (Decorator).

# include <iostream>
# include <memory>

using namespace std;

class Component
{
public:
	virtual ~Component() = default;

	virtual void operation() = 0;
};

class ConComponent : public Component
{
public:
	virtual void operation() override { cout << "ConComponent; "; }
};

class Decorator : public Component
{
protected:
	shared_ptr<Component> component;

public:
	Decorator(shared_ptr<Component> comp) : component(comp) {}
};

class ConDecorator : public Decorator
{
public:
	using Decorator::Decorator;

	virtual void operation() override;
};

#pragma region Method
void ConDecorator::operation()
{
	if (component)
	{
		component->operation();

		cout << "ConDecorator; ";
	}

}
#pragma endregion

int main()
{
	shared_ptr<Component> component = make_shared<ConComponent>();
	shared_ptr<Component> decorator1 = make_shared<ConDecorator>(component);

	decorator1->operation();
	cout << endl;

	shared_ptr<Component> decorator2 = make_shared<ConDecorator>(decorator1);

	decorator2->operation();
	cout << endl;
}

Композит или Компоновщик

Мост

Заместить (Proxy)

Фасад

Шаблоны и паттерны поведение

Их очень много, рассмотрим только основную часть

Clone this wiki locally