T
T
Taras Serevann2016-05-19 22:14:37
PHP
Taras Serevann, 2016-05-19 22:14:37

Which of these OOP approaches is better and what are they called?

Let's say from the very beginning we have a more or less abstract class and it does something, let's say it casts magic.

class Magick
{
    protected $magick;

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

Those. the result of the class depends on its data. Now we want to somehow change the data. Which of these methods should we choose? (I saw both, including in quite popular applications.
Method 1:
class MeguminMagick extends Magick
{
    $magick = 'explosion stuff';
}

$megumin = new MaguminMagick;

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

The first one is quite beautiful and flexible, but the second one I see more often. Incl. wondering which one is more correct and why.

Answer the question

In order to leave comments, you need to log in

4 answer(s)
S
Sergey, 2016-05-19
Protko @Fesor

from the very beginning we have a more or less abstract class

It should not be. "abstract" classes are a way to eliminate duplication, we are more concerned with object interfaces and polymorphism. But specifically in the example under consideration, only encapsulation is important to us.
Below are both methods and how they differ. In the first, we simply create an object that already contains some default state. In the second, we create an object with a zero state, and then ask it to change its state to a new one.
That's the whole difference. The next step is encapsulation. That is, our state must be isolated inside the object, and access to it directly from the outside world must be closed.
If we want to create an object with some default state, we take the first option (either as in the example, we write it directly as a property value, or we do it in the constructor).
If we want to create an "empty" object and only then determine its state, we choose the second option. As a rule, this method creates a lot of side effects if you don’t control it, and they usually do it not because it’s right, but because they don’t know how to do it differently (a typical example is doctrine entities. Many simply can’t imagine how to work with them without setters).
If we want to create an object but it cannot be empty, the object must require an initial value at the time of creation. Then we just pass the value to the constructor. Without this value (or with the wrong one), we will not be able to create an object. For example, if the user must have an email and a password, it makes sense to "force" developers to explicitly pass them to the object's constructor. It's kind of a variation.
And now let's try the first and second approaches together + the third case with the required parameters:
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;
     }
}

That is, the whole difference is only in where the data comes from for the object to form its state. Inside (first case) or outside (second case). Both approaches can be used together.
Well, yes - why in your example inheritance and a certain "abstract" class - it's not clear. Inheritance is not a principle, it is a mechanism for achieving subtype polymorphism. You need to be careful with it, especially if polymorphism is not dealt with first.

S
Stanislav Makarov, 2016-05-19
@Nipheris

The first method cannot be better than the second, since they are used in completely different situations.
A subclass should be created when you have additional logic called for specific cases. According to your example, if Magick is a class of any magicians, then a subclass of water magicians can define its own magick, which it can cast. Well, or form a more specific, "water" magic.
The second option implies that the magic can be completely set from the outside, and Magick only casts it, but does not participate in its "formation".
Now you have not decided for yourself what Magick is. You need to do this, and the question will disappear by itself.

A
AtomKrieg, 2016-05-19
@AtomKrieg

It is not necessary to create heirs if they do not have additional logic. Occam's principle.

N
novrm, 2016-05-19
@novrm

Допустим ситуацию:
- Вы создали некий модуль MyModule, в котором использовали класс MeguminMagick.
- MyModule вы опубликовали на github...
- Другие пользователи взяли MyModule в свои проекты...
В первом случае ваш MyModule не обладает возможностью настройки ибо для изменения переменной $magick нужно "влезть" в ваш код и переписать его... Это очень плохо... Ибо когда вы будете обновлять ваш MyModule возникнут конфликты...
Во втором случае - сеттер позволит настроить MyModule не изменяя его код. Что и является его гибкостью... С другой стороны для этого вам нужно будет поработать больше - дописать сеттеры, геттеры и т.д...
In short - the first way is shorter and faster and more convenient for you personally. The other one is oriented to use by other developers in their projects.
To be even more meticulous, use both the first and second methods together...
Set a value for $magick and write a setter and getter for this variable:

/**
     * @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;
    }

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question