Andriy Shyrokoryadov

.Net developer, data scientist

Декоратор. Шаблоны проектирования - видео №5. №44.

Текст к видео "#44 Декоратор" на канале YouTube

Рад приветствовать вас на моём канале. Сегодня мы рассмотрим очередной шаблон проектирования и во время детального анализа сценария, когда данный шаблон должен использоваться, я сделаю подводку к следующему принципу SOLID. Шаблон, о котором сегодня пойдет речь, называет «Декоратор», а принцип SOLID, который активно эксплуатирует данный шаблон, называется принцип открытости / закрытости или на английском языке Open / Close principle, что соответствует букве O в аббревиатуре SOLID. Итак, в сегодняшнем видео – шаблон «Декоратор», в следующем видео через неделю «Принцип открытости / закрытости».

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

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

Какие возможности у нас есть:

  • Класс в удвоенным умножением наследует класс «Калькулятор» и мы надписываем метод «Multiply».
  • Изменяем класс «Калькулятор» и вводим дополнительный аргумент метода «Multiply», чтобы разделить логику с умножением на 2 и без такого умножения.
  • Используем шаблон проектирования «Декоратор».

Какую возможность мы будем использовать:

  • Подход с наследованием – это нарушение принципа D из принципов SOLID, мы делаем так что наше код становится зависим от конкретной реализации, а не от абстрактной.
  • Подход с дополнительным аргументов – мы добавляем новый аргумент в сигнатуру метода, то есть нам необходимо будет выполнить множество изменений в других частях кода, где вызывается метод «Multiply».
  • Наш выбор – использование шаблона проектирования «Декоратор»

Что необходимо знать о шаблоне декоратор:

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

Сложно описывать шаблон декоратор без примера в коде. Поэтому давайте рассмотрим пример. Сегодня это будет приложение, в котором мы рассчитываем стоимость доставки заказа и сумму комиссии за оплату заказа. Стоимость доставки зависит от выбранной компании поставщика, а комиссия зависит от суммы оплаты. Запускаем Visual Studio…

Пример посылок был примером выдуманным. Целью данного примера было только лишь показать, как можно использовать шаблон «Декоратор». Я хотел бы поделиться с вами еще одним примером, когда я на практике использовал шаблон проектирования «Декоратор». Есть некоторый класс, который может выполнить любой запрос на базе данных. Функциональность класса была заточена под выполнение команды «UPDATE». Проблема заключалась в том, что если во время выполнения нескольких команд «UPDATE» появлялась ошибка, то часть данных была изменена, а часть команд после возникновения ошибки не была выполнена и, соответственно, часть данных не была изменена. Для того чтобы решить эту задачу, не изменяя исходного класса, я создал декоратор, который добавлял обслуживание транзакций для запроса, которые выполнялись в декорируемом классе. В коде это выглядело следующим образом.

public class UpdateQueryRunner : IQueryRunner
{
	public int RunQuery(string queryString)
	{
		string connectionString = GetConnectionString()
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            SqlCommand command = new SqlCommand(queryString, connection);
            try
            {
                connection.Open();
				return command.ExecuteNonQuery();  
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
				return 0;
            }
        }
	}
}

public class TransactionUpdateQueryRunner : IQueryRunner
{
	private readonly IQueryRunner _decoratedQueryRunner;
	
	public TransactionUpdateQueryRunner(IQueryRunner queryRunner)
	{
		_decoratedQueryRunner = queryRunner;
	}	
	
	public int RunQuery(string queryString)
	{
		var transactionQuery = $"BEGIN TRAN; {queryString} COMMIT TRAN;"
		return _decoratedQueryRunner.RunQuery();
	}
}

public interface IQueryRunner
{
	int RunQuery(string queryString);
}

//пример использования кода, указанного выше.
var updateQueryRunner = new UpdateQueryRunner();
var transactionQueryRunner = new TransactionUpdateQueryRunner(updateQueryRunner);
transactionQueryRunner.RunQuery("UPDATE [TABLE] SET Name='ABC' WHERE ID=1;") //конечный запрос BEGIN TRAN; UPDATE [TABLE] SET Name='ABC' WHERE ID=1; COMMIT TRAN;

Пример кода из видео на GitHub

Для открытия файла проекта необходимо Visual Studio 2019.