Задать вопрос
Taras_Serevann
@Taras_Serevann
веб-разработчик, автор

Какой из этих подходов в ООП лучше и как они называются?

Допустим, с самого начала у нас есть более-менее абстрактный класс и он что-то делает, допустим, кастует магию.
class Magick
{
    protected $magick;

    public function doMagick()
    {
         echo $this->magick;
     }
}


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

Способ 1:
class MeguminMagick extends Magick
{
    $magick = 'explosion stuff';
}

$megumin = new MaguminMagick;


Способ 2:
$megumin = new Magick;
$megumin->setMagick('explosion stuff'); // предположим, там есть сеттер


Первый довольно красивый и гибкий, но второй вижу чаще. В т.ч. интересует, какой из них более правильный и почему.
  • Вопрос задан
  • 471 просмотр
Подписаться 3 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 4
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
с самого начала у нас есть более-менее абстрактный класс


Так не должно быть. "абстрактные" классы это способ устранения дублирования, нам больше важны интерфейсы объектов и полиморфизм. Но конкретно в рассматриваемом примере нам важна только инкапсуляция.

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

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

Если мы хотим создать объект с каким-то состоянием по умолчанию - мы берем первый вариант (либо как в примере прописывваем прямо как значение свойства, либо сэтим в конструкторе).

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

Если же мы хотим создать объект но он не может быть пустым, объект должен требовать начальное значение в момент создания. Тогда мы просто передаем значение в конструктор. Без этого значения (или с неправильным) мы не сможем создать объект. К примеру если у пользователя обязательно должен быть email и пароль имеет смысл "заставлять" разработчиков явно передавать их в конструктор объекта. Это эдакая вариация.

А теперь попробуем первый и второй подходы вместе + третий кейс с обязательными параметрами:

class User {
     private $id;
     private $email;
     private $password;

     public function __construct($email, $password) 
     {
           // это ваш первый пример только без наследования, оно не нужно
           // в нашей задаче идентификатор пораждается
           // при создании объекта самим объектом, состояние по умолчанию
           $this->id = Uuid::uuid4(); 

           // мы требуем входящие данные что бы задать начальное состояние
           $this->email = $email;
           $this->password = $password;
     }

     // мутация состояния, второй вариант.
     public function changePassword($password) 
     {
            $this->password = $password;
     }
}


То есть вся разница только в том, откуда приходят данные для формирование объектом своего состояния. Изнутри (первый случай) или снаружи (второй случай). Оба подхода вполне себе можно использовать вместе.

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

Подкласс нужно создавать тогда, когда у вас есть дополнительная логика что называется для конкретных случаев. По вашему примеру, если Magick - это класс всяких магов, то подкласс водяных магов может определять свой magick, который он умеет кастовать. Ну или образовывать более конкретную, "водяную" магию.

Второй вариант подразумевает, что магия может полностью задаваться извне, и Magick только кастит её, но в её "формировании" не участвует.

Сейчас вы не решили для себя, что такое Magick. Вам нужно это сделать, и вопрос отпадёт сам собой.
Ответ написан
AtomKrieg
@AtomKrieg
Давай я поищу в Google за тебя
Не нужно создавать наследников если в них нет дополнительной логики. Принцип Оккама.
Ответ написан
@novrm
Допустим ситуацию:
- Вы создали некий модуль MyModule, в котором использовали класс MeguminMagick.
- MyModule вы опубликовали на github...
- Другие пользователи взяли MyModule в свои проекты...

В первом случае ваш MyModule не обладает возможностью настройки ибо для изменения переменной $magick нужно "влезть" в ваш код и переписать его... Это очень плохо... Ибо когда вы будете обновлять ваш MyModule возникнут конфликты...

Во втором случае - сеттер позволит настроить MyModule не изменяя его код. Что и является его гибкостью... С другой стороны для этого вам нужно будет поработать больше - дописать сеттеры, геттеры и т.д...

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

Если быть еще дотошнее - используйте и первый и второй способы вместе...
Задайте значение для $magick и напишите для этой переменной сеттер и геттер:

/**
     * @var string
     */
    public $magick = 'explosion stuff';

    /**
     * Set magick instance
     *
     * @return MeguminMagick
     */
    public function setMagick($magick)
    {
        $this->magick = (string) $magick;
        return $this;
    }

    /**
     * Get magick instance
     *
     * @return string
     */
    public function getMagick()
    {
        if (!is_string($this->magick)) {
            throw new \InvalidArgumentException(sprintf('"%s" expects string.', __METHOD__));
        };

        return $this->magick;
    }
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы