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

Что такое шаблоны проектирования?

Шаблоны проектирования — это многократно используемые решения часто возникающих проблем при проектировании программного обеспечения.

Если вы инженер-программист и не знакомы ни с одним программным обеспечением Шаблоны проектирования, то вы сильно отстали.

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

Шаблон проектирования программного обеспечения был представлен четырьмя авторами, известными как Банда четырех, которые опубликовали книгу под названием «Элементы многоразового объектно-ориентированного программного обеспечения», которая стала настолько популярной.

Существует 23 шаблона проектирования, которые решают разные задачи. Вам не обязательно знать все шаблоны проектирования, но лучше ознакомиться с известными и постепенно вводить другие по мере того, как вы будете их практиковать и применять в своем коде. Шаблоны проектирования также работают рука об руку с принципами SOLID. Знание принципов SOLID и шаблонов проектирования сделает вас лучшим инженером-программистом, но не обязательно знать принципы SOLID для работы с шаблонами проектирования.

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

Что такое фабричный метод?

Фабричный метод — это порождающий шаблон проектирования, предоставляющий интерфейсы для создания объектов.

В шаблоне проектирования фабричный метод у нас есть класс-создатель, который определяет фабричный метод, отвечающий за создание объектов (продуктов).

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

Допустим, у нас есть фабричный метод, производящий айфоны, и все разные модели айфонов управляются в одном классе. Было бы сложно добавить новую модель iPhone, потому что необходимо было бы внести некоторые изменения в класс iPhone, который управляет всеми iPhone, что может быть обременительным для разработчика, а также может вызвать ошибки, тем самым нарушая работу существующих клиентов.

Но если у нас есть специальный класс только для iPhone 14, он будет применять принцип SRP, потому что класс просто делает одну вещь, а именно управляет новым iPhone 14, и если Применяется шаблон проектирования фабричный метод, фабричный метод будет отвечать за создание разных iPhone, что упрощает разработку для разработчиков, поскольку им не нужно изменять существующий код.

Зачем вам нужен фабричный метод?

  • Фабрики могут сделать ваш код гибким, сократив слишком много условных операторов (if...else).
  • Заводы применяют SRP. Если вы не знакомы с принципом SOLID, вам следует изучить его, но это не обязательно для шаблонов проектирования.
  • Фабрики также помогают избежать кодов жесткой связи.
  • Фабрики применяют принцип открытости/закрытости: вы можете создавать новые продукты, не нарушая существующий клиентский код.
  • Фабрики помогают избежать пересборки кода каждый раз при изменении требований; вы просто повторно используете объекты.

Структура фабричного метода

Структурирование фабричного метода не очень сложно; вам просто нужно определить несколько классов и интерфейсов.

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

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

Затем класс создателя определяет фабричный метод, который возвращает продукт, а тип возвращаемого значения — интерфейс продукта.

Затем конкретный создатель переопределяет базовый фабричный метод, который возвращает продукт. В C# этот класс является необязательным, поскольку мы собираемся использовать класс Activator, который содержит метод для локального или удаленного создания типов объектов.

Демо (Игра футбольного матча)

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

Без фабричного метода

using Newtonsoft.Json;
using System.Collections.Generic;

int homeScore=1;
int awayScore=2;
List<string> outcome = new List<string>();

if(homeScore > 0 && awayScore > 0)
{
   outcome.Add("Both teams scored");
}
if(homeScore > awayScore)
{
   outcome.Add("Home team wins");
}
if(awayScore > homeScore)
{
  outcome.Add("Away team wins");
}
if(awayScore == homeScore)
{
  outcome.Add("Draw");
}

var result = JsonConvet.SerializeObject(outcome);
Console.WriteLine(result);

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

Фабричным методом

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

Шаг 1. Определите интерфейс продукта

interface ISoccer
{
  string Outcome(int homeScore, int awayScore);
}

Шаг 2. Определите конкретный класс продукта

class Draw : ISoccer
{
   public string Outcome(int homeScore, int awayScore)
   {
      return (homeScore == awayScore)?"Draw":"";
   }
}
class HomeWin: ISoccer
{
   public string Outcome(int homeScore, int awayScore)
   {
      return (homeScore > awayScore)?"Home team wins":"";
   }
}
class AwayWin: ISoccer
{
   public string Outcome(int homeScore, int awayScore)
   {
      return (awayScore > homeScore)?"Away team wins":"";
   }
}
class BTS: ISoccer
{
   public string Outcome(int homeScore, int awayScore)
   {
      return (awayScore > 0 && homeScore > 0)?"Both team scored":"";
   }
}

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

Шаг 3. Определите класс создателя

class Soccer
{
   public ISoccer Create(string className)
   {
      return (ISoccer)Activator.CreateInstance(Type.GetType($"DesignPattern.Factory.Products.{className}"));
   }
}

Класс создателя отвечает за создание объекта или продуктов, а возвращаемый тип должен быть типом интерфейса продукта. Если вы заметили, мы передаем некоторые строки внутри метода Type.GetType(Namespace.ClassName). Мы просто передаем пространство имен конкретных классов продуктов, а параметр «className» — это фактическое имя класса продукта.

Метод Activator.CreatInstance() Создает экземпляр указанного типа, используя конструктор, который лучше всего соответствует заданным параметрам.

Шаг 4. Клиент (program.cs)

using Newtonsoft.Json;
using System.Collections.Generic;

int homeScore=2;
int awayScore=2;
List<string> outcome = new List<string>();

Soccer soccer = new Soccer(); //instantiate the creator class that is responsible for creating objects
ISoccer draw = soccer.Create(nameof(Draw)); //creating the object. 
outcome.Add(draw.Outcome(homeScore, awayScore)); //no more conditionals

var result = JsonConvet.SerializeObject(outcome);
Console.WriteLine(result);

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

Заключение

Чтобы узнать больше о шаблонах проектирования и твердых принципах, посмотрите этот интересный Курс Udemy, чтобы узнать о шаблонах проектирования и принципах проектирования.

Следуйте за мной для более интересных тем. Ваше здоровье!