{"id":22544355,"url":"https://github.com/stacenko-developer/patterns","last_synced_at":"2025-07-19T10:39:30.946Z","repository":{"id":174698134,"uuid":"652637664","full_name":"stacenko-developer/Patterns","owner":"stacenko-developer","description":"Паттерны проектирования с примерами на C#","archived":false,"fork":false,"pushed_at":"2023-06-28T12:51:10.000Z","size":896,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T16:14:00.932Z","etag":null,"topics":["abstract-factory","abstract-factory-pattern","adapter","adapter-pattern","csharp","decorator","decorator-pattern","dependency-injection","dependency-injection-pattern","iterator","iterator-pattern","mediator","mediator-pattern","observer","observer-pattern","patterns","singleton","singleton-pattern"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stacenko-developer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-12T13:38:04.000Z","updated_at":"2025-02-15T23:32:01.000Z","dependencies_parsed_at":"2023-07-11T07:01:05.089Z","dependency_job_id":null,"html_url":"https://github.com/stacenko-developer/Patterns","commit_stats":null,"previous_names":["stacenko-developer/patterns"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stacenko-developer/Patterns","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2FPatterns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2FPatterns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2FPatterns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2FPatterns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stacenko-developer","download_url":"https://codeload.github.com/stacenko-developer/Patterns/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacenko-developer%2FPatterns/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265919725,"owners_count":23849453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstract-factory","abstract-factory-pattern","adapter","adapter-pattern","csharp","decorator","decorator-pattern","dependency-injection","dependency-injection-pattern","iterator","iterator-pattern","mediator","mediator-pattern","observer","observer-pattern","patterns","singleton","singleton-pattern"],"created_at":"2024-12-07T14:07:02.314Z","updated_at":"2025-07-19T10:39:30.886Z","avatar_url":"https://github.com/stacenko-developer.png","language":"C#","readme":"# Паттерны проектирования с примерами на C#\nВ данном репозитории содержатся реализации паттернов на языке программирования C#. Ниже вы можете ознакомиться с описанием паттернов, их назначением, а также преимуществами и недостатками. \u003cbr /\u003e\nПосле изучения теории можете переходить в код - он хорошо задокументирован, поэтому разобраться в нем не составит труда. \u003cbr /\u003e\nПомимо документации в данном файле будет пошагово описана реализация каждого паттерна, которыцй есть в репозитории.\n### В папке каждого паттерна содержатся: \u003cbr /\u003e\n* его реализация в библиотеке классов; \u003cbr /\u003e\n* демонстрация работы в консольном приложении; \u003cbr /\u003e\n* [тестирование](https://github.com/stacenko-developer/UnitTests \"тестирование\") методов классов и проверка корректности реализации паттерна \u003cbr /\u003e\n\n## Оглавление\n\n\n1. [Порождающие паттерны (Creational)](#Порождающие-паттерны)\n    1. [Абстрактная фабрика (Abstract Factory)](#Абстрактная-фабрика)\n    2. [Одиночка (Singleton)](#Одиночка)\n    3. [Строитель (Builder)](#Строитель)\n    4. [Прототип (Prototype)](#Прототип)\n    5. [Фабричный метод (Factory Method)](#Фабричный-метод)\n2. [Структурные паттерны](#Структурные-паттерны)\n    1. [Адаптер (Adapter)](#Адаптер)\n    2. [Декоратор (Decorator)](#Декоратор)\n    3. [Компоновщик (Composite)](#Компоновщик)\n    4. [Фасад (Facade)](#Фасад)\n3. [Поведенческие (Behavioral)](#Поведенческие-паттерны)\n    1. [Итератор (Iterator)](#Итератор)\n    2. [Наблюдатель (Observer)](#Наблюдатель)\n    3. [Посредник (Mediator)](#Посредник)\n    4. [Шаблонный метод (Template Method)](#Шаблонный-метод)\n4. [Архитектурные (Architectural)](#Архитектурные-паттерны)\n    1. [Внедрение зависимостей (Dependency Injection)](#Внедрение-зависимостей)\n\n## Порождающие паттерны\n\nС помощью пораждающих паттернов (Creational) у нас есть возможность удобно и безопасносно создавать объекты или группы объектов.\n____\n### Абстрактная фабрика\n__Абстрактная фабрика__ (Abstract Factory) – это порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов. \u003cbr /\u003e\nНам нужен такой способ создавать объекты, чтобы они сочетались с другими одного и того же семейства. Кроме того, мы не хотим вносить изменения в существующий код при добавлении новых объектов в программу. \u003cbr /\u003e\u003cbr\u003e\n\u003e Данный паттерн необходимо использовать, когда система не должна зависеть от способа создания и компоновки новых объектов и когда создаваемые объекты должны использоваться вместе и являются взаимосвязанными.\n\n\u003cbr\u003eПредположим, что у нас есть какая-то компания, в которой работают сотрудники. Для простоты у нас есть два типа работников: __программист__ и __директор__. У каждого сотрудника есть девайс (компьютер и ноутбук) и служебный транспорт (пусть будет BMW и LADA). \u003cbr\u003e \u003cbr\u003e\n:one: Для начала создадим два абстрактных класса: __WorkingCar__ (транспорт для нашего сотрудника) и __WorkingDevice__ (устройство, на котором наш сотрудник будет работать). Начнем с автомобиля: у него есть модель, цена и год выпуска. Помимо этого у нас будет абстрактный метод получения стоимости налога:\n```C#\n/// \u003csummary\u003e\n/// Рабочий автомобиль.\n/// \u003c/summary\u003e\npublic abstract class WorkingCar\n{\n\t/// \u003csummary\u003e\n\t/// Модель автомобиля.\n\t/// \u003c/summary\u003e\n\tprotected string _model;\n\n\t/// \u003csummary\u003e\n\t/// Цена.\n\t/// \u003c/summary\u003e\n\tprotected int _price;\n\n\t/// \u003csummary\u003e\n\t/// Год выпуска.\n\t/// \u003c/summary\u003e\n\tprotected int _releaseYear;\n\t\n\t/// \u003csummary\u003e\n\t/// Получить стоимость налога.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eНалог.\u003c/returns\u003e\n\tpublic abstract int GetTax();\n}\n```\nАналогично реализовываем абстрактный класс рабочего устройства, у коготого есть цена и модель. В данном классе тоже будет абстрактный метод, который будет расчитывать стоимость дополнительных аксессуаров (например, зарядка, мышка и так далее).\n```C#\n/// \u003csummary\u003e\n/// Рабочее устройство.\n/// \u003c/summary\u003e\npublic abstract class WorkingDevice\n{\n\t/// \u003csummary\u003e\n\t/// Модель.\n\t/// \u003c/summary\u003e\n\tprotected string _model;\n\n\t/// \u003csummary\u003e\n\t/// Цена.\n\t/// \u003c/summary\u003e\n\tprotected int _price;\n\t\n\t/// \u003csummary\u003e\n\t/// Получить стоимость дополнительных аксессуаров.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСтоимость дополнительных аксессуаров.\u003c/returns\u003e\n\tpublic abstract int GetAccessoriesCost();\n}\n```\n:two: После этого нам необходимо создать __классы наследники__: у WorkingCar классами-наследниками будут LADA и BMW, у WorkingDevice - Laptop и Computer. В данных классах наследникам мы реализуем методы, которые были в наших абстрактных классах.\u003cbr\u003e\n:three: Теперь нам необходимо реализовать __интерфейс__ фабрики по созданию сотрудника, в котором будет 2 метода: создание объекта __рабочего устройства__ и создание объекта __рабочего автомобиля__. \n```C#\n/// \u003csummary\u003e\n/// Фабрика сотрудника.\n/// \u003c/summary\u003e\npublic interface IWorkerFactory \n{\n\t/// \u003csummary\u003e\n\t/// Создание рабочего устройства для сотрудника. \n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРабочее устройство.\u003c/returns\u003e\n\tWorkingDevice CreateWorkingDevice();\n\n\t/// \u003csummary\u003e\n\t/// Создание рабочего автомобиля.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРабочий автомобиль.\u003c/returns\u003e\n\tWorkingCar CreateWorkingCar();\n}\n```\nДалее нам необходимо создать фабрику по созданию программиста и директора, реализующую интерфейс __IWorkerFactory__. Рассмотрим пример фабрики по созданию директора. У нас метод __CreateWorkingCar()__ возвращает объект автомобиля марки __BMW__ и метод __CreateWorkingDevice()__ объект __компьютера__ в качестве рабочего устройства.\n```C#\n/// \u003csummary\u003e\n/// Фабрика директора.\n/// \u003c/summary\u003e\npublic class DirectorFactory : IWorkerFactory \n{\n\t/// \u003csummary\u003e\n\t/// Создание рабочего автомобиля.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРабочий автомобиль.\u003c/returns\u003e\n\tpublic WorkingCar CreateWorkingCar() =\u003e new BMW();\n\n\t/// \u003csummary\u003e\n\t/// Создание рабочего устройства.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРабочее устройство.\u003c/returns\u003e\n\tpublic WorkingDevice CreateWorkingDevice() =\u003e new Computer();\n}\n```\n\u003e Аналогично реализована фабрика по созданию объекта программиста: метод __CreateWorkingCar()__ возвращает объект Lada и метод __CreateWorkingDevice()__ возвращает ноутбук. \n\n\u003cbr\u003e:four: Теперь у нас есть все условия для того, чтобы создать класс самого сотрудника. Как было сказано ранее, у сотрудника есть служебный автомобиль и рабочее устройство. Добавим их в поля сотрудника.\u003cbr\u003e\nВ конструкторе в качестве параметра мы будем принимать интерфейс __IWorkerFactory__. Напомню, его у нас реализуют ProgrammerFactory и DirectorFactory.\u003cbr\u003e\n           __В итоге у нас получился следующий код:__\n```C#\n/// \u003csummary\u003e\n/// Сотрудник.\n/// \u003c/summary\u003e\npublic class Worker\n{\n\t\t\n\t/// \u003csummary\u003e\n\t/// Рабочий автомобиль.\n\t/// \u003c/summary\u003e\n\tprivate WorkingCar _workingCar; \n\n\t/// \u003csummary\u003e\n\t/// Рабочее устройство.\n\t/// \u003c/summary\u003e\n\tprivate WorkingDevice _workingDevice;\n\n\t/// \u003csummary\u003e\n\t/// Создание сотрудника с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"workerFactory\"\u003eФабрика сотрудника.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eФабрика сотрудника равна null!\u003c/exception\u003e\n\tpublic Worker(IWorkerFactory workerFactory)\n\t{\n\t\tif (workerFactory == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(workerFactory),\n\t\t\t\t\"Фабрика для создания сотрудника равна null!\");\n\t\t}\n\n\t\t_workingCar = workerFactory.CreateWorkingCar();\n\t\t_workingDevice = workerFactory.CreateWorkingDevice();\n\t}\n\t\n\t/// \u003csummary\u003e\n\t/// Получить стоимость налога за автомобиль.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСтоимость налога.\u003c/returns\u003e\n\tpublic int GetTax() =\u003e _workingCar.GetTax();\n\n\t/// \u003csummary\u003e\n\t/// Получить стоимость дополнительных аксессуаров для рабочего устройства.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСтоимость дополнительных аксессуаров.\u003c/returns\u003e\n\tpublic int GetAccessoriesCost() =\u003e _workingDevice.GetAccessoriesCost();\n}\n```\n:white_check_mark: __Преимущества паттерна Abstract Factory__: упрощение добавления новых продуктов, их сочетаемость, а также избавление кода от привязки к конкретным классам продуктов.\u003cbr\u003e\n:x: __Недостатки__: возможное усложнение кода из-за создания огромного количества вспомогательных классов.\n____\n### Одиночка\n__Одиночка (Singleton)__ - это __паттерн__, который позволяет гарантировать, что класс имеет только один экземпляр, обеспечивая при этом глобальную точку доступа к этому экземпляру. \u003cbr\u003e\nМодель Singleton решает две проблемы одновременно, __нарушая принцип единой ответственности__: \u003cbr\u003e\u003cbr\u003e\n:one: Гарантия того, что класс имеет только один экземпляр. Это может пригодиться, когда необходимо контролировать доступ к какому-либо общему ресурсу, например, к базе данных или файлу. \u003cbr\u003e\n:two: Предоставление глобальной точки доступа к этому экземпляру.\n\u003e Шаблон требует специальной обработки в __многопоточной среде__, чтобы несколько потоков не создавали экземпляр класса несколько раз.\n\nТеперь перейдем к реализации данного __паттерна__. Пусть у нас есть какой-то сайт, в котором есть разделы. Раздел будет иметь следующие свойства: название и код (идентификатор). \u003cbr\u003e\n```C#\n/// \u003csummary\u003e\n/// Раздел.\n/// \u003c/summary\u003e\npublic class Section\n{\n\t/// \u003csummary\u003e\n\t/// Название раздела.\n\t/// \u003c/summary\u003e\n\tpublic string Name { get; set; }\n\n\t/// \u003csummary\u003e\n\t/// Код раздела.\n\t/// \u003c/summary\u003e\n\tpublic string Code { get; set; } \n}\n```\nТеперь мы хотим создать экземпляр класса базы данных __SectionDatabase__, в которой будут храниться наши разделы. \u003cbr\u003e\u003cbr\u003e\n:one: В данном классе создаем статическое поле, имеющее тот же тип, что и сам класс: SectionDatabase. По умолчанию он будет равен null, так как еще ни разу не был создан экземпляр данного класса.\u003cbr\u003e\n:two: Создаем заблокированный объект, который мы будем использовать для синхронизации. Это означает, что в критическую область кода потоки будут заходить по очереди.\u003cbr\u003e\n:three: Создаем список разделов, в который мы будем добавлять созданные разделы.\u003cbr\u003e\n:four: Создаем защищенный конструктор. Это необходимо для того, чтобы у нас не было возможности вызвать публичный конструктор, так как в этом случае мы не сможем контролировать количество созданных экземпляров класса SectionDatabase.\u003cbr\u003e\n:five: Добавляем публичный метод __Initialize__. Его назначение - инициализировать объект базы данных, а также проверять: если объект базы данных уже был создан, то необходимо возврать уже ранее созданный экземпляр. Также не забываем про использование синхронизации для критической секции.\u003cbr\u003e\u003cbr\u003e\nТеперь посмотрим на получившийся результат (код представлен в упрощенном виде, полная реализация доступна в репозитории):\n```C#\n/// \u003csummary\u003e\n/// База данных разделов. Реализация паттерна Singleton.\n/// \u003c/summary\u003e\npublic class SectionDatabase\n{\n        /// \u003csummary\u003e\n        /// База данных разделов.\n        /// \u003c/summary\u003e\n        private static SectionDatabase Database = null;\n\n        /// \u003csummary\u003e\n        /// Заблокированный объект.\n        /// Служит для синхронизации потоков.\n        /// \u003c/summary\u003e\n        private static object LockObject = new object(); \n\n        /// \u003csummary\u003e\n        /// Список разделов.\n        /// \u003c/summary\u003e\n        private List\u003cSection\u003e _sectionsList;\n\n        /// \u003csummary\u003e\n        /// Создает хранилище данных разделов.\n        /// \u003c/summary\u003e\n        protected SectionDatabase()\n        {\n        }\n\t\n        /// \u003csummary\u003e\n        /// Инициализация хранилища данных разделов.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eХранилище данных разделов.\u003c/returns\u003e\n        public static SectionDatabase Initialize()\n        {\n            if (Database == null)\n            {\n                lock (LockObject)\n                {\n                    if (Database == null)\n                    {\n                        Database = new SectionDatabase();\n                    }\n                }\n            }\n\n            return Database;\n        }\n}\n```\n:white_check_mark: __Преимущества паттерна Singleton__: класс гарантированно имеет только один экземпляр и не более, у нас есть точка доступа к единственному экземпляру (в нашем случае это метод Initialize).\u003cbr\u003e\n:x: __Недостатки__: нарушение принципа единой ответственности (Single Responsibility Principle), требуется особая обработка в многопоточной среде.\n___\n### Строитель\n__Строитель (Builder)__ - это порождающий паттерн проектирования, который позволяет разделить создание экземпляра класса на несколько шагов. Данный паттерн может быть полезен, когда созданние какого-либо экземпляра класса требует много разных этапов и когда также важно, в каком порядке эти этапы будут выполняться.\u003cbr\u003e\n\u003e :x: Проблема заключается в том, что у нас может быть какой-то сложный объект и его создание может привести к огромному количеству кода в конструкторе\nПаттерн Builder (Строитель) состоить из двух участников:\n* __Строитель (Builder)__ – предоставляет методы для сборки частей экземпляра класса;\n* __Распорядитель (Director)__ – определяет саму стратегию того, как будет происходить сборка: определяет, в каком порядке будут вызываться методы Строителя.\n\nРеализуем данный паттерн на основе примера: у нас есть завод по производству компьютеров. У нас есть разработчики компьютеров и директор.\u003cbr\u003e\n:one: Создадим класс компьютера, для простоты он будет содержать всего 4 характеристики:\n```C#\n/// \u003csummary\u003e\n/// Содержит методы для разработчика компьютеров.\n/// \u003c/summary\u003e\npublic interface IComputerDeveloper\n{   \n\t/// \u003csummary\u003e\n\t/// Установка процессора.\n\t/// \u003c/summary\u003e\n\tvoid SetProcessor();\n\t\n\t/// \u003csummary\u003e\n\t/// Установка оперативной памяти.\n\t/// \u003c/summary\u003e\n\tvoid SetRandomAccessMemory();\n\t\n\t/// \u003csummary\u003e\n\t/// Установка операционной системы.\n\t/// \u003c/summary\u003e\n\tvoid SetOperationSystem();\n\t\n\t/// \u003csummary\u003e\n\t/// Получение компьютера.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eКомпьютер.\u003c/returns\u003e\n\tComputer GetComputer();\n}\n```\n:two: Теперь создадим интерфейс __IComputerDeveloper__, которые будет содержать методы разработчика компьютеров: \n```C#\n/// \u003csummary\u003e\n/// Содержит методы для разработчика компьютеров.\n/// \u003c/summary\u003e\npublic interface IComputerDeveloper\n{   \n        /// \u003csummary\u003e\n        /// Установка процессора.\n        /// \u003c/summary\u003e\n        void SetProcessor();\n\n        /// \u003csummary\u003e\n        /// Установка оперативной памяти.\n        /// \u003c/summary\u003e\n        void SetRandomAccessMemory();\n\n        /// \u003csummary\u003e\n        /// Установка операционной системы.\n        /// \u003c/summary\u003e\n        void SetOperationSystem();\n\n        /// \u003csummary\u003e\n        /// Получение компьютера.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eКомпьютер.\u003c/returns\u003e\n        Computer GetComputer();\n}\n```\n:three: Затем создадим классы HPComputerDeveloper (разработчик компьютеров HP) и DELLComputerDeveloper (Разработчик компьютеров DELL), в которых будет реализован интерфейс IComputerDeveloper. Рассмотрим реализация класса HPComputerDeveloper, класс DELLComputerDeveloper реализован аналогично.\n```C#\n/// \u003csummary\u003e\n/// Разработчик компьютеров HP.\n/// \u003c/summary\u003e\npublic class HPComputerDeveloper : IComputerDeveloper\n{\n        /// \u003csummary\u003e\n        /// Компьютер.\n        /// \u003c/summary\u003e\n        private Computer _computer;\n\n        /// \u003csummary\u003e\n        /// Модель.\n        /// \u003c/summary\u003e\n        private string _model = \"HP\";\n\n        /// \u003csummary\u003e\n        /// Процессор.\n        /// \u003c/summary\u003e\n        private string _processor = \"Intel Core i5-7400\";\n\n        /// \u003csummary\u003e\n        /// Количество оперативной памяти.\n        /// \u003c/summary\u003e\n        private int _randomAccessMemoryCount = 8;\n\n        /// \u003csummary\u003e\n        /// Операционная система.\n        /// \u003c/summary\u003e\n        private string _operationSystem = \"Windows 10 Pro\";\n\n        /// \u003csummary\u003e\n        /// Создание разработчика компьютеров HP.\n        /// \u003c/summary\u003e\n        public HPComputerDeveloper() \n        {\n            _computer = new Computer();\n            _computer.Model = _model;\n        }\n\n        /// \u003csummary\u003e\n        /// Установка процессора.\n        /// \u003c/summary\u003e\n        public void SetProcessor()\n        {\n            _computer.Processor = _processor;\n        }\n\n        /// \u003csummary\u003e\n        /// Установка оперативной памяти.\n        /// \u003c/summary\u003e\n        public void SetRandomAccessMemory()\n        {\n            _computer.RandomAccessMemory = _randomAccessMemoryCount;\n        }\n\n        /// \u003csummary\u003e\n        /// Установка операционной системы.\n        /// \u003c/summary\u003e\n        public void SetOperationSystem()\n        {\n            _computer.OperationSystem = _operationSystem;\n        }\n\n        /// \u003csummary\u003e\n        /// Получение компьютера.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eКомпьютер.\u003c/returns\u003e\n        public Computer GetComputer() =\u003e _computer;\n}\n```\n:four: Теперь создадим класс Director, который будет иметь поле IComputerDeveloper, то есть, он будет принимать в конструкторе одного из разработчиков компьютеров и в зависимости от разработчика создавать определенный компьютер.\n```C#\n/// \u003csummary\u003e\n/// Директор.\n/// \u003c/summary\u003e\npublic class Director\n{\n        /// \u003csummary\u003e\n        /// Разработчик компьютеров.\n        /// \u003c/summary\u003e\n        private IComputerDeveloper _computerDeveloper;\n\n        /// \u003csummary\u003e\n        /// Создание директора с помощью указанных параметров.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"computerDeveloper\"\u003eРазработчик компьютеров.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eРазработчик компьютеров равен null!\u003c/exception\u003e\n        public Director(IComputerDeveloper computerDeveloper) \n        {\n            if (computerDeveloper == null)\n            {\n                throw new ArgumentNullException(nameof(computerDeveloper), \"Разработчик компьютеров равен null!\");\n            }\n\n            _computerDeveloper = computerDeveloper;\n        }\n\n        /// \u003csummary\u003e\n        /// Создание полноценного компьютера.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eСозданный компьютер.\u003c/returns\u003e\n        public Computer CreateFullComputer()\n        {\n            _computerDeveloper.SetProcessor();\n            _computerDeveloper.SetRandomAccessMemory();\n            _computerDeveloper.SetOperationSystem();\n\n            return _computerDeveloper.GetComputer();\n        }\n\n        /// \u003csummary\u003e\n        /// Создание компьютера без операционной системы.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eСозданный компьютер.\u003c/returns\u003e\n        public Computer CreateComputerWithoutOperationSystem()\n        {\n            _computerDeveloper.SetProcessor();\n            _computerDeveloper.SetRandomAccessMemory();\n\n            return _computerDeveloper.GetComputer();\n        }\n}\n```\n\u003e За счет того, что мы разбили процесс создания компьютера на отдельный шаги, мы можем создавать разные объекты, например, полноценный компьютер или компьютер без операционной системы.\n\n:white_check_mark: __Преимущества паттерна Builder__: контроль за этапами создания экземпляра класса, в зависимости от этапов можно получить различные объекты.\u003cbr\u003e\n:x: __Недостатки__: жесткая связка конкретного Builder и продукта, который он создает. \n___\n### Прототип\n__Прототип (Prototype)__ - это такой паттерн, который используется ​для создания новых объектов с помощью клонирования существующих. Для того, чтобы определение паттерна было более понятно, приведу конкретный пример.\u003cbr\u003e\nПредположим, что у нас есть биржа фриланса, где есть заказчики и исполнители.\u003cbr\u003e\n:one: Для начала создадим абстрактный класс User - пользователь биржи фриланса. У пользователя будут идентификатор, ФИО и абстрактный метод Clone. Это означает, что мы обязательно должны реализовать его в классе-наследнике.\n```C#\n/// \u003csummary\u003e\n/// Пользователь\n/// \u003c/summary\u003e\npublic abstract class User\n{\n        /// \u003csummary\u003e\n        /// Идентификатор.\n        /// \u003c/summary\u003e\n        public int Id { get; set; }\n\n        /// \u003csummary\u003e\n        /// Имя.\n        /// \u003c/summary\u003e\n        public string FirstName { get; set; }\n\n        /// \u003csummary\u003e\n        /// Фамилия.\n        /// \u003c/summary\u003e\n        public string LastName { get; set; }\n        \n        /// \u003csummary\u003e\n        /// Отчество.\n        /// \u003c/summary\u003e\n        public string Patronymic { get; set; }\n\n        /// \u003csummary\u003e\n        /// Клонирование пользователя.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eНового пользователя.\u003c/returns\u003e\n        public abstract User Clone();\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта пользователя.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные пользователя в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Идентификатор - {Id} Имя - {FirstName} Фамилия - {LastName} Отчество - {Patronymic}\";\n}\n```\n:two: Теперь реализуем класс-наследник исполнителя: в него не будем добавлять дополнительные свойства. Реализация метода Clone будет выглядеть так: \n```C#\n/// \u003csummary\u003e\n/// Исполнитель.\n/// \u003c/summary\u003e\npublic class Executor : User\n{\n        /// \u003csummary\u003e\n        /// Клонирование пользователя.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eНового пользователя.\u003c/returns\u003e\n        public override User Clone() =\u003e (Executor)MemberwiseClone();\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта исполнителя.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные исполнителя в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Данные исполнителя: {base.ToString()}\";\n}\n```\n\u003e В данном случае мы можем выполнить неполное (поверхностное) копирование. Это подходит тогда, когда у нас все поля Executor являются значимыми типами (исключение: string).\n\nТеперь рассмотрим второй класс-наследник: Customer. \n```C#\n/// \u003csummary\u003e\n/// Заказчик\n/// \u003c/summary\u003e\npublic class Customer : User\n{\n        /// \u003csummary\u003e\n        /// Паспорт.\n        /// \u003c/summary\u003e\n        public Passport Passport { get; set; }\n\n        /// \u003csummary\u003e\n        /// Создание пользователя.\n        /// \u003c/summary\u003e\n        public Customer()\n        {\n            Passport = new Passport();\n        }\n\n        /// \u003csummary\u003e\n        /// Клонирование пользователя.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eНового пользователя.\u003c/returns\u003e\n        public override User Clone()\n        {\n            var customer = (Customer)MemberwiseClone();\n            customer.Passport = new Passport();\n            customer.Passport.Series = Passport.Series;\n            customer.Passport.Number = Passport.Number;\n            customer.Passport.ReceiptPlace = Passport.ReceiptPlace;\n\n            return customer;\n        }\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта заказчика.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные заказчика в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Данные заказчика: {base.ToString()} {Passport}\";\n}\n```\n\u003e Здесь у нас уже присутствует класс Passport - это ссылочный тип, соответсвенно, мы не можем использовать неполное копирование. Если мы будем использовать неполное копирование, то у нас будет два заказчика ссылаться на один и тот же объект паспортных данных. Поэтому нам придется создать новый объект паспорта и вручную его проинициализировать.\n\nТакже, чтобы убедиться в том, что у нас создаются два абсолютно разных объекта, которые не ссылаются на одни и те же поля, рекомендую запустить тесты в проекте PrototypeTests, где происходит данная проверка.\u003cbr\u003e\u003cbr\u003e\n:white_check_mark: __Преимущества паттерна Prototype__: клонирование объектов без привязки к конкретным классам, сокращение кода инициализации экземплятор классов.\u003cbr\u003e\n:x: __Недостатки__: Проблемы с клонированием составных объектов, то есть, тех объектов, которые внутри содержат другие объекты.\n___\n### Фабричный метод\n__Фабричный метод (Factory Method)__ - это порождающий паттерн проектирования, который определяет интерфейс для создания объектов определенного класса, но именно в подклассах принимается решение о типе объекта, который будет создан.\u003cbr\u003e\n\u003e :white_check_mark: Фабричный метод будет полезен, если мы заранее не знаем, объекты каких типов мы хотим создать.\n\nУ нас есть следующие участники: \n* базовый класс какого-либо продукта;\n* наследники базового класса продукта;\n* базовый класс создателя этого продукта;\n* создатели-наследники базового класса создателя, которые созданию продукты-наследники базового класса продукта\u003cbr\u003e\n\n:one: Реализуем паттерн на примере создания телефонов. Пусть у нас будет базовый класс __Phone__, содержащий следующие свойства: \n```C#\n/// \u003csummary\u003e\n/// Телефон.\n/// \u003c/summary\u003e\npublic abstract class Phone\n{\n        /// \u003csummary\u003e\n        /// Цена.\n        /// \u003c/summary\u003e\n        public decimal Price { get; set; }\n\n        /// \u003csummary\u003e\n        /// Модель.\n        /// \u003c/summary\u003e\n        public string Model { get; set; } = string.Empty;\n\n        /// \u003csummary\u003e\n        /// Процессор.\n        /// \u003c/summary\u003e\n        public string Processor { get; set; } = string.Empty;\n\n        /// \u003csummary\u003e\n        /// Оперативная память.\n        /// \u003c/summary\u003e\n        public int RandomAccessMemory { get; set; }\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта телефона.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные телефона в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Цена = {Price} Модель = {Model} Процессор = {Processor} Оперативная память = {RandomAccessMemory}\";\n}\n```\n:two: Создадим два класса наследника: Nokia и Samsung, в них добавим по одному дополнительному свойству.\n```C#\n/// \u003csummary\u003e\n/// Нокиа.\n/// \u003c/summary\u003e\npublic class Nokia : Phone\n{\n        /// \u003csummary\u003e\n        /// Работает ли батарея.\n        /// \u003c/summary\u003e\n        public bool IsBatteryWork { get; set; }\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта Нокиа.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные Нокиа в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Нокиа: {base.ToString()} Работает ли батарея = {IsBatteryWork}\";\n}\n\n/// \u003csummary\u003e\n/// Самсунг.\n/// \u003c/summary\u003e\npublic class Samsung : Phone\n{\n        /// \u003csummary\u003e\n        /// Влючена ли сейчас фронтальная камера.\n        /// \u003c/summary\u003e\n        public bool IsFrontCamera { get; set; }\n\n        /// \u003csummary\u003e\n        /// Строковое представления объекта Самсунга.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные Самсунга в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"Самсунг: {base.ToString()} Включена ли фронтальная камера = {IsFrontCamera}\";\n}\n```\n:three: Создадим интерфейс создателя телефонов IPhoneDeveloper:\n```C#\n/// \u003csummary\u003e\n/// Содержит методы для рабработчика телефонов.\n/// \u003c/summary\u003e\npublic interface IPhoneDeveloper\n{   \n        /// \u003csummary\u003e\n        /// Создание телефона.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eТелефон.\u003c/returns\u003e\n        Phone CreatePhone();\n}\n```\n:four: Создадим разработчиков телефонов Нокиа и Самсунга, реализующих интерфейсы __IPhoneDeveloper__:\n```C#\n/// \u003csummary\u003e\n/// Разработчик телефонов фирмы Нокиа.\n/// \u003c/summary\u003e\npublic class NokiaDeveloper : IPhoneDeveloper\n{\n        /// \u003csummary\u003e\n        /// Создание телефона.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eТелефон.\u003c/returns\u003e\n        public Phone CreatePhone() =\u003e new Nokia();\n}\n\n/// \u003csummary\u003e\n/// Разработчик телефонов фирмы Самсунг.\n/// \u003c/summary\u003e\npublic class SamsungDeveloper\n{\n        /// \u003csummary\u003e\n        /// Создание телефона.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eТелефон.\u003c/returns\u003e\n        public Phone CreatePhone() =\u003e new Samsung();\n}\n```\n:white_check_mark: __Преимущества паттерна Factory Method__: упрощение поддержки кода, так как продукт создается в отдельном классе.\u003cbr\u003e\n:x: __Недостатки__: Значительное увеличение кода, так как для каждого класса продукта необходимо будет добавлять класс-создатель, который будет создавать данный продукт.\n___\n## Структурные паттерны\n__Структурные паттерны__ (Structural) - цель их применения заключается в том, что благодаря им вы можете совмещать и сочетать сущности вместе.\n___\n### Адаптер\nАдаптер (Adapter) - это структурный шаблон проектирования, который используется для организации использования методов объекта, недоступного для модификации, через специально созданный интерфейс.\u003cbr\u003e\n\u003e __Проблема__: у нас уже есть конкретный класс и нужно, чтобы этот класс реализовывал определенный интерфейс, при этом сам класс менять нельзя.\n\n:white_check_mark: Решение: мы пишем класс, который будет реализовывать необходимый интерфейс, затем мы наследуем наш класс от того класса, который нам нужно изменить без прямого вмешательства в тот класс.\nПредположим, что у нас есть офис, в котором работают сотрудники. У офиса есть название, адрес:\n```C#\n/// \u003csummary\u003e\n/// Офис.\n/// \u003c/summary\u003e\npublic class Office\n{\n        /// \u003csummary\u003e\n        /// Название.\n        /// \u003c/summary\u003e\n        protected string _name; \n\n        /// \u003csummary\u003e\n        /// Адрес.\n        /// \u003c/summary\u003e\n        protected string _address;\n\n        /// \u003csummary\u003e\n        /// Создает офис с помощью указанных параметров.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"name\"\u003eНазвание офиса.\u003c/param\u003e\n        /// \u003cparam name=\"address\"\u003eАдрес офиса.\u003c/param\u003e\n        public Office(string name, string address)\n        {\n            Validator.ValidateStringText(name);\n            Validator.ValidateStringText(address);\n\n            _name = name;\n            _address = address;\n        }\n}\n```\nТеперь нам необходимо добавить метод \"переезда офиса\". То есть метод, который устанавливает новое значение для адреса. Не будем забывать, что существующий класс нельзя модифицировать. Решим проблему с помощью паттерна Адаптер:\u003cbr\u003e\n:one: Создадим интерфейс IMovable, в котором будет один метод Move, принимающий новый адрес офиса.\n```C#\n/// \u003csummary\u003e\n/// Содержит методы, связанные с переездом офиса.\n/// \u003c/summary\u003e\npublic interface IMovable\n{\n\t/// \u003csummary\u003e\n\t/// Изменение адреса офиса.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"newAddress\"\u003eНовый адрес.\u003c/param\u003e  \n\tvoid Move(string newAddress);\n}\n```\n:two: Создадим класс-наследник нашего базового класса Office, пусть это будет офис какой-то конкретной компании (например, Норбит). В данном классе мы реализуем интерфейс __IMovable__.\n```C#\n/// \u003csummary\u003e\n/// Офис Норбит. Реализация паттерна Адаптер.\n/// \u003c/summary\u003e\npublic class NrbOffice : Office, IMovable\n{\n\t/// \u003csummary\u003e\n\t/// Создает офис Норбит с помощью указанных параметров. \n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"name\"\u003eНазвание офиса.\u003c/param\u003e\n\t/// \u003cparam name=\"address\"\u003eАдрес офиса.\u003c/param\u003e\n\tpublic NrbOffice(string name, string address)\n\t\t: base(name, address)\n\t{\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Изменение адреса офиса.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"newAddress\"\u003eНовый адрес.\u003c/param\u003e\n\tpublic void Move(string newAddress)\n\t{\n\t\tValidator.ValidateStringText(newAddress);\n\n\t\t_address = newAddress;\n\t}\n}\n```\n:white_check_mark: __Преимущества паттерна Adapter__: возможность отделения интерфейса или кода преобразования данных от основной бизнес-логики программы.\u003cbr\u003e\n:x: __Недостатки__: общая сложность кода увеличивается. \n___\n### Декоратор\n__Декоратор (Decorator)__ - это паттерн, который позволяет динамически подключать к объекту дополнительную функциональность, оборачивая объект в обертки. \u003cbr\u003e\nДля того, чтобы определить какой-либо новый функционал, как правило, мы прибегаем к наследованию. Декораторы в отличие от наследования позволяют динамически в процессе выполнения определять новые возможности у объектов.\u003cbr\u003e\n\u003e Можно использовать несколько разных обёрток одновременно и вы получите бъединённое поведение сразу всех обёрток.\n\nДавайте теперь реализуем данный паттерн. Пусть у нас также будут сотрудники какой-то компании. У нас есть __задача__: отфильтровать сотрудника по определенному признаку.\u003cbr\u003e\n:one: Для начала создадим абстрактный класс __WorkersFilter__, в котором будет метод __GetFiltratedList()__, который будет возвращать нам отфильтрованный список сотрудников.\n```C#\n/// \u003csummary\u003e\n/// Фильтр.\n/// \u003c/summary\u003e\npublic abstract class WorkersFilter\n{\n        /// \u003csummary\u003e\n        /// Получение отфильтрованного списка.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eОтфильтрованный список.\u003c/returns\u003e  \n        public abstract List\u003cWorker\u003e GetFiltratedList(); \n}\n```\n:two: Создаем класс-наследник NorbitWorkersFilter, базовый класс у которого WorkersFilter. В наследнике мы реализуем логику метода GetFiltratedList(). То есть текущий фильтр будет возвращать коллекцию, у всех сотрудников которой значение свойства __Organization__ равно __Norbit__.\n```C#\n/// \u003csummary\u003e\n/// Фильтр сотрудников.\n/// \u003c/summary\u003e\npublic class NorbitWorkersFilter : WorkersFilter\n{\n\t/// \u003csummary\u003e\n\t/// Сотрудники.\n\t/// \u003c/summary\u003e\n\tprotected static List\u003cWorker\u003e Workers; \n\n\t/// \u003csummary\u003e\n\t/// Название организации по умолчанию.\n\t/// \u003c/summary\u003e\n\tprivate string _defaultOrganization = \"Норбит\";\n\n\t/// \u003csummary\u003e\n\t/// Создание фильтра сотрудников с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"workers\"\u003eСписок сотрудников.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eСписок сотрудников или его элементы равны null!\u003c/exception\u003e\n\tpublic NorbitWorkersFilter(List\u003cWorker\u003e workers)\n\t{\n\t\tif (workers == null || workers.FindIndex(worker =\u003e worker == null) != -1)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(workers), \"Список сотрудников или его элементы равны null!\");\n\t\t}\n\n\t\tWorkers = workers;\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Получение отфильтрованного списка сотрудников.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eОтфильтрованный список сотрудников.\u003c/returns\u003e\n\tpublic override List\u003cWorker\u003e GetFiltratedList() =\u003e Workers\n\t\t.Where(worker =\u003e worker.Organization == _defaultOrganization)\n\t\t.ToList();\n}\n```\n:three: Создаем класс дополнительного условия фильтрации, который будет классом-наследником класс NorbitWorkersFilter. Он будет создаваться с помощью конструктора, который будет приниматт объект типа NorbitWorkersFilter. \n```C#\n/// \u003csummary\u003e\n/// Дополнительное условие фильтрации.\n/// \u003c/summary\u003e\npublic class AdditionalFilteringCondition : NorbitWorkersFilter\n{\n\t/// \u003csummary\u003e\n\t/// Фильтр сотрудников Норбит.\n\t/// \u003c/summary\u003e\n\tprotected NorbitWorkersFilter _filter; \n\n\t/// \u003csummary\u003e\n\t/// Создает дополнительное условие фильтрации с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"filter\"\u003eФильтр сотрудников Норбит.\u003c/param\u003e\n\tpublic AdditionalFilteringCondition(NorbitWorkersFilter filter)\n\t\t: base(Workers)\n\t{\n\t\tif (filter == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(filter), \"Фильтр равен null!\");\n\t\t}\n\n\t\t_filter = filter;\n\t\tWorkers = base.GetFiltratedList();\n\t}\n}\n```\n\u003e Перед тем, как список будет отфильтрован дополнительным условием, он сначала будет отфильтрован фильтров базового метода с помощью __base.GetFiltratedList()__\n\n:four: Добавим классы, которые будут фильтровать сотрудников по возрасту и должности. \n```C#\n/// \u003csummary\u003e\n/// Фильтр сотрудников по возрасту.\n/// \u003c/summary\u003e\npublic class AgeWorkersFilter : AdditionalFilteringCondition\n{\n\t/// \u003csummary\u003e\n\t/// Минимальное допустимое значение возраста.  \n\t/// \u003c/summary\u003e\n\tprivate int _defaultMinCorrectAge = 25; \n\n\t/// \u003csummary\u003e\n\t/// Создание фильтра сотрудников по возрасту с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"filter\"\u003eБазовый фильтр сотрудников Норбит.\u003c/param\u003e\n\tpublic AgeWorkersFilter(NorbitWorkersFilter filter)\n\t\t: base(filter)\n\t{\n\t\tWorkers = filter.GetFiltratedList();\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Получение отфильтрованного списка сотрудников.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eОтфильтрованный список сотрудников.\u003c/returns\u003e\n\tpublic override List\u003cWorker\u003e GetFiltratedList() =\u003e Workers\n\t\t.Where(worker =\u003e worker.Age \u003e= _defaultMinCorrectAge)\n\t\t.ToList();\n}\n```\n\u003e По такому же принципу реализован класс PostWorkersFilter. Для подробного ознакомления рекомендую перейти в репозиторий. Также с помощью консольного приложения вы можете наблюдать снижения количества сотрудников в коллекции по мере добавления к базовому фильтру сотрудников дополнительный оберток.\n\n:white_check_mark: __Преимущества паттерна Decorator__: возможность добавлять или удалять функционал из экземпляра класса во время выполнения, благодаря оберткам объединить несколько возможных вариантов поведения объекта.\u003cbr\u003e\n:x: __Недостатки__: в результате получается большое число мелких объектов, которые друг на друга похожи и отличаются способом взаимосвязи.\n___\n### Компоновщик \n__Компоновщик (Composite)__ - это структурный паттерн проектирования, который используется, когда объекты должны быть реализованы в виде древовидной структуры и когда клиенты аналогично управляют как целыми объектами, так и составными частями.\u003cbr\u003e\n\u003e :white_check_mark: Реализуем данный паттерн на примере файловой системы.\n\n:one: Создадим абстрактный класс компонента файловой системы, единственное поле у которого будет название компонента. Мы также можем добавлять в компонент другие компоненты и аналогично удалять их.\u003cbr\u003e\n```C#\n/// \u003csummary\u003e\n/// Компонент файловой системы.\n/// \u003c/summary\u003e\npublic abstract class FileSystemComponent\n{\n        /// \u003csummary\u003e\n        /// Название.\n        /// \u003c/summary\u003e\n        protected string _name;\n\n        /// \u003csummary\u003e\n        /// Создание компонента файловой системы с помощью указанных параметров.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"name\"\u003eНазвание компонента файловой системы.\u003c/param\u003e\n        public FileSystemComponent(string name)\n        {   \n            Validator.ValidateStringText(name);\n            \n            _name = name;\n        }\n\n        /// \u003csummary\u003e\n        /// Проверка корректности компонента.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"component\"\u003eКомпонент, который необходимо проверить.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eКомпонент равен null!\u003c/exception\u003e\n        protected void ValidateComponent(FileSystemComponent component)\n        {\n            if (component == null)\n            {\n                throw new ArgumentNullException(nameof(component), \"Компонент равен null!\");\n            }\n        }\n\n        /// \u003csummary\u003e\n        /// Добавление компонента.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"component\"\u003eКомпонент, который необходимо добавить.\u003c/param\u003e\n        public virtual void Add(FileSystemComponent component) \n        {   \n            ValidateComponent(component);\n        }\n\n        /// \u003csummary\u003e\n        /// Добавление компонента.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"component\"\u003eКомпонент, который необходимо добавить.\u003c/param\u003e\n        public virtual void Remove(FileSystemComponent component) \n        {\n            ValidateComponent(component);\n        }\n\n        /// \u003csummary\u003e\n        /// Строковое преставление объекта компонента файловой системы.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные объекта компонента файловой системы в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e _name;\n}\n```\n:two: Создадим первый класс-наследник компонента файловой системы: файл. У файла не будут переопределены методы добавления и удаления - базовая реализация нас вполне устраивает, поскольку мы не может добавлять в файл другие файлы.\n```C#\n/// \u003csummary\u003e\n/// Файл.\n/// \u003c/summary\u003e\npublic class File : FileSystemComponent\n{\n        /// \u003csummary\u003e\n        /// Создание файла с помощью указанных параметров.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"name\"\u003eНазвание файла.\u003c/param\u003e\n        public File(string name) \n            : base(name)\n        {\n        }\n}\n```\n:three: Добавим второй класс-наследник компонента файловой системы: папка. У папки будет список компонентов файловой системы, поскольку в папку мы уже можем добавить как другие файлы, так и другие папки. Также будут переопределены методы добавления и удаления компонентов файловой системы из директории.\n```C#\n/// \u003csummary\u003e\n/// Папка.\n/// \u003c/summary\u003e\npublic class Directory : FileSystemComponent\n{\n        /// \u003csummary\u003e\n        /// Список компонентов файловой системы, которые находятся в папке.\n        /// \u003c/summary\u003e\n        private List\u003cFileSystemComponent\u003e _components = new List\u003cFileSystemComponent\u003e(); \n\n        /// \u003csummary\u003e\n        /// Создает папку с помощью указанных параметров.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"name\"\u003eНазвание папки.\u003c/param\u003e\n        public Directory(string name) \n            : base(name)\n        {\n        }\n\n        /// \u003csummary\u003e\n        /// Добавление компонента.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"component\"\u003eКомпонент, который необходимо добавить.\u003c/param\u003e\n        public override void Add(FileSystemComponent component)\n        {\n            ValidateComponent(component);\n\n            _components.Add(component);\n        }\n\n        /// \u003csummary\u003e\n        /// Добавление компонента.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"component\"\u003eКомпонент, который необходимо добавить.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eУказанный компонент файловой системы отсутствует в директории!\u003c/exception\u003e\n        public override void Remove(FileSystemComponent component)\n        {\n            ValidateComponent(component);\n\n            if (!_components.Contains(component))\n            {\n                throw new ArgumentNullException(\"Указанный компонент файловой системы отсутствует в директории!\");\n            }\n\n            _components.Remove(component);\n        }\n\n        /// \u003csummary\u003e\n        /// Строковое преставление объекта компонента файловой системы.\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003eДанные объекта компонента файловой системы в виде строки.\u003c/returns\u003e\n        public override string ToString() =\u003e $\"{_name}: {string.Join(\"=\u003e\", _components)}{Environment.NewLine}\";\n}\n```\n:white_check_mark: __Преимущества паттерна Composite__: упрощение работы с деревом компонентов, также более удобно добавлять в программу новые компоненты.\u003cbr\u003e\n:x: __Недостатки__: слишком общий интерфейс для классов, функциональность которых может сильно отличаться.\n___\n### Фасад\n__Фасад (Facade)__ - \n## Поведенческие паттерны\n__Поведенческие паттерны__ (Behavioral) описывают способы реализации взаимодействия между объектами с отличающимися типами. При таком взаимодействии объекты могут решать более трудные задачи, чем если бы они решали их по-отдельности.\n___\n### Итератор\n__Итератор (Iterator)__ - это поведенческий паттерн проектирования, благодаря которому у нас есть возможность последовательно обходить элементы составных объектов, при этом не раскрывая их внутреннего представления.\u003cbr\u003e\nИдея паттерна в том, чтобы вынести поведение обхода коллекции из самой коллекции в __отдельный класс__.\u003cbr\u003e\n\u003e :white_check_mark: Зная эту информацию, давайте теперь его реализуем.\n\nПусть у нас будет файловая система, которая будет хранить файлы. У каждого файла есть следующие свойства:\n```C#\n/// \u003csummary\u003e\n/// Файл.\n/// \u003c/summary\u003e\npublic class File\n{\n\t/// \u003csummary\u003e\n\t/// Идентификатор.\n\t/// \u003c/summary\u003e\n\tpublic Guid Id { get; set; }\n\n\t/// \u003csummary\u003e\n\t/// Название.\n\t/// \u003c/summary\u003e\n\tpublic string Name { get; set; }\n\n\t/// \u003csummary\u003e\n\t/// Тип.\n\t/// \u003c/summary\u003e\n\tpublic string Type { get; set; }\n\n\t/// \u003csummary\u003e\n\t/// Строковое преставление объекта файла.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eДанные объекта файла в виде строки.\u003c/returns\u003e\n\tpublic override string ToString() =\u003e $\"Идентификатор: {Id} Название: {Name} Тип: {Type}\";\n}\n```\n:one: Создадим интерфейс IFileIterator итератора для файловой системы:\n```C#\n/// \u003csummary\u003e\n/// Содержит методы для итератора файловой системы.\n/// \u003c/summary\u003e\npublic interface IFileIterator\n{\n\t/// \u003csummary\u003e\n\t/// Проверяет, есть ли в коллекции следующий элемент.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРезультат проверки.\u003c/returns\u003e\n\tbool HasNext();\n\n\t/// \u003csummary\u003e\n\t/// Получает следующий элемент.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСледующий элемент.\u003c/returns\u003e\n\tFile Next();\n}\n```\n:two: Создадим интерфейс IFileNumerator, содержащий методы получения итератора из коллекции: \n```C#\n/// \u003csummary\u003e\n/// Содержит методы получения итератора из коллекции.\n/// \u003c/summary\u003e\npublic interface IFileNumerable\n{\n\t/// \u003csummary\u003e\n\t/// Создание итератора.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСозданный итератор.\u003c/returns\u003e\n\tIFileIterator CreateNumerator(); \n\n\t/// \u003csummary\u003e\n\t/// Количество элементов в коллекции.\n\t/// \u003c/summary\u003e\n\tint Count { get; }\n\n\t/// \u003csummary\u003e\n\t/// Получение элемента из коллекции по индексу.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"index\"\u003eИндекс элемента, который необходимо получить.\u003c/param\u003e\n\t/// \u003creturns\u003eЭлемент по индексу.\u003c/returns\u003e\n\tFile this[int index] { get; }\n}\n```\n:three: Теперь мы можем создать конкретную файловую систему, реализующую интерфейс IFileNumerable:\n```C#\n/// \u003csummary\u003e\n/// Файловая система.\n/// \u003c/summary\u003e\npublic class FileSystem : IFileNumerable\n{\n\t/// \u003csummary\u003e\n\t/// Файлы, хранящиеся в файловой системе.\n\t/// \u003c/summary\u003e\n\tprivate List\u003cFile\u003e _files;\n\n\t/// \u003csummary\u003e\n\t/// Количество файлов в файловой системе.\n\t/// \u003c/summary\u003e\n\tpublic int Count =\u003e _files.Count;\n\n\t/// \u003csummary\u003e\n\t/// Создание файловой системы.\n\t/// \u003c/summary\u003e\n\tpublic FileSystem()\n\t{\n\t\t_files = new List\u003cFile\u003e();\n\t}\n \n\t/// \u003csummary\u003e\n\t/// Проверяет выход индекса за границы списка файлов файловой системы.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"index\"\u003eПорядковый номер элемента.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentOutOfRangeException\"\u003eИндекс вышел за границы!\n\t/// \u003c/exception\u003e\n\tprivate void ValidateIndex(int index)\n\t{\n\t\tif (index \u003c 0 || index \u003e= _files.Count)\n\t\t{\n\t\t\tthrow new ArgumentOutOfRangeException(\"Индекс вышел за границы массива!\");\n\t\t}\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Создание итератора.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eИтератор.\u003c/returns\u003e\n\tpublic IFileIterator CreateNumerator() =\u003e new FileSystemNumerator(this); // Данный класс мы создадим далее.\n\n\t/// \u003csummary\u003e\n\t/// Доступ к элементам файловой системы.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"index\"\u003eПозиция элемента, к которому необходим доступ.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentOutOfRangeException\"\u003eИндекс вышел за границы!\u003c/exception\u003e\n\tpublic File this[int index]\n\t{\n\t\tget\n\t\t{\n\t\t\tValidateIndex(index);\n\t\t\treturn _files[index];\n\t\t}\n\t}\n}\n```\n:four: Теперь последний шаг - реализуем класс-алгоритм обхода файловой системы, реализующий интерфейс IFileIterator.\n```C#\n/// \u003csummary\u003e\n/// Реализует алгоритм обхода файловой системы.\n/// \u003c/summary\u003e\npublic class FileSystemNumerator : IFileIterator\n{\n\t/// \u003csummary\u003e\n\t/// Содержит методы для создания объекта-итератора.\n\t/// \u003c/summary\u003e\n\tprivate IFileNumerable _aggregate;\n\n\t/// \u003csummary\u003e\n\t/// Индекс текущего элемента.\n\t/// \u003c/summary\u003e\n\tprivate int _index = 0;\n\n\t/// \u003csummary\u003e\n\t/// Создание итератора файловой системы с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"aggregate\"\u003eСодержит методы для создания объекта-итератора.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eИнтерфейс для создания объекта-итератора равен null!\u003c/exception\u003e\n\tpublic FileSystemNumerator(IFileNumerable aggregate)\n\t{\n\t\tif (aggregate == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(aggregate), \n\t\t\t\t\"Интерфейс для создания объекта-итератора равен null!\");\n\t\t}\n\n\t\t_aggregate = aggregate;\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Проверяет наличие следующего элемента.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eРезультат проверки.\u003c/returns\u003e\n\tpublic bool HasNext() =\u003e _index \u003c _aggregate.Count;\n\n\t/// \u003csummary\u003e\n\t/// Получение следующего элемента из файловой системы.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eСледующий файл.\u003c/returns\u003e\n\t/// \u003cexception cref=\"ArgumentOutOfRangeException\"\u003eИндекс вышел за границы!\u003c/exception\u003e\n\tpublic File Next()\n\t{\n\t\tif (!HasNext())\n\t\t{\n\t\t\tthrow new ArgumentOutOfRangeException(\"Индекс вышел за границы!\");\n\t\t}\n\n\t\treturn _aggregate[_index++];\n\t}\n}\n```\n:white_check_mark: __Преимущества паттерна Iterator__: достигается упрощение классов хранения данных.\u003cbr\u003e\n:x: __Недостатки__: Если вы работаете только с простыми коллекциями, то вам нет необходимости использовать данный паттерн.\n___\n### Наблюдатель\n__Наблюдатель (Observer)__ - поведенческий шаблон проектирования. Определяет зависимость типа «один ко многим» таким образом, что при изменении объекта все, зависящие от него, получают сообщение об этом событии. \u003cbr\u003e\nВ dotnet есть три способа реализации данного паттерна:\u003cbr\u003e\u003cbr\u003e\n:one: __Через делегаты.__ Данный способ гарантирует наличие наблюдателя и подходит, когда нужно реализовать отношение: 1 поставщик – 1 наблюдатель. Также при данном подходе можно получить результат – ответ от подписчика.\u003cbr\u003e\n:two: __Через события.__ Любое число подписчиков. Нет гарантии наличия подписчиков. Не предусмотрено получение ответа от подписчика.\u003cbr\u003e\n:three: __Через набор интерфейсов IObserver__ (механизм для получения push-уведомлений)/IObservable (определяет поставщика push-уведомлений).\u003cbr\u003e\n\u003e :x: Почему стоит использовать эти интерфейсы вместо событий: события плохо поддаются тестированию, данный паттерн универсален, он может использоваться и в других языках программирования. В C# есть события, а в других языках программирования их может и не быть.\n\n:grey_exclamation: Таким образом, у нас есть __IObservable__ – определяет наблюдаемый объект и __IObserver__ – определяет наблюдателей.\u003cbr\u003e\u003cbr\u003e\nРеализуем паттерн __наблюдатель__ на примере __корпоративного портала для сотрудников__. У нас будут пользователи корпоративного портала - сотрудники компании. \u003cbr\u003e\nСотрудники могут подписываться на уведомления о каких-либо новостях, событиях, которые будут публиковаться на корпоративном портале.\u003cbr\u003e\nСоответственно, все пользователи, которые подписаны на уведомления корпоративного портала (подписчики) будут уведомлены о каком-либо событии. В данном случае корпоративный портал будет __поставщиком данных__ для наших подписчиков. \u003cbr\u003e\u003cbr\u003e\n:one: Создаем класс Message, который будет являться объектом сообщения (уведомления), которое будут получать подписчики от поставщика данных - в нашем случае корпоративного портала.\n```C#\n/// \u003csummary\u003e\n/// Сообщение.\n/// \u003c/summary\u003e\npublic class Message\n{\n\t/// \u003csummary\u003e\n\t/// Текст сообщения.\n\t/// \u003c/summary\u003e \n\tprivate string _text;\n\n\t/// \u003csummary\u003e\n\t/// Автор сообщения.\n\t/// \u003c/summary\u003e\n\tprivate string _author;\n\n\t/// \u003csummary\u003e\n\t/// Создание сообщения с помощью указанных данных.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"text\"\u003eТекст сообщения.\u003c/param\u003e\n\t/// \u003cparam name=\"author\"\u003eАвтор сообщения.\u003c/param\u003e\n\tpublic Message(string text, string author)\n\t{\n\t\tValidator.ValidateStringText(text);\n\t\tValidator.ValidateStringText(author);\n\n\t\t_text = text;\n\t\t_author = author;\n\t}\n \n\t/// \u003csummary\u003e\n\t/// Строковое представление объекта сообщения.\n\t/// \u003c/summary\u003e\n\t/// \u003creturns\u003eДанные сообщения в виде строки.\u003c/returns\u003e\n\tpublic override string ToString() =\u003e $\"Текст сообщения: {Environment.NewLine}{_text}\" +\n\t\t$\"{Environment.NewLine}Автор: {_author}\";\n}\n```\n:two: Далее мы создаем объект пользователя, реализующего интерфейс IObserver, поскольку он является наблюдателем. Параметром интерфейса выступает __Message__ - тип данных уведопления, которое будут получать подписчики.\u003cbr\u003e\nРеализуя данный интерфейс, нам необходимо реализовать логику работы методов __OnCompleted, OnError, OnNext__.\n```C#\n/// \u003csummary\u003e\n/// Пользователь.\n/// \u003c/summary\u003e\npublic class User : IObserver\u003cMessage\u003e\n{\n\t/// \u003csummary\u003e\n\t/// Логин пользователя.\n\t/// \u003c/summary\u003e\n\tprivate string _login; \n\n\t/// \u003csummary\u003e\n\t/// Создает пользователя с помощью указанных параметров.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"login\"\u003eЛогин пользователя.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eЛогин равен null!\u003c/exception\u003e\n\tpublic User(string login)\n\t{\n\t\tValidator.ValidateStringText(login);\n\n\t\t_login = login;\n\t}\n \n\t/// \u003csummary\u003e\n\t/// Обработчик события, когда от поставщика данных больше не будет поступать никаких уведомлений.\n\t/// \u003c/summary\u003e\n\tpublic void OnCompleted()\n\t{\n\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Обработчик события возникновения исключения у поставщика данных при отправке уведомлений.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"error\"\u003eИсключение, которое возникло у поставщика данных.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eИсключение равно null!\u003c/exception\u003e\n\tpublic void OnError(Exception error)\n\t{\n\t\tif (error == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(error), \"Исключение равно null!\");\n\t\t}\n\n\t\tConsole.WriteLine($\"Отправка уведомлений пользователю завершилась с ошибкой: {error.Message}\" +\n\t\t\t$\"{Environment.NewLine}Логин получателя: {_login}\");\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Обработчик события поступления уведомлений от поставщика данных.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"value\"\u003eСообщение, поступившее от поставщика данных.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eСообщение равно null!\u003c/exception\u003e\n\tpublic void OnNext(Message value)\n\t{\n\t\tif (value == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(value), \"Сообщение равно null!\");\n\t\t}\n\n\t\tConsole.WriteLine($\"Полученное уведомление: {Environment.NewLine}{value}{Environment.NewLine}\" +\n\t\t\t$\"Логин получателя: {_login}\");\n\t}\n }\n```\n:three: Теперь создадим класс нашего корпоративного портала, реализующий интерфейс IObservable (наблюдаемый объект). В качестве параметра также выступает тип данных Message - тип сообщения для подписчиков.\n```C#\n/// \u003csummary\u003e\n/// Корпоративный портал.\n/// \u003c/summary\u003e\npublic class CorporatePortal : IObservable\u003cMessage\u003e\n{\n\t/// \u003csummary\u003e\n\t/// Список подписчиков.\n\t/// \u003c/summary\u003e\n\tprivate readonly List\u003cIObserver\u003cMessage\u003e\u003e _observers; \n\n\t/// \u003csummary\u003e\n\t/// Создание корпоративного портала.\n\t/// \u003c/summary\u003e\n\tpublic CorporatePortal()\n\t{\n\t\t_observers = new List\u003cIObserver\u003cMessage\u003e\u003e();\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Подписка на уведомления.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"observer\"\u003eПодписчик.\u003c/param\u003e\n\t/// \u003creturns\u003eОбъект с механизмом освобождения неуправляемых ресурсов.\u003c/returns\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eПодписчик равен null!\u003c/exception\u003e\n\tpublic IDisposable Subscribe(IObserver\u003cMessage\u003e observer)\n\t{\n\t\tif (observer == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(observer), \"Подписчик равен null!\");\n\t\t}\n\n\t\t_observers.Add(observer);\n\n\t\treturn new Unsubscriber\u003cMessage\u003e(_observers, observer); // Данные класс будет реализован далее.\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Отправляет уведомление всем подписчикам.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"message\"\u003eСообщение, которое будет отправлено всем подписчикам.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eСообщение равно null!\u003c/exception\u003e\n\tpublic void Notify(Message message)\n\t{\n\t\tif (message == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(message), \"Сообщение равно null!\");\n\t\t}\n\n\t\tforeach (var observer in _observers)\n\t\t{\n\t\t\tobserver.OnNext(message);\n\t\t}\n\t}\n}\n```\n\u003e Несколько комментариев касательно Unsubscriber: нам необходимо, чтобы помимо подписки на событие, у пользователя была возможность и отписаться от события. В Unsubscriber должен храниться список всех подписчиков и конкретный подписчик, с которым будет происходить взаимодействие.\n\n:four: Теперь давайте реализуем данный класс.\u003cbr\u003e\u003cbr\u003e\n:pushpin: __Обратите внимание__, что он должен реализовывать интерфейс __IDisposable__, в котором содержится метод Dispose - именно так будет происходить отписка пользователя от уведомлений корпоративного портала.\n```C#\n/// \u003csummary\u003e\n/// Работает с отписками от уведомлений.\n/// \u003c/summary\u003e\n/// \u003ctypeparam name=\"T\"\u003eТип подписчика.\u003c/typeparam\u003e\npublic class Unsubscriber\u003cT\u003e : IDisposable\n{\n\t/// \u003csummary\u003e\n\t/// Список подписчиков.\n\t/// \u003c/summary\u003e\n\tprivate readonly List\u003cIObserver\u003cT\u003e\u003e _observers; \n\n\t/// \u003csummary\u003e\n\t/// Подписчик.\n\t/// \u003c/summary\u003e\n\tprivate readonly IObserver\u003cT\u003e _observer;\n\n\t/// \u003csummary\u003e\n\t/// Создание экземпляра для отписок от уведомлений с помощью указанных данных.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"observers\"\u003eПодписчики.\u003c/param\u003e\n\t/// \u003cparam name=\"observer\"\u003eПодписчик.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eПодписчики равны null!\u003c/exception\u003e\n\tpublic Unsubscriber(List\u003cIObserver\u003cT\u003e\u003e observers, IObserver\u003cT\u003e observer)\n\t{\n\t\tif (observers == null || observers.FindIndex(subscriber =\u003e subscriber == null) != -1)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(observers),\n\t\t\t\t\"Список подписчиков или его элементы равны null!\");\n\t\t}\n\n\t\tif (observer == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(observer), \"Подписчик равен null!\");\n\t\t}\n\n\t\tif (!observers.Contains(observer))\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(observer),\n\t\t\t\t\"Подписчик не найден в списке подписчиков!\");\n\t\t}\n\n\t\t_observer = observer;\n\t\t_observers = observers;\n\t}\n \n\t/// \u003csummary\u003e\n\t/// Отписка подписчика от уведомлений.\n\t/// \u003c/summary\u003e\n\tpublic void Dispose()\n\t{\n\t\tif (_observers.Contains(_observer))\n\t\t{\n\t\t\t_observers.Remove(_observer);\n\t\t}\n\t}\n}\n```\nОтписка у нас происходит следующим образом: мы удаляем подписчика из нашей коллекции подписчиков, соответственно, ему больше не будут приходить уведомления корпоративного портала.\u003cbr\u003e\u003cbr\u003e\n:white_check_mark: __Преимущества паттерна Observer__: Можно создавать новые классы подписчиков. При этот класс наблюдаемого объекта как-то изменять не нужно.\u003cbr\u003e\n:x: __Недостатки__: Подписчики уведомляются в произвольном порядке.\n___\n### Посредник \n__Посредник (Mediator)__ - это поведенческий паттерн проектирования, бгагодаря которому уменьшается связанность множества классов между собой. Это достигается за счет перемещения этих связей в один класс-посредник.\u003cbr\u003e\nСамое популярное применение посредника в C# коде – это связь нескольких компонентов __GUI__ одной программы. __Аналогия из жизни__: пилоты не общаются напрямую друг с другом, а через диспетчера.\u003cbr\u003e\u003cbr\u003e\nДавайте попробуем реализовать данный паттерн на __следующем примере__: пусть у нас есть какая-то IT-компания, в которой есть программист и тимлид. Тимлид не скидывает лично (напрямую) программисту задачу, например, в социальных сетях. Он публикует ее в __TFS__ (Team Foundation Server). \u003cbr\u003e \n\nПрограммист заходит на TFS, чтобы проверить, не появилось ли для него задач. Если задачи есть - берет их в работу. После выполнения задачи, программист переводит ее в \"ожидающие проверки\". Тимлид также заходит на TFS, чтобы проверить, выполнил ли программист задачи, которые он выдал. Если сделал - проверяет их. Если есть недочеты - отправляет на доработку, если недочетов нет - закрывает задачу. Также тимлид может дать программисту новые задачи для выполнения, если таковые имеются. \u003cbr\u003e\n\n\u003e В нашем примере посредником (Mediator) будет является TFS между программистом и тимлидом.\n\nТеперь реализуем это в коде.\u003cbr\u003e\n:one: Создадим интерфейс __IMediator__ - он будет содержать методы, которыми будет обладать класс-посредник. С помощью метода __Notify__ посредник будет уведомлять обе стороны о каком-либо событии.\n```C#\n/// \u003csummary\u003e\n/// Содержит методы для посредника.\n/// \u003c/summary\u003e\npublic interface IMediator\n{\n\t/// \u003csummary\u003e\n\t/// Обрабатывает уведомления.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"worker\"\u003eРаботник.\u003c/param\u003e\n\t/// \u003cparam name=\"message\"\u003eСообщение.\u003c/param\u003e\n\tvoid Notify(Worker worker, string message);\n}\n```\n:two: Теперь реализуем абстрактный класс Worker - это будет наш сотрудник. Он будет хранить посредника - его можно будет установить в методе __SetMediator__:\n```C#\n/// \u003csummary\u003e\n/// Работник.\n/// \u003c/summary\u003e\npublic abstract class Worker\n{\n\t/// \u003csummary\u003e\n\t/// Посредник.\n\t/// \u003c/summary\u003e\n\tprotected IMediator _mediator;\n\n\t/// \u003csummary\u003e\n\t///Устанавливает посредника для работника.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"mediator\"\u003eПосредник.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eПосредник равен null!\u003c/exception\u003e\n\tpublic void SetMediator(IMediator mediator)\n\t{\n\t\tif (mediator == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(mediator), \"Посредник равен null!\");\n\t\t}\n\n\t\t_mediator = mediator;\n\t}\n}\n```\n:three: Теперь создадим первый класс-наследник нашего Worker: это будет класс Programmer. У него будет два метода: начать работу и закончить работу. Причем программисту нельзя давать новую задачу, пока он не завершил предыдущую. \n```C#\n/// \u003csummary\u003e\n/// Программист.\n/// \u003c/summary\u003e\npublic class Programmer : Worker\n{\n\t/// \u003csummary\u003e\n\t/// Текст задачи,над которой работает программист.\n\t/// \u003c/summary\u003e\n\tprivate string _taskText = string.Empty;\n\n\t/// \u003csummary\u003e\n\t/// Получение текста задания.\n\t/// \u003c/summary\u003e\n\tpublic string TaskText =\u003e _taskText;\n\n\t/// \u003csummary\u003e\n\t/// Начинает работу над задачей.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"taskText\"\u003eТекст задачи.\u003c/param\u003e\n\tpublic void StartWork(string taskText)\n\t{\n\t\tValidator.ValidateStringText(taskText);\n\n\t\tif (_taskText.Length != 0)\n\t\t{\n\t\t\tif (_mediator != null)\n\t\t\t{\n\t\t\t\t_mediator.Notify(this, \"Программист уже работает над задачей!\");\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t_taskText = taskText;\n\n\t\tif (_mediator != null)\n\t\t{\n\t\t\t_mediator.Notify(this, $\"Программист начал работу над задачей: {taskText}\");\n\t\t}\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Завершает работу над задачей.\n\t/// \u003c/summary\u003e\n\tpublic void FinishWork()\n\t{\n\t\tif (_mediator != null)\n\t\t{\n\t\t\t_mediator.Notify(this, $\"Программист завершил работу над задачей: {_taskText}\");\n\t\t}\n\t\t_taskText = string.Empty;\n\t}\n}\n```\n:four: Вторым классом-наследником будет наш Тимлид. У него будет метод __GiveTask__ - выдать задачу. \n```C#\n/// \u003csummary\u003e\n/// Тимлид.\n/// \u003c/summary\u003e\npublic class TeamLead : Worker\n{\n\t/// \u003csummary\u003e\n\t/// Дать задание.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"taskText\"\u003eТекст задания.\u003c/param\u003e\n\tpublic void GiveTask(string taskText)\n\t{\n\t\tValidator.ValidateStringText(taskText);\n\n\t\tif (_mediator != null)\n\t\t{\n\t\t\t_mediator.Notify(this, \"Выдаю задачу программисту: \" + taskText);\n\t\t}\n\t}\n}\n```\n:five: Теперь мы можем написать класс TFS, который будет реализовывать интерфейс посредника IMediator. Класс внутри будет содержать объекты тимлида и программиста, посредниками которых он является. Также у нас будет список задач. С помощью метода AddTask мы можем добавить задачу в список. В конструкторе мы не только инициализируем тимлида и программиста, но и устанавливаем им текущего посредника.\u003cbr\u003e\u003cbr\u003e\nЛогика работы метода Notify следующая: если уведомление посреднику отправляет тимлид, то это означает, что он дает задачу сотруднику, значит сотрудник должен приступить к работе. Если уведомление приходит от сотрудника, то это означает, что он выполнил задачу и тимлид дает ему новую задачу: он будет давать новые задачи до тех пор, пока список задач не станет пустым.\n```C#\n/// \u003csummary\u003e\n/// TFS.\n/// \u003c/summary\u003e\npublic class TFS : IMediator\n{\n\t/// \u003csummary\u003e\n\t/// Программист.\n\t/// \u003c/summary\u003e\n\tprivate Programmer _programmer;\n\n\t/// \u003csummary\u003e\n\t/// Тимлид.\n\t/// \u003c/summary\u003e\n\tprivate TeamLead _teamLead;\n\n\t/// \u003csummary\u003e\n\t/// Задачи.\n\t/// \u003c/summary\u003e\n\tprivate List\u003cstring\u003e _tasks;\n\n\t/// \u003csummary\u003e\n\t/// Создает TFS с помощью указанных данных.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"programmer\"\u003eПрограммист.\u003c/param\u003e\n\t/// \u003cparam name=\"teamLead\"\u003eТимлид.\u003c/param\u003e\n\t/// \u003cexception cref=\"ArgumentNullException\"\u003eПрограммист или тимлид равен null!\u003c/exception\u003e\n\tpublic TFS(Programmer programmer, TeamLead teamLead)\n\t{\n\t\tif (programmer == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(programmer), \"Программист равен null!\");\n\t\t}\n\n\t\tif (teamLead == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(teamLead), \"Тимлид равен null!\");\n\t\t}\n\n\t\t_programmer = programmer;\n\t\t_teamLead = teamLead;\n\t\tprogrammer.SetMediator(this);\n\t\tteamLead.SetMediator(this);\n\t\t_tasks = new List\u003cstring\u003e();\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Обрабатывает уведомления.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"worker\"\u003eРаботник.\u003c/param\u003e\n\t/// \u003cparam name=\"message\"\u003eСообщение.\u003c/param\u003e\n\tpublic void Notify(Worker worker, string message)\n\t{\n\t\tif (worker == null)\n\t\t{\n\t\t\tthrow new ArgumentNullException(nameof(worker), \"Работник равен null!\");\n\t\t}\n\n\t\tValidator.ValidateStringText(message);\n\n\t\tConsole.WriteLine(message);\n\n\t\tif (worker is Programmer)\n\t\t{\n\t\t\tif (message.StartsWith(\"Программист завершил работу над задачей\"))\n\t\t\t{\n\t\t\t\tif (_tasks.Count != 0)\n\t\t\t\t{\n\t\t\t\t\t_teamLead.GiveTask(_tasks[0]);\n\t\t\t\t\t_tasks.RemoveAt(0);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (worker is TeamLead)\n\t\t{\n\t\t\t_programmer.StartWork(message);\n\t\t}\n\t}\n\n\t/// \u003csummary\u003e\n\t/// Добавить задачу.\n\t/// \u003c/summary\u003e\n\t/// \u003cparam name=\"taskText\"\u003eТекст задачи.\u003c/param\u003e\n\tpublic void AddTask(string taskText)\n\t{\n\t\tValidator.ValidateStringText(taskText);\n  \n\t\t_tasks.Add(taskText);\n\t}\n}\n```\n:white_check_mark: __Преимущества паттерна Mediator__: Достигается устранение зависимости между компонентами, благодаря чему их можно повторно использовать, более удобным становится взаимодействие между компонентами, также управление компонентами централизовано.\u003cbr\u003e\n:x: __Недостатки__: Код посредника может быть очень большим.\n___\n### Шаблонный метод\n__Шаблонный метод__ (Template Method) - это поведенческий паттерн проектирования, который определяет общий алгоритм поведения подклассов. При этом подклассы имеют возможность переопределять части этого алгоритма, не меняя при этом его общей структуры.\n\u003e :white_check_mark: Если бы мы не использовали данный паттерн, то нам приходилось бы явно прописывать реализацию алгоритма в каждой подклассе, несмотря на то, что алгоритмы в этих подклассах имеют небольшие различия.\n\nРассмотрим реализацию шаблонного метода на конкретном примере: пусть у нас будет бухгалтерия, в котором выдают зарплату для сотрудников.\u003cbr\u003e\n:one: Реализуем класс Worker, содержащий необходимые свойства, которыми будет обладать каждый сотрудник. \n```C#\n/// \u003csummary\u003e\n/// Сотрудник.\n/// \u003c/summary\u003e\npublic class Worker\n{\n        /// \u003csummary\u003e\n        /// Идентификатор.\n        /// \u003c/summary\u003e\n        public Guid Id { get; set; }\n\n        /// \u003csummary\u003e\n        /// Имя.\n        /// \u003c/summary\u003e\n        public string FirstName { get; set; }\n\n        /// \u003csummary\u003e\n        /// Фамилия.\n        /// \u003c/summary\u003e\n        public string LastName { get; set; }    \n\n        /// \u003csummary\u003e\n        /// Отчество.\n        /// \u003c/summary\u003e\n        public string Patronymic { get; set; }\n\n        /// \u003csummary\u003e\n        /// Должность.\n        /// \u003c/summary\u003e\n        public string Post { get; set; }\n\n        /// \u003csummary\u003e\n        /// Выплачена ли зарплата.\n        /// \u003c/summary\u003e\n        public bool IsSalaryPaid { get; set; }\n}\n```\n:two: Реализуем абстрактный класс бухгалтерии, в которой из полей будет список сотрудников типа Worker, которые работают в конкретной компании и словарь, в котором в зависимости от должности указана зарплата, которую должен получать сотрудник, занимая определенную должность.\u003cbr\u003e\u003cbr\u003e \nНам необходимо выдать зарплату сотруднику - за это отвечает метод __GetSalary__. Алгоритм выдачи зарплаты следующий: \u003cbr\u003e\n1. Сначала вызывается метод __ValidateWorkerId__, проверяющий корректности идентификатора сотрудника, которому необходимо выдать зарплату - он не должен быть null.\u003cbr\u003e\n2. Далее вызывается метод __ValidateWorkerExistence__, проверяющий существование сотрудника с указанным идентификатором.\u003cbr\u003e\n3. Затем вызывается метод __ValidatePaidSalary__, проверяющий, получал ли работник зарплату ранее, если он ее уже получил, то повторно она выплачиваться не должна.\u003cbr\u003e\n4. Последний этап - вызывается метод __GetCalculationSalary__, который расчитывает зарплату в зависимости от должности - он будет виртуальным. Это означает, что классы-наследники могут его переопределить, а могут и не переопределять, поскольку в базовом классе прописана его реализация по умолчанию. Результат метода GetCalculationSalary и будет возвращаться методом GetSalary.\u003cbr\u003e\n\u003e __Обратите внимание__, что нам нет смысла переопределять методы из первых трех пунктов - неважно, какой компании является бухгалтерия. То есть в абсолютно любой бухгалтерии прежде чем выдать сотруднику зарплату, необходимо выполнить первые три пункта (возможно больше, в примере представлена упрощенная версия, чтобы было более понятно назначение паттерна.\n\n:white_check_mark: Благодаря шаблонному методу нам не нужно в бухгалтерии каждой компании прописывать выполнение первых трех пунктов, потому что они уже реализованы в базовом классе __Accounting__.\n```C#\n/// \u003csummary\u003e\n/// Бухгалтегия.\n/// \u003c/summary\u003e\npublic abstract class Accounting\n{\n        /// \u003csummary\u003e\n        /// Список сотрудников для выплаты зарплаты.\n        /// \u003c/summary\u003e\n        protected List\u003cWorker\u003e _workers = new List\u003cWorker\u003e();\n\n        /// \u003csummary\u003e\n        /// Зарплаты сотрудников.\n        /// \u003c/summary\u003e\n        protected Dictionary\u003cstring, decimal\u003e _workersSalary = new Dictionary\u003cstring, decimal\u003e();\n\n        /// \u003csummary\u003e\n        /// Создание бухгалтерии.\n        /// \u003c/summary\u003e\n        public Accounting()\n        {   \n            _workers = new List\u003cWorker\u003e();\n            _workersSalary = new Dictionary\u003cstring, decimal\u003e();\n        }\n\n        /// \u003csummary\u003e\n        /// Проверка корректности идентификатора сотрудника.\n        /// \u003c/summary\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eИдентификатор сотрудника равен null!\u003c/exception\u003e\n        protected void ValidateWorkerId(Guid id)\n        {\n            if (id == null)\n            {\n                throw new ArgumentNullException(nameof(id), \"Идентификатор сотрудника равен null!\");\n            }\n        }\n\n        /// \u003csummary\u003e\n        /// Проверка наличия сотрудника в базе.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника, которого необходимо проверить.\u003c/param\u003e\n        protected void ValidateWorkerExistence(Guid id)\n        {\n            if (_workers.FirstOrDefault(worker =\u003e worker.Id == id) == null) \n            {\n                throw new ArgumentNullException(nameof(id), \"Сотрудник с указанным идентификатором не найден!\");\n            }\n        }\n\n        /// \u003csummary\u003e\n        /// Проверка того, была ли выплачена зарплата работнику ранее.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentOutOfRangeException\"\u003eДанному сотруднику уже выплачена зарплата!\u003c/exception\u003e\n        protected void ValidatePaidSalary(Guid id)\n        {\n            if (_workers.FirstOrDefault(worker =\u003e worker.Id == id).IsSalaryPaid)\n            {\n                throw new ArgumentOutOfRangeException(\"Данному сотруднику уже выплачена зарплата!\");\n            }\n        }\n\n        /// \u003csummary\u003e\n        /// Получение расчитанной зарплаты сотрудника.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника.\u003c/param\u003e\n        /// \u003creturns\u003eРасчитанная зарплата.\u003c/returns\u003e\n        protected virtual decimal GetCalculationSalary(Guid id)\n            =\u003e _workersSalary[_workers.FirstOrDefault(worker =\u003e worker.Id == id).Post];\n\n        /// \u003csummary\u003e\n        /// Выплата зарплаты сотруднику.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника, которого необходимо проверить.\u003c/param\u003e\n        public decimal GetSalary(Guid id)\n        {\n            ValidateWorkerId(id);\n            ValidateWorkerExistence(id);\n            ValidatePaidSalary(id);\n\n            _workers.FirstOrDefault(worker =\u003e worker.Id == id).IsSalaryPaid = true;\n\n            return GetCalculationSalary(id);\n        }\n\n        /// \u003csummary\u003e\n        /// Добавление сотрудника в базу бухгалтерии.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"firstName\"\u003eИмя.\u003c/param\u003e\n        /// \u003cparam name=\"lastName\"\u003eФамилия.\u003c/param\u003e\n        /// \u003cparam name=\"patronymic\"\u003eОтчество.\u003c/param\u003e\n        /// \u003cparam name=\"post\"\u003eДолжность.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentOutOfRangeException\"\u003eСотрудник с указанными данными уже есть в базе!\u003c/exception\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eУказанная должность в бухгалтерии не найдена!\u003c/exception\u003e\n        public Guid AddWorker(string firstName, string lastName, string patronymic, string post)\n        {\n            Validator.ValidateStringText(firstName);\n            Validator.ValidateStringText(lastName);\n            Validator.ValidateStringText(patronymic);\n            Validator.ValidateStringText(post);\n\n            if (!_workersSalary.ContainsKey(post))\n            {\n                throw new ArgumentNullException(\"Указанная должность в бухгалтерии не найдена!\");\n            }\n\n            var id = Guid.NewGuid();\n\n            _workers.Add(new Worker\n            {\n                Id = id,\n                FirstName = firstName,\n                LastName = lastName,\n                Patronymic = patronymic,\n                Post = post,\n                IsSalaryPaid = false\n            });\n\n            return id;\n        }\n\n        /// \u003csummary\u003e\n        /// Получение сотрудника по идентификатору.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника.\u003c/param\u003e\n        /// \u003creturns\u003eСотрудника.\u003c/returns\u003e\n        public Worker GetWorker(Guid id)\n        {\n            ValidateWorkerId(id);\n            ValidateWorkerExistence(id);\n\n            return _workers.First(worker =\u003e worker.Id == id);\n        }\n\n        /// \u003csummary\u003e\n        /// Удаляет сотрудника из базы.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника, которого необходимо удалить.\u003c/param\u003e\n        /// \u003cexception cref=\"ArgumentNullException\"\u003eСотрудник с указанным идентификатором не найден в базе!\u003c/exception\u003e\n        public void DeleteWorker(Guid id)\n        {\n            ValidateWorkerExistence(id);\n\n            _workers.Remove(_workers.FirstOrDefault(worker =\u003e worker.Id == id));\n        }\n}\n```\n:three: Теперь реализуем первый класс-наследник базового класса Accounting - пусть это будет бухгалтерия __Сбера__ (SberAccounting). В Сбере сотрудникам помимо заработной платы выдается еще и фиксированная премия. Это значит, что нам необходимо изменить логику расчета зарплаты для сотрудников Сбера:\n```C#\n/// \u003csummary\u003e\n/// Бухгалтерия Сбера.\n/// \u003c/summary\u003e\npublic class SberAccounting : Accounting\n{\n        /// \u003csummary\u003e\n        /// Премия.\n        /// \u003c/summary\u003e\n        private decimal _prize = 5000;\n\n        /// \u003csummary\u003e\n        /// Создание бухгалтерии Сбера.\n        /// \u003c/summary\u003e\n        public SberAccounting() \n        {\n            _workers = new List\u003cWorker\u003e();\n\n            _workersSalary = new Dictionary\u003cstring, decimal\u003e\n            {\n                {\"Менеджер\", 30000 },\n                {\"Программист\", 100000 }\n            };\n        }\n\n        /// \u003csummary\u003e\n        /// Получение расчитанной зарплаты сотрудника.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника.\u003c/param\u003e\n        /// \u003creturns\u003eРасчитанная зарплата.\u003c/returns\u003e\n        protected override decimal GetCalculationSalary(Guid id) =\u003e base.GetCalculationSalary(id) + _prize;\n}\n```\n:four: Реализуем второй класс-наследник: бухгалтерия Озона (OzonAccounting). У Озона у сотрудников нет премии. Все сотрудники добираются на работу на корпоративном такси, соответственно, ежемесячная плата за корпоративное такси вычитается из их заработной платы. Это означает, что нам также придется переопределить расчет зарплаты и здесь:\n```C#\n/// \u003csummary\u003e\n/// Бухгалтерия Ozon.\n/// \u003c/summary\u003e\npublic class OzonAccounting : Accounting\n{\n        /// \u003csummary\u003e\n        /// Плата за корпоративное такси в месяц для поезди от дома до работы и обратно.\n        /// \u003c/summary\u003e\n        private decimal _taxiCostByMonth = 15000;\n\t\n        /// \u003csummary\u003e\n        /// Создание бухгалтерии Озона.\n        /// \u003c/summary\u003e\n        public OzonAccounting()\n        {\n            _workers = new List\u003cWorker\u003e();\n\n            _workersSalary = new Dictionary\u003cstring, decimal\u003e\n            {\n                { \"Менеджер\", 40000 },\n                { \"Программист\", 90000 }\n            };\n        }\n\t\n        /// \u003csummary\u003e\n        /// Получение расчитанной зарплаты сотрудника.\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"id\"\u003eИдентификатор сотрудника.\u003c/param\u003e\n        /// \u003creturns\u003eРасчитанная зарплата.\u003c/returns\u003e\n        protected override decimal GetCalculationSalary(Guid id) =\u003e base.GetCalculationSalary(id) - _taxiCostByMonth;\n}\n```\n:white_check_mark: __Преимущества паттерна Template Method__: Сокращение дублирования кода \u003cbr\u003e\n:x: __Недостатки__: По мере роста шагов в шаблонном методе возникают проблемы с его дальнейшей поддержкой.\u003cbr\u003e\n___\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacenko-developer%2Fpatterns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstacenko-developer%2Fpatterns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacenko-developer%2Fpatterns/lists"}