Когда необходимо применять порождающий паттерн фабричный метод
Перейти к содержимому

Когда необходимо применять порождающий паттерн фабричный метод

  • автор:

Порождающие паттерны: «Фабричный метод» (Factory Method)

Порождающие паттерны: «Фабричный метод» (Factory Method)

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

Когда следует использовать фабричный метод

  • Когда заранее неизвестно, объекты каких типов необходимо создавать;
  • Когда система должна быть независимой от процесса создания новых объектов и расширяемой: в нее можно легко вводить новые классы, объекты которых система должна создавать;
  • Когда создание новых объектов необходимо делегировать из базового класса классам наследникам;

Схема примера использования паттерна «Фабричный метод» из реальной жизни:

Схема примера использования паттерна

UML схема паттерна фабричный метод

Реализация шаблона «фабричный метод» на C#

using System; namespace DoFactory.GangOfFour.Factory.Structural < class MainApp < static void Main() < // An array of creators Creator[] creators = new Creator[2]; creators[0] = new ConcreteCreatorA(); creators[1] = new ConcreteCreatorB(); // Iterate over creators and create products foreach (Creator creator in creators) < Product product = creator.FactoryMethod(); Console.WriteLine("Created ", product.GetType().Name); > // Wait for user Console.ReadKey(); > > abstract class Product < >class ConcreteProductA : Product < >class ConcreteProductB : Product < >abstract class Creator < public abstract Product FactoryMethod(); >class ConcreteCreatorA : Creator < public override Product FactoryMethod() < return new ConcreteProductA(); >> class ConcreteCreatorB : Creator < public override Product FactoryMethod() < return new ConcreteProductB(); >> >

Output

Created ConcreteProductA Created ConcreteProductB

Обсуждение паттерна «Фабричный метод»

У каждой реализации фабричного метода есть свои особенности

  1. Классический фабричный метод является частным случаем шаблонного метода. Это значит, что фабричный метод привязан к текущей иерархии типов и не может быть использован повторно в другом контексте.
  2. Полиморфный фабричный метод является стратегией создания экземпляров некоторого семейства типов, что позволяет использовать одну фабрику в разных контекстах. Тип создаваемого объекта определяется типом фабрики и обычно не зависит от аргументов фабричного метода.
  3. Статический фабричный метод является самой простой формой фабричного метода. Статический метод создания позволяет обойти ограничения конструк- торов. Например, тип создаваемого объекта может зависеть от аргументов ме- тода, экземпляр может возвращаться из кэша, а не создаваться заново или же фабричный метод может быть асинхронным.

Использование Func в качестве фабрики

В некоторых случаях Func может использоваться в качестве полноценной фабрики и передаваться классу извне его клиентами. Данный вариант фабрики является допустимым во внутреннем (internal) коде и только для функций с небольшим количеством аргументов. Понять, что делает Func, довольно просто, но разобраться в назначении Func factory без контекста будет практически невозможно.Читаемость кода очень важна, поэтому именованная фабрика является предпочтительным вариантом.

Конструктор vs. фабричный метод

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

Именованные конструкторы.

В языке C# имя конструктора совпадает с именем класса, что делает невозможным использование двух конструкторов с одним набором и типом параметров. Хорошим примером такого ограничения является структура Timespan, которая представляет собой интервал времени. Очень удобно создавать интервал времени по количеству секунд, минут, часов и дней, но сделать несколько конструкторов, каждый из которых принимает один параметр типа double, невозможно. Для этого структура Timespan содержит набор фабричных методов (пример ниже)

public struct Timespan < public Timespan(double ticks) < . >public static Timespan FromMilliseoncds(double value) public static Timespan FromSeconds(double value) public static Timespan FromMinutes(double value) >

Тяжеловесный процесс создания

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

Примеры в .NET Framework

