Laravel4 Преимущество контейнера IOC

Мне сложно понять преимущества контейнера IOC в области внедрения зависимостей.
Рассмотрим этот базовый пример:

App::bind('Car', function()
{
    return new Car;
});

Route::get('/', function()
{
    dd(App::make('Car'));  //  resolve it
}); 

Я не вижу преимуществ использования контейнера IOC по сравнению с созданием нового экземпляра в конструкторе.
Помимо преимуществ тестирования, я читал, что причина в слабой связи.
Однако, поскольку привязка «автомобиль» просто возвращается экземпляр новой машины, я не понимаю, в каком смысле этот пример был бы более слабосвязанным.
Мне кажется, что эти двое делают одно и то же.


person html_programmer    schedule 11.08.2014    source источник


Ответы (2)


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

class TestController
{
    public function __construct(CarRepositoryInterface $car)
    {
        $this->_repository = $car;
    }

    public function route($id)
    {
        return View::make('my.view')->with('car', $this->_repository->find($id));
    }
}

Очень просто, репозиторий вводится в конструктор контроллера, который затем используется в маршруте для загрузки определенного автомобиля по идентификатору. Детали репозитория здесь не так уж и важны, и, по-видимому, есть поставщик услуг, который привязывает CarRepositoryInterface к конкретной CarRepository реализации:

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind('CarRepositoryInterface', function($app) {
            return new EloquentCarRepository(new Car());
        });
    }
}

Итак, вот оно, каждый раз, когда создается контроллер, создается EloquentCarRepository и вводится в конструктор для использования контроллером.

Но подождите, что произойдет, если вы захотите отказаться от использования Eloquent, чтобы сказать Doctrine? Поскольку здесь мы используем внедрение зависимостей, вам не нужно изменять ни одной строчки кода в вашем контроллере (или любом другом контроллере, который может использовать вашу текущую реализацию). Все, что вам нужно сделать, это определить другую реализацию CarRepositoryInterface, например DoctrineCarRepository, и изменить одну строку кода в вашем сервис-провайдере:

return new DoctrineCarRepository();

Все остальное, что зависит от CarRepositoryInterface, теперь волшебным образом работает. И все это благодаря IoC.

Вы также можете добавить более сложную логику к своему поставщику услуг:

public function register()
{
    $this->app->bind('CarRepositoryInterface', function($app) {
        if($app->environment() == 'production') {
            return new EloquentCarRepository(new Car());
        } else {
            return new DoctrineCarRepository(new Car());
        }
    });
}

Здесь EloquentCarRepository будет использоваться только в производственной среде, тогда как в любой другой среде будет использоваться DoctrineCarRepository. (Этот пример предназначен только для того, чтобы показать, как вы можете получить гораздо больший контроль над типом объекта, создаваемым во время выполнения, а не то, что я выступаю за это на самом деле ..)

Дополнение

Как я сказал в своем комментарии, это тип использования, при котором вы не совсем уверены, какой тип объекта вам понадобится, до времени выполнения. Есть еще одно применение: управление зависимостями.

Предположим, у вас есть объект, который зависит от другого объекта:

class MyObject
{
    public function __construct(AnotherObject $obj)
    {
        $this->obj = $obj;
    }
}

Предположим также, что AnotherObject зависит от еще одного объекта:

class AnotherObject
{
    public function __construct(YetAnotherObject $obj)
    {
        $this->obj = $obj;
    }
}

Это может быстро выйти из-под контроля, и вы можете столкнуться с длинными цепочками зависимостей, которые необходимо удовлетворить, прежде чем вы действительно сможете создать объект. С помощью IoC вы можете просто извлечь экземпляр из контейнера:

$myObject = app()->make('MyObject');

Пока IoC может построить все зависимости, вам не нужно делать что-то вроде этого:

$yetAnotherObj = new YetAnotherObject();
$anotherObj = new AnotherObject($yetAnotherObj);
$myObject = new MyObject($anotherObj);
person Jeff Lambert    schedule 11.08.2014
comment
Я думаю, что понимаю. Если я хорошо понимаю, контейнер IOC позволяет определить общую абстракцию (ключевое определение), для которой конкретный класс привязан во время выполнения. Это было бы правильно? - person html_programmer; 12.08.2014
comment
Правильный. Его лучше всего использовать в ситуациях, когда вы не уверены, какой именно тип объекта вам нужен, до времени выполнения. - person Jeff Lambert; 12.08.2014
comment
@Kim Я добавил еще одно использование выше, просто для пояснения. - person Jeff Lambert; 12.08.2014
comment
Да, это определенно полезно! Небольшой вопрос по этому поводу. Принято ли в этом случае выполнять явную привязку? Я только что прочитал об автоматическом разрешении. Нужно ли определять привязку, если вы просто возвращаете экземпляр класса (например, ваш самый первый пример)? - person html_programmer; 12.08.2014
comment
@Kim, чтобы воспользоваться этим, вам не нужен поставщик услуг. Поставщик услуг действительно необходим только в том случае, если вам нужно принять решение о том, какую реализацию внедрить, или если есть сложные зависимости, которые необходимо построить в первую очередь. Все, что вам нужно сделать во всех других случаях, - это определить свой класс, а затем внедрить этот класс в свой контроллер. - person Jeff Lambert; 12.08.2014

Этот пример, который вы публикуете, не представляет собой реальный вариант использования контейнера IoC ...

Контейнер IoC более полезен в этом примере:

Когда у вас есть BillingNotifierInterface, который реализован EmailBillingNotifier

App::bind('BillingNotifierInterface', function()
{
  return new EmailBillingNotifier;
});

И используется BillerInterface, который реализуется StripeBiller, например:

App::bind('BillerInterface', function()
{
  return new StripeBiller(App::make('BillingNotifierInterface'));
})

Но внезапно ваша команда хочет перейти с EmailBillingNotifier на SMSBillingNotifier, вы просто меняете одну строку, и ваше приложение продолжает работать плавно, как кошка ...

App::bind('BillingNotifierInterface', function()
{
  return new SMSBillingNotifier;
});

Это НАСТОЯЩЕЕ приложение КОНТЕЙНЕРА IoC ...

person NBPalomino    schedule 11.08.2014
comment
+1 Хороший пример. Вы даже можете позволить приложению посмотреть на предпочтения пользователя, чтобы определить, хотят ли они получать уведомления по электронной почте или sms, а контейнер IoC может автоматически сделать всю тяжелую работу за вас. - person Jeff Lambert; 12.08.2014
comment
+1 Спасибо за этот простой пример. Это упрощает понимание. - person html_programmer; 12.08.2014