Вы находитесь на странице: 1из 18

1. Классификация языков программирования. Парадигмы программирования.

2. Объектно-ориентированный подход. Основные понятия.


3. Библиотека STL.
4. Принципы наследования и композиции при конструировании новых классов.
Преимущества и недостатки наследования по сравнению с композицией.
5. Инициализация данных объекта класса (Конструктор по умолчанию, конструктор с
параметрами). Назначение деструктора. Способы передачи объекта в функцию,
конструктор копирования.
6. Перегрузка операторов.
7. Статические переменные и методы класса. Константные методы. Перегруженные
методы.
8. Виртуальные функции, понятие полиморфизма. Чистые виртуальные функции и
абстрактные классы. Виртуальный деструктор.
9. Множественное наследование.
10. Понятие обобщенного программирования. Шаблоны классов. Шаблоны
функций.
11. Понятие исключения. Механизм генерации и обработки исключений.
12. Понятие паттернов проектирования. Паттерн Фабрика.
13. Понятие паттернов проектирования. Паттерн Декоратор.
14. Понятие паттернов проектирования. Паттерн Адаптер.
15. Понятие паттернов проектирования. Паттерн Фасад.
16. Понятие паттернов проектирования. Паттерн Шаблонный метод.

4.Принципы наследования и композиции при


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

Самый большой недостаток наследования заключается в том, что оно легко


нарушает один из базовых принципов ООП — инкапсуляцию. Это связано с тем,
что фактически родительский класс определяет поведение дочернего класса, а
это значит, что даже незначительное изменение в родительском классе может
сильно сказаться на поведении класса-потомка. Плюс ко всему, повторное
использование кода сильно затрудняется, если реализация родителя содержит
аспекты несовместимые с задачами потомка. Как правило, чтобы выйти из
такой ситуации необходимо провести глубокий рефакторинг кода, а это не
всегда возможно.
На практике, чтобы избежать зависимости от реализации, предпочтительнее
наследовать абстрактные классы (или интерфейсы). Тогда класс-потомок может
сам определить каким образом реализовать свою работу.

В противовес наследованию, часто используется другой метод — композиция. 

Композиция объектов строится динамически за счет связывания одного объекта


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

Итак, какие же преимущества есть у композиции перед наследованием?

1. Нет конфликта имён, возможного при наследовании.(с общим интерфейсом)

2. Возможность смены агрегируемого объекта в runtime.(-||-)

3. Полная замена агрегируемого объекта в классах, производных от класса,


включающего агрегируемый объект. (виртуальный метод)

Если рассматривать, например, C#, не поддерживающий множественное


наследование, но позволяющий наследовать от множества интерфейсов и
создавать методы расширения для этих интерфейсов, то можно выделить ещё
два плюса (речь в данном случае может идти только о поведениях
(алгоритмах) в рамках паттерна «Стратегия»):

4. Агрегируемое поведение (алгоритм) может включать в себя другие объекты.


Что в частности позволяет переиспользовать посредством агрегации другое
поведение.

5. При агрегации есть возможность скрыть определённую часть реализации, а


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

Но как же минусы? Неужели их нет?

1. Итак, если нам необходима возможность смены поведения извне, то


композиция, по сравнению с наследованием, имеет принципиально другой тип
отношений между объектом поведения и объектом, его использующим. Если
при наследовании от абстрактного поведения мы имеем отношение 1:1, то при
агрегации и возможности установки поведения извне мы получаем отношение
1:many. Т.е. один и тот же объект поведения может использоваться
несколькими объектами-владельцами. Это порождает проблемы с общим для
нескольких таких объектов-владельцев состоянием поведения.

Разрешить эту ситуацию можно, запретив установку поведения извне или


доверив его, например, generic-методу:

void SetBehavior<TBehavior>()

запретив тем самым создание поведения кем-либо, кроме объекта-владельца.


Однако мы не можем запретить использовать поведение «где-то ещё». В
языках без сборщика мусора (GC) это порождает понятные проблемы. Конечно,
в таких языках можно неправомерно обратиться по ссылке и на сам объект-
владелец, но, раздавая отделённые объекты поведения направо и налево, мы
получаем в разы больше шансов получить exception.

2. Агрегация (и это, пожалуй, главный нюанс) отличается от наследования в


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

В таком случае, нам придётся или передавать в такой код нетипизированный


