Предисловие

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

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

Введение

Шаблоны проектирования довольно просты. Мы, как разработчики, сталкиваемся с проблемами в нашем коде. У нас, как у разработчиков, есть 5 целей разработки программного обеспечения, чтобы предоставить продукт, который

  • вовремя
  • в пределах бюджета
  • без ошибок (без ошибок)
  • удовлетворяет потребности сейчас
  • удовлетворяет потребность в будущем

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

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

Шаблон

Фабричный шаблон используется для создания экземпляра объекта во время выполнения. Вот и все. Это просто класс, который может возвращать разные типы объектов в зависимости от переданного ему значения. Например, вы можете передать значение `chocolate` в `IceCreamFactory` и получить обратно класс `ChocolateIceCream`.

Это может потребоваться сделать во время выполнения, где этот шаблон вступает в игру. Значение `chocolate` может храниться в столбце базы данных, который извлекается, например, для определенного пользователя.

Пример кода

class IceCreamFactory:
flavors = {
   'chocolate': ChocolateIceCream,
   'vanilla': VanillaIceCream,
   'strawberry': StrawberryIceCream,
 }
def make(self, flavor):
   return self.flavors[flavor]()

Вот и все. Мы только что создали фабрику, используя шаблон фабрики. Теперь все, что нам нужно сделать, это указать нужный нам аромат, и мы сможем вернуть правильный аромат:

flavor = IceCreamFactory().make('strawberry') 
#== <some.module.StrawberryIceCream>

Проблема — пример из реального мира

Вот реальный пример того, как я использовал этот шаблон. Подготавливаем сервер. Для простоты у нас есть 2 услуги, которые мы хотим предоставить:

  • Постгрес
  • Nginx

Теперь нам нужно предоставить обе эти услуги на 2 разных типах серверов:

  • Убунту
  • Дебиан

Теперь обратите внимание, что у нас есть несколько различных возможностей, из которых мы можем выбирать:

  • Установка Postgres в Ubuntu
  • Запуск Postgres в Ubuntu
  • Остановка Postgres в Ubuntu
  • Установка Nginx на Debian

    … и так далее и тому подобное

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

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

Решение

Решение здесь состоит в том, чтобы использовать 2 фабрики.

Первая фабрика будет ServerFactory, которая будет отвечать за получение любого из нескольких серверов (Ubuntu, Debian, Centos, ..).

Вторая фабрика будет ServicesFactory, которая будет отвечать за получение любого из нескольких сервисов (Postgres, Nginx, RabbitMQ, ..).

Фабрика серверов

Используя шаблон выше, давайте продолжим и создадим наш ServerFactory:

class ServerFactory:
servers = {
   'ubuntu': UbuntuServer,
   'debian': DebianServer,
   'centos': CentosServer,
 }
def make(self, server):
   return self.servers[server]()

Обратите внимание, что мы только что изменили вкусы мороженого для серверов (разновидности UNIX?). Вы можете скопировать и вставить этот шаблон, чтобы создать собственные фабрики в собственной кодовой базе.

Фабрика услуг

Теперь давайте проделаем то же самое для сервисов:

class ServiceFactory:
services = {
   'postgres': PostgresService,
   'nginx': NginxService,
   'rabbitmq': RabbitMQService,
 }
def make(self, service):
   return self.services[service]()

обратите внимание, что весь этот код является одним и тем же котлом.

Объединение фабрики

Теперь, если мы подумаем об этом с точки зрения базы данных:

A server has many services

Правильно? Поскольку сервер имеет много сервисов, давайте сымитируем это. Давайте сделаем так, чтобы UbuntuFactory имел доступ к ServiceFactory:

from app.factories import ServiceFactory
class UbuntuServer:
 
   services = ServiceFactory()
def connect(self):
   self.establish_connection()
   return self

Бум. Сделанный. Теперь наш UbuntuServer имеет доступ ко всем нашим сервисам.

Использование всего вместе

Давайте продолжим и используем эти 2 фабрики в нашем приложении:

from app.factories import ServerFactory
def show(self):
   user = User.find(1)
   user.server #== 'ubuntu'
   server = ServerFactory().make(user.server) 
   #== <app.servers.UbuntuServer>
   
   server.connect()
for service in ('postgres', 'nginx', 'rabbitmq'):
   server.services.make(service).install()

Будущее

Шаблоны проектирования полезны для решения проблем сейчас и в будущем. Этот шаблон полезен, потому что:

  • поддерживать больше серверов так же просто, как создавать новые серверы.
  • поддерживать больше сервисов так же просто, как создавать новые сервисы.

Эти две вещи могут существовать отдельно друг от друга и, следовательно, могут увеличиваться или уменьшаться отдельно друг от друга.