Представлен шаблон проектирования Simple Factory (Простая фабрика), условия его применения и пример реализации на PHP.
Содержание
- Применение
- Структура шаблона
- Реализация
- Итоги
Применение
Основное назначение порождающего шаблона проектирования Simple Factory — отделить код по созданию объектов от прикладного кода, который эти объекты использует. Для этого оператор new по созданию объектов выносится в специальный класс-фабрику. Какой объект создавать фабрика определяет по передаваемым ей параметрам. О достоинствах такого подхода будет рассказано ниже, а пока можно указать на основной — упрощается доработка приложения при исходных изменениях за счет слабой связанности кода.
Структура шаблона
Исходные данные таковы. Пусть нам нужно организовать систему привилегий пользователей. Для этого в лучших традициях ООП вначале создадим абстрактный класс, на основе которого в наследниках будем изменять их права пользования.
Итак.
- User — абстрактный класс, определяет минимальный набор привилегий пользователя.
- GuestUser — класс, определяющий права гостевого пользователя.
- EditorUser — класс, определяющий права редактора.
- AdminUser — класс, определяющий права администратора системы.
- UserFactory — отдельный класс фабрики, создает для клиентского кода пользователей с нужными правами.
В примере будут созданы пользователи с правами гостя и администратора. Затем будет добавлен пользователь с правами редактора.
Реализация
Создаём абстрактный класс, определяющий минимальный набор привилегий.
<?php
declare(strict_types=1);
abstract class User
{
public function __construct(
protected string $login
) {}
public function getLogin(): string
{
return $this->login;
}
public function readAccess(): bool
{
return true;
}
public function updateAccess(): bool
{
return false;
}
public function writeAccess(): bool
{
return false;
}
}
Теперь в наследниках созданного класса будем изменять привилегии базового пользователя. Создадим классы, описывающие привилегии гостя и администратора.
class GuestUser extends User
{
// Наследует поведение и конструктор базового класса
}
class AdminUser extends User
{
public function updateAccess(): bool
{
return true;
}
public function writeAccess(): bool
{
return true;
}
}
Систему классов, описывающих привилегии пользователей мы создали. Все они имеют одинаковый набор методов, определяющий их права доступа. Теперь наша задача создать класс фабрики, возвращающий экземпляры описанных классов. Сделаем это с помощью UserFactory, статический метод которого и будет это осуществлять.
class UserFactory
{
private static array $users = [
"login1" => "guest",
"login2" => "admin",
];
public static function createUser(string $login): User
{
$accessType = self::$users[$login] ?? "unknown";
if ($accessType === "unknown") {
echo "Запрошенный тип пользователя не найден, у вас будут права гостя.\n";
$accessType = "guest";
}
return match ($accessType) {
"guest" => new GuestUser($login),
"admin" => new AdminUser($login),
};
}
}
Итак, мы создали классы, описывающие привилегии пользователей, затем был создан класс фабрики объектов, метод которого в зависимости от входного параметра создает объект одного из классов привилегий. Кто-то может спросить, зачем создавать класс UserFactory, если объекты можно создать оператором new? Отвечу, как в Одессе, вопросом на вопрос. А если нам придется изменить систему привилегий, а у нас такие объекты создаются достаточно часто? Что будет проще в итоге: один раз потрудиться в начале над классом фабрики UserFactory, или потеть потом в поисках операторов new в исходном коде? Учтите, пример с пользователями взят для наглядности, это может быть любой объект, от краски для волос до списка запчастей ракеты, а у них вероятность изменений будет повыше. Статическая переменная класса фабрики при этом скорее всего потребует другой инициализации. Например, через базу данных или вообще можно воспользоваться внешней переменной.
Пойдем дальше. Учитывая вышесказанное посмотрим, что надо будет сделать, чтобы добавить пользователя с новыми привилегиями. А заодно наглядно будут видны недостатки шаблона проектирования Simple Factory.
Во-первых, нам нужно создать класс привилегий для нового пользователя. Пусть этот пользователь будет иметь права редактора. Замечу, этот класс нам пришлось бы создавать в любом случае.
class EditorUser extends User
{
public function updateAccess(): bool
{
return true;
}
}
Во-вторых, надо доработать класс фабрики объектов.
class UserFactory
{
private static array $users = [
"login1" => "guest",
"login2" => "admin",
"login3" => "editor",
];
public static function createUser(string $login): User
{
$accessType = self::$users[$login] ?? "unknown";
if ($accessType === "unknown") {
echo "Запрошенный тип пользователя не найден, у вас будут права гостя.\n";
$accessType = "guest";
}
return match ($accessType) {
"guest" => new GuestUser($login),
"admin" => new AdminUser($login),
"editor" => new EditorUser($login),
};
}
}
Сколько нам потребуется времени на такую доработку кода? Вопрос риторический.
Итоги
Итак, сегодня вам был продемонстрирован порождающий шаблон проектирования Simple Factory. Согласитесь, шаблон не сложный, тем не менее он имеет массу достоинств.
- Клиенту не нужно знать всех деталей используемой в приложении системы классов.
- Из предыдущего пункта вытекает важное следствие — слабая связанность основного кода (клиента) с используемыми им системами компонентов.
- Вся логика по созданию и инициализации объектов находится в одном месте. А это логика может быть очень сложной.
- Благодаря вышеперечисленному шаблон позволяет существенно упростить поддержку приложения в случае внесения изменений.
Как уже упоминалось, шаблон прост, поэтому не лишен недостатков. Вот основные из них.
- При добавлении каждого нового типа объекта приходится изменять код метода фабрики, что нарушает принцип открытости/закрытости кода приложения.
- По мере роста количества типов объектов класс фабрики может стать непомерно большим и сложным для поддержки.
- Класс фабрики должен обладать знаниями обо всех создаваемых объектах, что создает сильную связанность с ними.
Надеюсь, шаблон простая фабрика ясен. Полезен он или нет в каждом конкретном случае, решать вам.
Перейти к списку шаблонов проектирования.