объект (как object или void*), или создавать дополнительный интерфейс для
объекта-владельца (некий IBehaviorOwner), или хранить в поведении
циклическую ссылку на объект-владелец. Понятно, что каждый из этих
вариантов имеет свои минусы и ещё больше усложняет код. Более того,
различные типы поведений могут зависеть друг от друга (и в это вполне
допустимо, особенно если они находятся в некоем закрытом самодостаточном
модуле).

3. Ну и последний минус — это конечно же производительность. Если объектов-


владельцев достаточно много, то создание и уничтожение вместо одного
объекта двух или более может не остаться незамеченным.

Получается, что утверждение «композиция всегда лучше наследования» в ряде


случаев спорно и не должно являться догмой. Особенно это касается языков,
позволяющих множественное наследование и не имеющих GC. Если в какой-
либо ситуации перечисленные выше плюсы не важны, и заранее известно, что
при работе с определёнными типами у вас не будет возможности их
использовать, стоит всё-таки рассмотреть вариант наследования.
7. Статические переменные и методы класса.
Константные методы. Перегруженные методы.
Если член (переменная или метод) класса объявляется как статический, то это
значит, существует только одна копия этого члена, общая для всех объектов класса.
Статический член принадлежит непосредственно классу, поэтому может использоваться
в том числе без создания объектов.
Статические ПЕРЕМЕННЫЕ класса должны быть определены глобально, вне
класса, с использованием оператора области видимости (чтобы было понятно, к какому
классу принадлежит переменная). Соответственно, доступны они тоже глобально при
обращении к ним через оператор области видимости (::). Если статическая переменная
объявлена, но не определена, то она по умолчанию инициализируется нулём.
Пример использования статической переменной:

#include <iostream>
using namespace std;

class a {
   public:
   static int var; // объявление статической переменной типа int
};
int a::var=5; // определение переменной в глобальном пространстве имён
int main() {
a obj;
cout<<a::var; // вывод значения переменной, обращаемся к ней через класс
// выведется 5
cout<<obj.var; // вывод значения переменной, обращаемся к ней через объект класса
// выведется 5
return 0;
}

Статические МЕТОДЫ класса могут напрямую обращаться только к другим


статическим методам или статическим переменным. (Из-за того, что они не привязаны к
объекту, у них нет указателя this и они не могут напрямую обращаться к нестатическим
методам и переменным, которые принадлежат объекту класса.) Также статические
методы не могут быть виртуальными или объявляться с модификатором const.
Пример использования статического метода:
#include <iostream>
using namespace std;
class b
{
public:
static int x; //  объявляем приватную статическую переменную
static void printvar() { cout<<x; } // объявляем статический метод
};
int b::x = 3; // определение статической переменной
int main()
{
b::printvar(); // выводим значение переменной с помощью статического метода
//выведется 3
}
Константные методы (объявляются с модификатором const) не могут изменять
переменные класса, поэтому их можно использовать, чтобы уберечься от
нежелательного изменения переменных в будущем. Но конструктор и деструктор нельзя
сделать константными. Также константные методы необходимы для работы с
константными объектами класса.
Пример использования константного метода.

#include <iostream>
using namespace std;

class A {
public:
 int x;
 A(int a) {
   x = a;
 }
 void f() {
   cout << x;
 }
 void f() const {              // перегружаем функцию f для возможности работы с
 cout << x;                     //константными объектами
 }
};
int main() {
 A a1(2);
 a1.f();     // выполнение f для объекта a1
 A const a2(4);
 a2.f();     // выполнение f для константного объекта а2
 return 0;
}

Перегрузка методов класса происходит так же, как и перегрузка других методов.
Исполльзуются перегруженные методы для возможности работы с различными типами
данных.
Пример:

#include <iostream>
using namespace std;

class A {
public:
static int subtract(int a, int b) // версия целочисленного типа
{
return a - b;
}

static double subtract(double a, double b) // версия типа с плавающей запятой


{
return a - b;
}
};
int main() {
cout<<A::subtract(6, 4);
cout<<A::subtract(6.5, 4.25);
 return 0;
}
8. Виртуальные функции, понятие
полиморфизма. Чистые виртуальные функции и
абстрактные классы. Виртуальный деструктор.
Виртуальные функции — специальный вид функций-членов класса.
Виртуальная функция отличается об обычной функции тем, что для обычной
функции связывание вызова функции с ее определением осуществляется на
этапе компиляции. Для виртуальных функций это происходит во время
выполнения программы.
Полиморфизм — это способность обьекта использовать методы производного класса, который не
существует на момент создания базового.
Для объявления виртуальной функции используется ключевое слово virtual.
Функция-член класса может быть объявлена как виртуальная, если
 класс, содержащий виртуальную функцию, базовый в иерархии
