R
R
ragnar_ok2021-06-25 15:13:08
Design patterns
ragnar_ok, 2021-06-25 15:13:08

How to instantiate a class in a factory method?

Пытаюсь реализовать паттерн "Простая фабрика" (в моем случае фабрика компонентов). Как принято создать экземпляр класса в фабричном методе: switch, reflection или?..

При использовании reflection фабричный метод (в примере ComponentFactory::createInstance) будет возвращать тип object, а не интерфейс (ComponentInterface), что, кажется плохим.

switch выглядит неплохо за исключением одной проблемы. Допустим, завтра мы хотим добавить еще одну реализацию. Нам нужно будет изменить этот код и добавить новый case. В перспективе количество case в ComponentFactory::createInstance может стать неприлично большим.

interface ComponentInterface
{
    public function getName(): string;
}
 
class NewsComponent implements ComponentInterface
{
    public function getName(): string
    {
        return 'news';
    }
}
 
class ComponentFactory
{
    public function createInstance($component): ?ComponentInterface
    {
        $instance = null;
        switch ($component) {
            case 'news':
                $instance = new NewsComponent();
                break;
        }
        
        return $instance;
    }
 
    public function createInstance($component)
    {
        if (!class_exists($component)) {
            throw new ClassNotFoundException();
        }
 
        $reflectionClass = new ReflectionClass($component);
        return $reflectionClass->newInstance();
    }
}

// клиентский код
$componentFactory = new ComponentFactory();
$component = $componentFactory->createInstance('news');

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
vitaly_74, 2021-06-25
@ragnar_ok

1. case сам по себе не есть плохо, и это не считается дурным тоном. т.е. использовать конструкцию case можно и нужно.
2. Давайте подумает чем характеризуется объект экземпляра класса, а потом перейдем к фабрике.
Объект прежде всего характеризуется данными хранящимися внутри него. Т.е. Фабрика должна внутри себя хранить то что она выпускает (строит) и всю информацию о нем.
3. не используйте рефлекшен. это плохо это дурной код, без крайней необходимости не нужно. Обычно рефлекшены не используются в клиентском коде, только непосредственно в самих фреймворках. но раз вы задаете вопросы про фабрики, вы фреймворк не пишите, а значит и использование рефлекшенов вам не нужно.
4. Взгляните на оператор new (например, new Factory()) по сути оператор new и есть фабрика, которая из класса создает объект с уникальными (а может и не всегда) наборами данных.
5. в ссылке выше, вам дали информацию по рефакторингу switch но если прочитать там, написано,
не стоит трогать если

Зачастую оператор switch используется в фабричных паттернах проектирования (Фабричный метод, Абстрактная фабрика) для выбора создаваемого класса

вот в данном случае не нужно трогать.
Почему лучше использовать switch?
- потому что когда через год, или два зайдете в эту фабрику, навряд ли вы захотите разбираться в рефлекшенах, и не интуитивных выражениях. А когда все есть в switch вы сразу увидите перед глазами какие объекты может выпускать фабрика + по аналогии создать подобный объект используя менее трудозатрат (почти копипаст).
Вот тут хороший пример фабрики:
https://refactoring.guru/ru/design-patterns/factor...
или можно так:
/**
 * Интерфейс Продукта объявляет операции, которые должны выполнять все
 * конкретные продукты.
 */
interface Product
{
    public function operation(): string;
}

/**
 * Конкретные Продукты предоставляют различные реализации интерфейса Продукта.
 */
class ConcreteProduct1 implements Product
{
    public function operation(): string
    {
        return "{Result of the ConcreteProduct1}";
    }
}

class ConcreteProduct2 implements Product
{
    public function operation(): string
    {
        return "{Result of the ConcreteProduct2}";
    }
}

class Factory implements Product{
   private $product;
   function __construct(string $productClass){
       switch($productClass){
           case default:
           case ConcreteProduct1::class:
                      $product = new ConcreteProduct1();
           break;
           case ConcreateProduct2::class:
                      $product = new ConcreateProduct2();
           break;
       }
       $this->product = $product;
   }

   public function operation(): string
    {
        return $this->product->operation();
    }
}

$product = new Factory(ConcreateProduct1::class);
echo $product->operation();

В данном случае Разрешено менять старый код, но только в пределах switch.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question