Использование ключевого слова " new " в конструкторах


Недавно я прочитал, что использование ключевого слова " new " в конструкторе сильно осуждается, но я не уверен, что понимаю, почему? Например, как это:

class A {
    public $foo;

    function __construct() {
        $this->foo = new Bar();
    }
}

Отличается от:

class A {
    public function someMethod() {
        $foo = new Bar();
    }
}

???

3   3  

3 ответа:

Это действительно теория инъекции зависимостей.

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

Это также нарушает парадигму "кодирования для интерфейса, а не реализации"

Пример:

class Phone {
    protected $network;

    public function __construct() {
        $this->network = new Verizon();
        $this->network->distinctiveRing();
    }
}

class Verizon {
    public function call($number) {
        ....
    }

    public function distinctiveRing() {

    }
}

Теперь предположим, что однажды вы захотели создать ATT, TMobile и Sprint телефон? Конечно, все они способны совершать звонки, и могут сделать это, имея только номер телефона. Кроме того, класс телефонов не должен заботиться о том, кто является оператором, поскольку его работа заключается в том, чтобы облегчить ввод номера, а не в том, чтобы фактически установить сетевое соединение, верно?

Таким образом, мы не должны создавать новый класс SprintPhone, который может создать экземпляр другого сетевого объекта Sprint, верно? Правильно.

Так какой же способ лучше?

class Phone {
    protected $network;

    public function __construct(NetworkInterface $network) {
        $this->network = $network;
    }
}

interface NetworkInterface {
    public function call($number);
}

class Verizon implements NetworkInterface {
    ...
}

class Sprint implements NetworkInterface {
    ...
}

Сейчас же, вы можете просто сказать: $phone = new Phone(new Sprint()) или $phone = new Phone(new Verizon())

Также обратите внимание, что наш призыв к distinctiveRing пропал. Почему? Ну, потому что мы не знаем, что любой объект, реализующий NetworkInterface, обязательно будет поддерживать отличительное кольцо. но это хорошо , потому что теперь наш Phone может поддерживать любой Network без изменений кода.

Если вам нужна поддержка distinctive ring, вы всегда можете создать новый интерфейс, поддерживающий метод distinctiveRing. В вашем объекте Phone Вы можете проверить, если ваш Network реализует DistinctiveRingerInterface и, если это так, сделайте свой отличительный перстень. Но при таком подходе вы больше не привязаны к конкретной сети. А еще лучше, Вы были вынуждены сделать это, потому что с самого начала выбрали правильный подход.

И, любая другая сеть, которая может быть осуществимо создана позже по дороге. Что еще более важно, ваш класс больше не должен заботиться о том, какой сетевой объект ему дан. Он знает (потому что он получил объект, реализующий NetworkInterface), что объект Network способен сделать call С $number.

Это также приводит к гораздо лучшему коду, с лучшим разделением проблем.

И наконец: тестирование.

В первом примере, если вы попытаетесь протестировать объект телефона, он будет совершать вызов в сети Verizon. Хреново быть человеком, которому звонят весь день, потому что ты проводишь модульные тесты, верно? Правильно.

Ну, просто создайте класс TestNetwork, который реализует NetworkInterface, и передайте его на свой телефон объект. Ваш класс TestNetwork может делать все, что вы хотите, внутри своего метода call - или ничего не делать.

Кроме того, вы можете просто создать макет объекта с помощью PHPUnit и убедиться, что метод call В вашей тестовой сети действительно вызывается. Вы не могли сделать этого раньше, потому что Network был создан экземпляр внутри вашего Phone.

Похоже, что ваш вопрос и ваш пример кода не слишком похожи?

Ваш первый пример кода должен работать, потому что вы присваиваете переменной класса:

class A {
    public $foo;

    function __construct() {
        $this->foo = new Bar();
    }
}

Ваш второй пример присваивает его переменной, локальной для метода _ _ construct (), поэтому у вас не будет возможности получить значение позже:

class A {
    public function someMethod() {
        $foo = new Bar();
    }
}

Кроме того: использование new обычно прекрасно. Однако вы, возможно, читали об инверсии контроля или IOC, которая является методом, позволяющим избежать зависимостей и таким образом, вы попытаетесь избежать создания классов непосредственно в конструкторе, см., например, http://ralphschindler.com/2011/05/18/learning-about-dependency-injection-and-php

В первом примере $foo доступен в любом методе этого класса, а также вне объекта
таким образом, вы можете сделать:

$a = new A();
$a->foo->sth;

Во втором примере $foo доступен только внутри someMethod.