порождения;
 реализация функции зависит от класса и будет различной в каждом
порожденном классе.
Виртуальная функция — это функция, которая определяется в базовом
классе, а любой порожденный класс может ее переопределить. Виртуальная
функция вызывается только через указатель или ссылку на базовый класс.
 
 
Определение того, какой экземпляр виртуальной функции вызывается по
выражению вызова функции, зависит от класса объекта, адресуемого
указателем или ссылкой, и осуществляется во время выполнения программы.
Этот механизм называется динамическим (поздним) связыванием или
разрешением типов во время выполнения.
Часто в самом базовом классе сами виртуальные функции фиктивны и имеют
пустое тело. Определенное значение им придается лишь в порожденных
классах. Такие функции называются чистыми виртуальными функциями.
 
Чистая виртуальная функция — это метод класса, тело которого не
определено.
 
В базовом классе такая функция записывается следующим образом:
virtual void func() = 0;
Чистая виртуальные функции используются для того, чтобы отложить решение
задачи о реализации функции на более поздний срок. В терминологии ООП это
называется отсроченным методом.
Класс, имеющий по крайней мере одну чистую виртуальную функцию,
называется абстрактным базовым классом. Для иерархии типа полезно
иметь абстрактный базовый класс. Он содержит общие свойства иерархии типа,
но каждый порожденный класс реализует эти свойства по-своему.
Cуществует правило - если базовый класс предназначен для полиморфного использования, то его
деструктор должен объявляться виртуальным. Для реализации механизма виртуальных
функций каждый объект класса хранит указатель на таблицу виртуальных функций vptr, что
увеличивает его общий размер. Обычно, при объявлении виртуального деструктора такой класс
уже имеет виртуальные функции, и увеличения размера соответствующего объекта не происходит.
Если же базовый класс не предназначен для полиморфного использования (не содержит
виртуальных функций), то его деструктор не должен объявляться виртуальным.
9. Множественное наследование.

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

class имя_порожденного_класса: базовый класс А, базовый класс Б


{
...
};
Поскольку класс Z наследует оба класса X и Y, то он имеет доступ к публичным и
защищенным членам обоих классов X и Y.

При создании объектов-представителей производного класса, порядок расположения


непосредственных базовых классов в списке баз определяет очерёдность вызова
конструкторов умолчания. Этот порядок влияет и на очерёдность вызова деструкторов
при уничтожении этих объектов.

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

 
 

10.Понятие обобщенного программирования.


Шаблоны классов. Шаблоны функций.
Обобщенное программирование – парадигма программирования,
заключающаяся в таком описании данных и алгоритмов, которое можно применять к
различным типам данных и структурам данных.
Шаблоны (англ. template) — средство языка C++, предназначенное для
кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например
типам данных, размерам буферов, значениям по умолчанию).

В С++ шаблоны функций — это функции, которые служат образцом для


создания других подобных функций. Главная идея – создание функций без указания
точного типа(ов) некоторых или всех переменных.
Пример:
#include <iostream>
using namespace std;
template <typename T> // объявление параметра шаблона функции
T maximum(T a, T b)   // объявление шаблона функции
{
return (a > b) ? a : b; // функция нахождения большего из двух
}
int main(){
cout << maximum (5, 6); // вызов функции для переменных типа int
cout << maximum (3.2, 4.7); // вызов функции для переменных типа double
return 0;
}

Создание шаблона класса аналогично созданию шаблона функции.


Пример:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class MyClass {
public:
   T sum(T x, T y) {
   return x + y;
   }
};
int main() {
   MyClass<double> od; //объявляем объект с полями типа double
   MyClass<string> os; //объявляем объект с полями типа string
   cout << od.sum(2.5, 5.75);    // 2.5+5.75=8.25
   //вывод 8.25
   cout << os.sum("Hello ", "world!"); //сложили строки
   //вывод Hello world
}

11.Понятие исключения. Механизм генерации и