В .NET Framework применяется огромное количество фабрик.

  1. Классический фабричный метод: Stream.CreateWaitHandle, SecurityAttribute.CreatePermission, ChannelFactory.CreateChannel, XmlNode.CreateNavigator.
  2. Полиморфная фабрика: IControllerFactory в ASP.NET MVC, IHttpHandlerFactory в ASP.NET, ServiceHostFactory и IChannelFactory в WCF, IQueryProvider в LINQ.
  3. Неполиморфная фабрика: TaskFactory в TPL.
  4. Обобщенная статическая фабрика: Activator.CreateInstance, Array.CreateInstance, StringComparer.Create.
  5. Сокрытие наследников: RandomNumberGenerator.Create, WebRequest.Create, BufferManager.CreateBufferManager, Message.CreateMessage, MessageFault.CreateFault
  6. Фасадные фабричные методы: File.Create, File.CreateText.
  7. Именованные конструкторы: Timespan.FromSecond, Timespan.FromMilliseconds, GCHandle.FromIntPtr, Color.FromArgb.

Когда необходимо применять порождающий паттерн фабричный метод

Фабричный метод (англ. Factory Method) — порождающий паттерн (шаблон) проектирования, предоставляющий подклассаминтерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какойкласс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Этопозволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами наболее высоком уровне. Также известен под названием виртуальный конструктор (англ. Virtual Constructor).

Цель

Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой классинстанциировать. Фабричный метод позволяет классу делегировать создание подклассов. Используется, когда:

  • классу заранее неизвестно, объекты каких подклассов ему нужно создавать.
  • класс спроектирован так, чтобы объекты, которые он создаёт, специфицировались подклассами.
  • класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и планируетсялокализовать знание о том, какой класс принимает эти обязанности на себя.
Плюсы
  • позволяет сделать код создания объектов более универсальным, не привязываясь к конкретным классам(ConcreteProduct), а оперируя лишь общим интерфейсом (Product);
  • позволяет установить связь между параллельными иерархиями классов.
