инкапсуляция и неоткрытие внутренних компонентов по сравнению с SRP

Такой код плохой? :

      public class TierType
{
   private const TYPE_HARD = 'hard';
   private const TYPE_MEDIUM = 'medium';
   private const TYPE_SOFT = 'soft';

   private const SUPPORTED_TYPES = [
       self::TYPE_HARD,
       self::TYPE_MEDIUM,
       self::TYPE_SOFT
   ];

   private function _construct(private string $type) {
   }

   public static function fromString(string $tierType): self
   {
       return match ($tierType) {
           self::TYPE_HARD => new self(self::TYPE_HARD),
           self::TYPE_MEDIUM => new self(self::TYPE_MEDIUM),
           self::TYPE_SOFT => new self(self::TYPE_SOFT),
           default =>  throw new Exception('Unsupported tier type provided'),
       };
   }

   public static function createHard(): self
   {
       return new self(self::self::TYPE_HARD)
   }

   public static function createMedium(): self
   {
       return new self(self::self::TYPE_MEDIUM)
   }

   public static function createSoft(): self
   {
       return new self(self::self::TYPE_SOFT)
   }
}

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

      public class TierType
{
   public const TYPE_HARD = 'hard';
   public const TYPE_MEDIUM = 'medium';
   public const TYPE_SOFT = 'soft';
   public const TYPE_EXTRA_SOFT = 'extra_soft';

   private const SUPPORTED_TYPES = [
       self::TYPE_HARD,
       self::TYPE_MEDIUM,
       self::TYPE_SOFT
   ];

   public function _construct(private string $type) {
       if (!in_array($this->type, self:::SUPPORTED_TYPES) {
           throw new Exception('Unsupported tier type provided')
       }
   }
}

public class TierFactory {

    public function create(string $type): TierType 
    {
       return match ($type) {
           self::TYPE_HARD => new TierType(TierType::TYPE_HARD),
           self::TYPE_MEDIUM => new TierType(TierType::TYPE_MEDIUM),
           self::TYPE_SOFT => new TierType(TierType::TYPE_SOFT),
           default =>  throw new Exception('Unsupported tier type provided'),
       };
    }

   public static function createHard(): TierType
   {
       return new TierType(TierType::TYPE_HARD)
   }

   public static function createMedium(): TierType
   {
       return new TierType(TierType::TYPE_MEDIUM)
   }

   public static function createSoft(): TierType
   {
       return new TierType(TierType::TYPE_SOFT)
   }
}

Я думаю, что это тоже нормально, но в этом случае, вероятно, нет необходимости создавать фабрику, поскольку логика проста, и мы позволяем другим разработчикам опускать эту фабрику и напрямую вызывать конструктор TierType или создавать другие типы фабрик. Надеюсь, у нас есть проверка в конструкторе, но что, если нет? Сложнее понять состояние объекта и добавить всю проверку, когда вы делегируете его многим классам. Из другого хадна я думаю, что класс должен сам сказать, как его создать, и предоставить интерфейс создания. Я согласен, что фабрика имеет смысл, если логика создания сложна и какая-то ее часть не связана с объектом, например нам нужно взять данные из разных источников, как-то их объединить и после этого перейти к объекту, в этом случае это не ответственность объекта.

Я правильно думаю ??? Или он действительно ломает SRP и это нормально передать на завод ???

1 ответ

Решение

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

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

      public class TypeHard extends TierType implements TierTypeInterface {

   public function __construct()
   {
       parent::__construct(parent::TYPE_HARD);
   }

}

Этот способ также проще использовать с контейнерами DI с автоматическим подключением.

Другие вопросы по тегам