обработки исключений.
Механизм обработки исключений является нелокальной структурой, основанной на
«раскручивании» стека, и его можно рассматривать в качестве альтернативы механизму
возврата из функции. Поэтому вполне возможно использовать исключения, которые не имеют
никакого отношения к ошибкам. Однако изначальной целью механизма обработки ошибок
является обработка ошибок и обеспечение устойчивости при возникновении ошибок.

Управление С++ - механизмом обработки исключений зиждется на трех ключевых словах:try,


catch и throw. В общих чертах их работа состоит в следующем: программные инструкции,
которые вы считаете нужным проконтролировать, помещаются в try-блок. Если исключение
возникает в этом блоке, оно дает знать о себе выбросом определенного рода информации (с
помощью throw). Это выброшено исключение может быть перехвачено программным путем с
помощью catch-блока и обработано соответствующим образом.
Код, в котором возможно возникновение исключительной ситуации, должен выполняется в
рамках try-блока. (Любая функция, вызываемая в рамках этого блока, так же подвергается
контролю.) Исключения, которые могут быть «выброшены» контролируемым кодом,
перехватываются catch-инструкцией, непосредственно следующей за try-блоком, в котором
фиксируются выбросы этих исключений. Общий формат try- и catch- блоков выглядит так:

try {
// try-блок, подлежащий проверке на наличие ошибок
}
catch (type1 arg) {
// catch-блок (обработчик исключения типа type1);
}
catch (type2 arg) {
// catch-блок (обработчик исключения типа type2);
// . . .
catch (typeN arg){
//catch-блок (обработчик исключения типа typeN);
}
try-блок может включать в себя несколько инструкций некоторой функции или охватывать
весь код функции main().

После выброса исключение перехватывается соответствующей catch-инструкцией, которая


выполняет его обработку. С одним try-блоком может быть связано несколько catch-функций.
Какая из них будет выполняться, определено типом исключения, т.е. будет выполнена та
catch-инструкция, тип исключения которой совпадает с типом сгенерированного исключения.
После перехвата исключения параметр arg примет его значения. Таким путем могут
перехватываться данные любого типа, включая объекты классов, созданных программистом.

Общий формат инструкции throw выглядит так:

throw exception;

Здесь с помощью элемента exception задается исключение, сгенерированное функцией throw.


Если это исключение подлежит перехвату, то инструкция throw должна быть выполнена или в
самом блоке try, либо в любой вызываемой из него функции.

Если в программе случится «выброс» исключения, для которого не предусмотрена


соответствующая catch-инструкция, произойдет аварийное завершение программы.

Пример обработки исключения:

#include<iostream>
using namespace std;
int main()
{
cout<< “НАЧАЛО\n”;
try{
cout << “В try-блоке\n”;
throw 99; //генерирование ошибки
cout<< “Эта инструкция не будет выполнена.”;
}
catch (int i) {
cout<<”Перехват исключения. Его значение равно“;
cout << i << “\n”;
}
cout << “КОНЕЦ”;
return 0;
}
Результат:

НАЧАЛО

В try-блоке

Перехват исключения. Его значение равно: 99

КОНЕЦ

Здесь try-блок содержит три инструкции, инструкция catch (int i)предназначена для обработки
исключений целочисленного типа. В этом try-блоке выполняется только две из трёх
инструкции: cout и throw. После генерирования исключения управление предается catch-
выражению, при этом выполнение try-блока прекращается.

catch-инструкция не вызывается, а просто с неё продолжается выполнение программы после


«выброса» исключения.

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

12. Понятие паттернов проектирования. Паттерн


Фабрика.
Шаблон проектирования или паттерн  в разработке программного обеспечения —
повторяемая архитектурная конструкция, представляющая собой решение проблемы
проектирования в рамках некоторого часто возникающего контекста.
Обычно шаблон не является законченным образцом, который может быть прямо
преобразован в код; это лишь пример решения задачи, который можно использовать в
различных ситуациях. Объектно-ориентированные шаблоны показывают отношения и
взаимодействия между классами или объектами, без определения того, какие конечные
классы или объекты приложения будут использоваться.
Абстрактная фабрика— порождающий шаблон проектирования, предоставляет интерфейс
для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя
их конкретных классов. Шаблон реализуется созданием абстрактного класса Factory, который
представляет собой интерфейс для создания компонентов системы. Затем пишутся
подклассы, реализующие этот интерфейс.
Плюсы:
 изолирует конкретные классы;
 упрощает замену семейств продуктов;
 гарантирует сочетаемость продуктов.
Минусы:
 сложно добавить поддержку нового вида продуктов.
https://refactoring.guru/ru/design-patterns/abstract-factory (почитать)

13. Понятие паттернов проектирования. Паттерн


Декоратор.
Шаблон проектирования или паттерн  в разработке программного обеспечения —
повторяемая архитектурная конструкция, представляющая собой решение проблемы
проектирования в рамках некоторого часто возникающего контекста.
Обычно шаблон не является законченным образцом, который может быть прямо
преобразован в код; это лишь пример решения задачи, который можно использовать в
различных ситуациях. Объектно-ориентированные шаблоны показывают отношения и
взаимодействия между классами или объектами, без определения того, какие конечные
классы или объекты приложения будут использоваться.
Паттерн Декоратор динамически наделяет объект новыми возможностями.
Особенности:
 Декораторы имеют тот же супертип, что и декорируемые объекты.
 Объект можно завернуть в 1 или несколько декораторов.
 Так как декоратор с объектом одного супертипа, то можно передавать декорированный
объект.
 Декоратор добавляет свое поведение до и/или после делегирования операций
декорируемому объекту, выполняющему остальную работу.
 Объект может быть декорирован в любой момент времени, так что мы можем
декорировать объекты динамически и с произвольным количеством декораторов.
Недостаток: введение декораторов усложняет код создания экземпляра компонента.
Необходимо не только создать объект, но и завернуть его декоратором.
Пример:
14. Понятие паттернов проектирования. Паттерн
Адаптер.
По большому счету, паттерн — это типичное решение общих проблем
проектирования. Паттерны решают ряд основных проблем, связанных с
программированием/проектированием.
Адаптер — это структурный паттерн проектирования, который позволяет
объектам с несовместимыми интерфейсами работать вместе.

Адаптер объектов
Эта реализация использует композицию: объект адаптера «оборачивает», то есть
содержит ссылку на служебный объект. Такой подход работает во всех языках
программирования.

Клиент — это класс, который содержит существующую бизнес-логику программы.


Клиентский интерфейс описывает протокол, через который клиент может
работать с другими классами.
Сервис – это какой-то полезный класс, обычно сторонний. Клиент не может
использовать этот класс напрямую, так как сервис имеет непонятный ему
интерфейс.
Адаптер — это класс, который может одновременно работать и с клиентом, и с
сервисом. Он реализует клиентский интерфейс и содержит ссылку на объект
сервиса. Адаптер получает вызовы от клиента через методы клиентского
интерфейса, а затем переводит их в вызовы методов обернутого объекта в
правильном формате.
Работая с адаптером через интерфейс, клиент не привязывается к конкретному
классу адаптера. Благодаря этому, вы можете добавлять в программу новые виды
адаптеров, независимо от клиентского кода. Это может пригодиться, если
интерфейс сервиса вдруг изменится, например, после выхода новой версии
сторонней библиотеки.
Адаптер классов
Эта реализация базируется на наследовании: адаптер наследует оба интерфейса
одновременно. Такой подход возможен только в языках, поддерживающих
множественное наследование, например, C++.
Адаптер классов не нуждается во вложенном объекте, так как он может одновременно
наследовать и часть существующего класса, и часть сервиса.

Преимущество: Отделяет и скрывает от клиента подробности преобразования


различных интерфейсов.
Недостаток: Усложняет код программы из-за введения дополнительных классов.
Пример
Приведем реализацию паттерна Adapter. Для примера выше адаптируем
показания температурного датчика системы климат-контроля, переведя их из
градусов Фаренгейта в градусы Цельсия (предполагается, что код этого датчика
недоступен для модификации).
#include <iostream>
// Уже существующий класс температурного датчика окружающей среды
class FahrenheitSensor
{
 public:
   // Получить показания температуры в градусах Фаренгейта
   float getFahrenheitTemp() {
     float t = 32.0;
     // ... какой то код
     return t;
   }
};
class Sensor
{   
 public:
   virtual ~Sensor() {}
   virtual float getTemperature() = 0;
};
class Adapter : public Sensor
{   
 public:
   Adapter( FahrenheitSensor* p ) : p_fsensor(p) {
   }
  ~Adapter() {
     delete p_fsensor;
   }
   float getTemperature() {
     return (p_fsensor->getFahrenheitTemp()-32.0)*5.0/9.0;
   }
 private:
   FahrenheitSensor* p_fsensor;
};
int main()
{
 Sensor* p = new Adapter( new FahrenheitSensor);
 cout << "Celsius temperature = " << p->getTemperature() << endl;
 delete p;   
 return 0;
}
Отношения с другими паттернами
 Мост проектируют загодя, чтобы развивать большие части приложения отдельно
друг от друга. Адаптер применяется постфактум, чтобы заставить несовместимые
классы работать вместе.
 Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой
объект без изменения его интерфейса. Причём Декоратор поддерживает
рекурсивную вложенность, чего не скажешь об Адаптере.
 Адаптер предоставляет классу альтернативный интерфейс. Декоратор
предоставляет расширенный интерфейс. Заместитель предоставляет тот же
интерфейс.
Фасад задаёт новый интерфейс, тогда как Адаптер повторно использует старый.
Адаптер оборачивает только один класс, а Фасад оборачивает целую подсистему. Кроме
того, Адаптер позволяет двум существующим интерфейсам работать сообща, вместо
того, чтобы задать полностью новый.

15. Понятие паттернов проектирования. Паттерн


Фасад.
Шаблон фасад (англ. Facade) — структурный шаблон проектирования, позволяющий
скрыть сложность системы путём сведения всех возможных внешних вызовов к одному
объекту, делегирующему их соответствующим объектам системы.

Вашему коду приходится работать с большим количеством объектов некой сложной


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

В результате бизнес-логика ваших классов тесно переплетается с деталями реализации


сторонних классов. Такой код довольно сложно понимать и поддерживать.

Фасад — это простой интерфейс для работы со сложной подсистемой, содержащей


множество классов. Фасад может иметь урезанный интерфейс, не имеющий 100%
функциональности, которой можно достичь, используя сложную подсистему напрямую.
Но он предоставляет именно те возможности, которые нужны клиенту, и скрывает все
остальные.

Аналогия из жизни
Когда вы звоните в магазин и делаете заказ по телефону, сотрудник службы поддержки
является вашим фасадом ко всем службам и отделам магазина. Он предоставляет вам
упрощённый интерфейс к системе создания заказа, платёжной системе и отделу
доставки.
16. Понятие паттернов проектирования. Паттерн
Шаблонный метод.
Шаблон проектирования или паттерн  в разработке программного обеспечения —
повторяемая архитектурная конструкция, представляющая собой решение проблемы
проектирования в рамках некоторого часто возникающего контекста.
Паттерн Шаблонный метод задаёт “скелет” алгоритма в методе, оставляя определение
реализации некоторых шагов подклассам (субклассам). Субклассы могут переопределять
некоторые части алгоритма без изменения его структуры.
Основной задачей паттерна является создание шаблона алгоритма. Шаблон алгоритма - это
метод, определяющий алгоритм в виде последовательности шагов. Один или несколько
шагов определяются в виде абстрактных методов, реализуемых субклассами. Таким образом
гарантируется неизменность структуры алгоритма при том, что часть реализации
предоставляется субклассами.

Код на примере кофейни:


#include <iostream>
using namespace std;

class Money {
public:
int rub;
int kop;

Money(int rub_, int kop_) {


rub = rub_;
kop = kop_;
}

Money operator-(Money to) {


int resultRub = this->rub - to.rub;
int resultKop = this->kop - to.kop;

if (resultKop < 0) {
resultRub = resultRub - 1;
resultKop = 100 + resultKop;
}
Money result = Money(resultRub, resultKop);
return result;

bool operator<(Money m2) {


if (rub < m2.rub) {
return true;
}
if (rub == m2.rub and kop < m2.kop) {
return true;
}
return false;
}

bool operator==(Money m2) {


return (rub == m2.rub and kop == m2.kop);
}

bool operator>(Money m2) {


if (rub > m2.rub) {
return true;
}
if (rub == m2.rub and kop > m2.kop) {
return true;
}
return false;
}
};

int main() {
Money gumPrice = Money(5, 0);
Money paid = Money(5, 0);
Money change = paid - gumPrice;
cout << change.rub << "." << change.kop << endl;
if (paid == gumPrice) {
cout << "true" << endl;
} else {
cout << "false" << endl;
}
}