Минусы
  • необходимость создавать наследника Creator для каждого нового типа продукта (ConcreteProduct).
  • Product — продукт
    • определяет интерфейс объектов, создаваемых абстрактным методом;
    • реализует интерфейс Product;
    • объявляет фабричный метод, который возвращает объект типа Product. Может также содержатьреализацию этого метода «по умолчанию»;
    • может вызывать фабричный метод для создания объекта типа Product;
    • переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект классаConcreteProduct.

    Паттерн проектирования Factory Method (Фабричный метод) на PHP

    1: 2:
    3: abstract class Product
    4: 5: >
    6:
    7: class ProductA extends Product
    8: 9: >
    10:
    11: class ProductB extends Product
    12: 13: >
    14:
    15: abstract class FactoryAbstract
    16: 17: public function create ( $type )
    18: 19: switch ( $type ) 20: case ‘A’ :
    21: return new ProductA ();
    22: case ‘B’ :
    23: default:
    24: return new ProductB ();
    25: >
    26: >
    27: >
    28:
    29: class Factory extends FactoryAbstract
    30: 31: >
    32:
    33: $factory = new Factory ();
    34: $productA = $factory -> create ( ‘A’ );

    Порождающие паттерны

    Фабричный метод (Factory Method) — это паттерн, который определяет интерфейс для создания объектов некоторого класса, но непосредственное решение о том, объект какого класса создавать происходит в подклассах. То есть паттерн предполагает, что базовый класс делегирует создание объектов классам-наследникам.

    Когда надо применять паттерн

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

    На языке UML паттерн можно описать следующим образом:

    Фабричный метод (FactoryMethod) в C# и .NET

    Формальное определение паттерна на языке C# может выглядеть следующим образом:

    abstract class Product <> class ConcreteProductA : Product <> class ConcreteProductB : Product <> abstract class Creator < public abstract Product FactoryMethod(); >class ConcreteCreatorA : Creator < public override Product FactoryMethod() < return new ConcreteProductA(); >> class ConcreteCreatorB : Creator < public override Product FactoryMethod() < return new ConcreteProductB(); >>

    Участники

    • Абстрактный класс Product определяет интерфейс класса, объекты которого надо создавать.
    • Конкретные классы ConcreteProductA и ConcreteProductB представляют реализацию класса Product. Таких классов может быть множество
    • Абстрактный класс Creator определяет абстрактный фабричный метод FactoryMethod() , который возвращает объект Product.
    • Конкретные классы ConcreteCreatorA и ConcreteCreatorB — наследники класса Creator, определяющие свою реализацию метода FactoryMethod() . Причем метод FactoryMethod() каждого отдельного класса-создателя возвращает определенный конкретный тип продукта. Для каждого конкретного класса продукта определяется свой конкретный класс создателя. Таким образом, класс Creator делегирует создание объекта Product своим наследникам. А классы ConcreteCreatorA и ConcreteCreatorB могут самостоятельно выбирать какой конкретный тип продукта им создавать.

    Теперь рассмотрим на реальном примере. Допустим, мы создаем программу для сферы строительства. Возможно, вначале мы захотим построить многоэтажный панельный дом. И для этого выбирается соответствующий подрядчик, который возводит каменные дома. Затем нам захочется построить деревянный дом и для этого также надо будет выбрать нужного подрядчика:

    class Program < static void Main(string[] args) < Developer dev = new PanelDeveloper("ООО КирпичСтрой"); House house2 = dev.Create(); dev = new WoodDeveloper("Частный застройщик"); House house = dev.Create(); Console.ReadLine(); >> // абстрактный класс строительной компании abstract class Developer < public string Name < get; set; >public Developer (string n) < Name = n; >// фабричный метод abstract public House Create(); > // строит панельные дома class PanelDeveloper : Developer < public PanelDeveloper(string n) : base(n) < >public override House Create() < return new PanelHouse(); >> // строит деревянные дома class WoodDeveloper : Developer < public WoodDeveloper(string n) : base(n) < >public override House Create() < return new WoodHouse(); >> abstract class House < >class PanelHouse : House < public PanelHouse() < Console.WriteLine("Панельный дом построен"); >> class WoodHouse : House < public WoodHouse() < Console.WriteLine("Деревянный дом построен"); >>

    В качестве абстрактного класса Product здесь выступает класс House. Его две конкретные реализации — PanelHouse и WoodHouse представляют типы домов, которые будут строить подрядчики. В качестве абстрактного класса создателя выступает Developer, определяющий абстрактный метод Create() . Этот метод реализуется в классах-наследниках WoodDeveloper и PanelDeveloper. И если в будущем нам потребуется построить дома какого-то другого типа, например, кирпичные, то мы можем с легкостью создать новый класс кирпичных домов, унаследованный от House, и определить класс соответствующего подрядчика. Таким образом, система получится легко расширяемой. Правда, недостатки паттерна тоже очевидны — для каждого нового продукта необходимо создавать свой класс создателя.

    Шаблоны проектирования простым языком. Часть первая. Порождающие шаблоны

    Первая статья из цикла, посвящённого шаблонам, или паттернам, проектирования. На понятных примерах объясняем суть порождающих шаблонов.

    Обложка поста Шаблоны проектирования простым языком. Часть первая. Порождающие шаблоны

    Рассказывает Камран Ахмед

    На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

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

    Википедия описывает их следующим образом:

    Будьте осторожны

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

    Также заметьте, что примеры ниже написаны на PHP 7. Но это не должно вас останавливать, ведь принципы остаются такими же.

    Типы шаблонов

    Шаблоны бывают следующих трех видов:

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

    Существуют следующие порождающие шаблоны:

    • простая фабрика (Simple Factory);
    • фабричный метод (Factory Method);
    • абстрактная фабрика (Abstract Factory);
    • строитель (Builder);
    • прототип (Prototype);
    • одиночка (Singleton).

    Простая фабрика (Simple Factory)

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

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

    Перейдем к коду. У нас есть интерфейс Door и его реализация:

    interface Door < public function getWidth(): float; public function getHeight(): float; >class WoodenDoor implements Door < protected $width; protected $height; public function __construct(float $width, float $height) < $this->width = $width; $this->height = $height; > public function getWidth(): float < return $this->width; > public function getHeight(): float < return $this->height; > > 

    Затем у нас есть наша DoorFactory , которая делает дверь и возвращает её:

    class DoorFactory < public static function makeDoor($width, $height): Door < return new WoodenDoor($width, $height); >> 

    И затем мы можем использовать всё это:

    $door = DoorFactory::makeDoor(100, 200); echo 'Width: ' . $door->getWidth(); echo 'Height: ' . $door->getHeight(); 

    Когда использовать: Когда создание объекта — это не просто несколько присвоений, а какая-то логика, тогда имеет смысл создать отдельную фабрику вместо повторения одного и того же кода повсюду.

    Фабричный метод (Fabric Method)

    Пример из жизни: Рассмотрим пример с менеджером по найму. Невозможно одному человеку провести собеседования со всеми кандидатами на все вакансии. В зависимости от вакансии он должен распределить этапы собеседования между разными людьми.

    Простыми словами: Менеджер предоставляет способ делегирования логики создания экземпляра дочерним классам.

    Перейдём к коду. Рассмотрим приведенный выше пример про HR-менеджера. Изначально у нас есть интерфейс Interviewer и несколько реализаций для него:

    interface Interviewer < public function askQuestions(); >class Developer implements Interviewer < public function askQuestions() < echo 'Спрашивает про шаблоны проектирования!'; >> class CommunityExecutive implements Interviewer < public function askQuestions() < echo 'Спрашивает о работе с сообществом'; >> 

    Теперь создадим нашего HiringManager :

    abstract class HiringManager < // Фабричный метод abstract public function makeInterviewer(): Interviewer; public function takeInterview() < $interviewer = $this->makeInterviewer(); $interviewer->askQuestions(); > > 

    И теперь любой дочерний класс может расширять его и предоставлять необходимого интервьюера:

    class DevelopmentManager extends HiringManager < public function makeInterviewer(): Interviewer < return new Developer(); >> class MarketingManager extends HiringManager < public function makeInterviewer(): Interviewer < return new CommunityExecutive(); >> 
    $devManager = new DevelopmentManager(); $devManager->takeInterview(); // Вывод: Спрашивает о шаблонах проектирования! $marketingManager = new MarketingManager(); $marketingManager->takeInterview(); // Вывод: Спрашивает о работе с сообществом 

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

    Примеры на Java и Python.

    Абстрактная фабрика (Abstract Factory)

    Пример из жизни: Расширим наш пример про двери из простой фабрики. В зависимости от ваших нужд вам понадобится деревянная дверь из одного магазина, железная дверь — из другого или пластиковая — из третьего. Кроме того, вам понадобится соответствующий специалист: столяр для деревянной двери, сварщик для железной двери и так далее. Как вы можете заметить, тут есть зависимость между дверьми.

    Простыми словами: Фабрика фабрик. Фабрика, которая группирует индивидуальные, но связанные/зависимые фабрики без указания их конкретных классов.

    Обратимся к коду. Используем пример про двери. Сначала у нас есть интерфейс Door и несколько его реализаций:

    interface Door < public function getDescription(); >class WoodenDoor implements Door < public function getDescription() < echo 'Я деревянная дверь'; >> class IronDoor implements Door < public function getDescription() < echo 'Я железная дверь'; >> 

    Затем у нас есть несколько DoorFittingExpert для каждого типа дверей:

    interface DoorFittingExpert < public function getDescription(); >class Welder implements DoorFittingExpert < public function getDescription() < echo 'Я работаю только с железными дверьми'; >> class Carpenter implements DoorFittingExpert < public function getDescription() < echo 'Я работаю только с деревянными дверьми'; >> 

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

    interface DoorFactory < public function makeDoor(): Door; public function makeFittingExpert(): DoorFittingExpert; >// Деревянная фабрика вернет деревянную дверь и столяра class WoodenDoorFactory implements DoorFactory < public function makeDoor(): Door < return new WoodenDoor(); >public function makeFittingExpert(): DoorFittingExpert < return new Carpenter(); >> // Железная фабрика вернет железную дверь и сварщика class IronDoorFactory implements DoorFactory < public function makeDoor(): Door < return new IronDoor(); >public function makeFittingExpert(): DoorFittingExpert < return new Welder(); >> 
    $woodenFactory = new WoodenDoorFactory(); $door = $woodenFactory->makeDoor(); $expert = $woodenFactory->makeFittingExpert(); $door->getDescription(); // Вывод: Я деревянная дверь $expert->getDescription(); // Вывод: Я работаю только с деревянными дверями // Аналогично для железной двери $ironFactory = new IronDoorFactory(); $door = $ironFactory->makeDoor(); $expert = $ironFactory->makeFittingExpert(); $door->getDescription(); // Вывод: Я железная дверь $expert->getDescription(); // Вывод: Я работаю только с железными дверями 

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

    Когда использовать: Когда есть взаимосвязанные зависимости с не очень простой логикой создания.

    Примеры на Java и Python.

    Строитель (Builder)

    Пример из жизни: Представьте, что вы пришли в McDonalds и заказали конкретный продукт, например, БигМак, и вам готовят его без лишних вопросов. Это пример простой фабрики. Но есть случаи, когда логика создания может включать в себя больше шагов. Например, вы хотите индивидуальный сэндвич в Subway: у вас есть несколько вариантов того, как он будет сделан. Какой хлеб вы хотите? Какие соусы использовать? Какой сыр? В таких случаях на помощь приходит шаблон «Строитель».

    Простыми словами: Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.

    Давайте я покажу на примере, что такое «Телескопический конструктор». Когда-то мы все видели конструктор вроде такого:

    public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)

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

    Перейдем к примеру в коде. Адекватной альтернативой будет использование шаблона «Строитель». Сначала у нас есть Burger , который мы хотим создать:

    class Burger < protected $size; protected $cheese = false; protected $pepperoni = false; protected $lettuce = false; protected $tomato = false; public function __construct(BurgerBuilder $builder) < $this->size = $builder->size; $this->cheese = $builder->cheese; $this->pepperoni = $builder->pepperoni; $this->lettuce = $builder->lettuce; $this->tomato = $builder->tomato; > > 

    Затем мы берём «Строителя»:

    class BurgerBuilder < public $size; public $cheese = false; public $pepperoni = false; public $lettuce = false; public $tomato = false; public function __construct(int $size) < $this->size = $size; > public function addPepperoni() < $this->pepperoni = true; return $this; > public function addLettuce() < $this->lettuce = true; return $this; > public function addCheese() < $this->cheese = true; return $this; > public function addTomato() < $this->tomato = true; return $this; > public function build(): Burger < return new Burger($this); >> 
    $burger = (new BurgerBuilder(14)) ->addPepperoni() ->addLettuce() ->addTomato() ->build(); 

    Когда использовать: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.

    Примеры на Java и Python.

    Прототип (Prototype)

    Пример из жизни: Помните Долли? Овечка, которая была клонирована. Не будем углубляться, главное — это то, что здесь все вращается вокруг клонирования.

    Простыми словами: Прототип создает объект, основанный на существующем объекте при помощи клонирования.

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

    Обратимся к коду. В PHP это может быть легко реализовано с использованием clone :

    class Sheep < protected $name; protected $category; public function __construct(string $name, string $category = 'Горная овечка') < $this->name = $name; $this->category = $category; > public function setName(string $name) < $this->name = $name; > public function getName() < return $this->name; > public function setCategory(string $category) < $this->category = $category; > public function getCategory() < return $this->category; > > 

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

    $original = new Sheep('Джолли'); echo $original->getName(); // Джолли echo $original->getCategory(); // Горная овечка // Клонируем и модифицируем то что нужно $cloned = clone $original; $cloned->setName('Долли'); echo $cloned->getName(); // Долли echo $cloned->getCategory(); // Горная овечка 

    Также вы можете использовать волшебный метод __clone для изменения клонирующего поведения.

    Когда использовать: Когда необходим объект, похожий на существующий объект, либо когда создание будет дороже клонирования.

    Примеры на Java и Python.

    Одиночка (Singleton)

    Пример из жизни: В стране одновременно может быть только один президент. Один и тот же президент должен действовать, когда того требуют обстоятельства. Президент здесь является одиночкой.

    Простыми словами: Обеспечивает тот факт, что создаваемый объект является единственным объектом своего класса.

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

    На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

    Подробнее о подводных камнях шаблона одиночка читайте в нашей статье.

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

    final class President < private static $instance; private function __construct() < // Прячем конструктор >public static function getInstance(): President < if (!self::$instance) < self::$instance = new self(); >return self::$instance; > private function __clone() < // Отключаем клонирование >private function __wakeup() < // Отключаем десериализацию >> 
    $president1 = President::getInstance(); $president2 = President::getInstance(); var_dump($president1 === $president2); // true 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *