Понимание IoC, DI и эталонных методов - PullRequest
8 голосов
/ 25 октября 2011

Я нахожусь в процессе обучения внедрения зависимостей и инверсии управления, и я думаю, что начинаю понимать, как это работает:

  • Объекты не должны заботиться о создании своихсобственные зависимости
  • Зависимости должны передаваться объекту (с помощью методов конструктора или установщика)
  • Контейнер DI может выполнять работу по созданию объектов со всеми их необходимыми зависимостями

Если это все правильно, могу ли я больше использовать то, что я называю "ссылочными методами" в моих объектах?

Вот что я имею в виду под ссылочными методами.Скажем, у меня есть две модели для семей и членов семьи.Я считаю очень полезным создавать методы, которые ссылаются на объекты, которые относятся к этой модели.В приведенном ниже примере при звонке $family->members() я могу быстро получить доступ ко всем членам семьи.Но это будет означать, что мой family объект создает экземпляры family_member классов ... и не нарушает ли это правила IoC?

Что если класс family_member имеет зависимость, которая находится за пределамииз области family класса?Вход здесь будет очень ценным!

<?php

    class family
    {
        public $id;

        public function members()
        {
            // Return an array of family_member objects
        }
    }

    class family_member
    {
        public $family_id;
        public $first_name;
        public $last_name;
        public $age;
    }

Ответы [ 3 ]

5 голосов
/ 25 октября 2011

Отказ от ответственности: я просто изучаю DI самостоятельно. Возьми ответ с крупицей соли.

Внедрение зависимостей составляет всего около Внедрение зависимостей . Если ваш объектно-ориентированный дизайн приводит к тому, что объект Family несет ответственность за создание экземпляров Member, то, безусловно, объект Family создает Member, потому что в этом случае Member равен больше не считается зависимостью Family, а ответственность . Поэтому:

class Family
{
    /**
     * Constructor.
     * 
     * Since you have decided in your OO design phase that this
     * object should have the responsibility of creating members,
     * Member is no longer a dependency. MySQLi is, since you need
     * it to get the information to create the member. Inject it.
     *
     */
    public function __construct($id, MySQLi $mysqli)
    {
        $this->id = $id;
        $this->mysqli = $mysqli;
    }

    /**
     * Query the database for members data, instantiates them and
     * return them.
     *
     */
    public function getMembers()
    {
        // Do work using MySQLi
    }
}

Но если подумать, действительно Family действительно должен нести ответственность за создание Member? Лучшим дизайном будет иметь другой объект, такой как FamilyMapper create Family со своими членами. Как это:

class FamilyMapper
{
    /**
     * Constructor.
     * 
     * A better OO design, imho is using the DataMapper pattern.
     * The mapper's responsibility is instantiating Family,
     * which means it's going to have to connect to the database,
     * which makes MySQLi its dependency. So we inject it.
     *
     */
    public function __construct(MySQLi $mysqli)
    {
        $this->mysqli = $mysqli;
    }

    public function findByID($familyID)
    {
        // Query database for family and members data
        // Instantiate and return them
    }

}

class Family
{
    /**
     * Constructor.
     * 
     * Family is an object representing a Family and its members,
     * along with methods that *operate* on the data, so Member
     * in this OO design is a dependency. Inject it.
     *
     */
    public function __construct($id, MemberCollection $members)
    {
        $this->id;
        $this->members;
    }

    public function getMembers()
    {
        return $this->members;
    }
}

Используя этот шаблон, ваши доменные объекты вместе с их методами (которые могут содержать бизнес-логику) будут отделены от вашего кода доступа к данным. Это хорошая вещь в внедрении зависимостей - она ​​заставляет вас переосмыслить свой дизайн ОО, чтобы вы в итоге получили более чистый код.

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

Полезные ссылки:

  1. http://martinfowler.com/articles/injection.html
  2. У кого-нибудь есть хорошая аналогия для внедрения зависимости?
  3. Как объяснить введение зависимости 5-летнему ребенку?

Дополнения * +1051 * Опять же, возьмите то, что находится ниже, с зерном соли. Обратите также внимание, что существует разница между инъекцией зависимости и контейнером ввода зависимости . Первый - это простая концепция внедрения зависимостей вместо того, чтобы объекты создавали их сами (что приводит к очень высокой связи). Мы видим это из приведенного выше примера. Последний термин для фреймворков / библиотек, которые имеют дело с внедрением зависимостей, поэтому вам не нужно делать ручное внедрение. Ответственность контейнера заключается в подключении зависимостей, поэтому вам не нужно делать грязную работу. Идея заключается в том, что вы определяете конфигурацию внедрения зависимостей, которая сообщает контейнеру, что есть у объекта Foo зависимостей и как их внедрить. Контейнер читает документацию и выполняет инъекцию за вас. Это то, что делают библиотеки DIC, такие как Pimple, SimpleDIC. Вы можете сравнить контейнеры внедрения зависимостей с фабриками, поскольку оба являются объектами создания, единственной обязанностью которых является создание объектов. Хотя фабрики часто являются специализированными (т. Е. FamilyMemberFactory создает экземпляры MemberInterface), контейнер внедрения зависимостей является более общим. Некоторые люди говорят, что использование контейнера внедрения зависимостей освобождает вас от необходимости в фабриках, но вы должны помнить, что это означает, что вы должны создавать и поддерживать файлы конфигурации внедрения зависимостей, которые могут содержать тысячи строк XML / PHP. Надеюсь, это поможет.

1 голос
/ 25 октября 2011

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

Таким образом, сочетание внедрения зависимостей и фабрик значительно облегчит вашу жизнь.

class family {
    protected $_members;

    public function __construct($members = array()) {
        $this->_members = array_filter($members, function($obj){
            return ($obj instanceof family_member);
        });
    }

    public function addMember() {
        $this->_members[] = $member = $this->_createMember();
        return $member;
    }

    protected function _createMember() {
        return new family_member;
    }
}

Заводская модель в некоторой степени совместима с инверсией управления и введением зависимостей. В то время как внедрение зависимостей освобождает ваш объект от создания его зависимостей, шаблон фабрики позволяет ему создавать базовую реализацию зависимости. И, в конце концов, он по-прежнему позволяет переопределять зависимости объекта при необходимости:

class familyExtended extends family {
    protected function _createMember() {
        return new familyExtended_member;
    }
}

Это особенно полезно при тестировании:

class familyTestable extends family {
    protected function _createMember() {
        return new family_memberFake;
    }
}

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

0 голосов
/ 25 октября 2011

Проблема в том, что вся идея DI заключается в том, что Family не должен знать, как создать конкретный FamilyMember, например, у вас должен быть IFamilyMember, и реализации для него могут различаться на каждом модуле / слое вашего заявления.

Должно быть что-то вроде этого:

public function GetMembers()
{
    $members = array();

    foreach ( $member in $this->internalMemberList ){
       // get the current implementation of IFamilyMember
       $memberImpl = $diContainer->Resolve("IFamilyMember");

       $memberImpl->Name = $member->Name;
       //.... Not the best example....
    }

    return $members;
}

По сути, все сводится к идее, что компоненты должны игнорировать свои зависимости, и в вашем случае Family зависит от FamilyMember, поэтому любая реализация FamilyMember должна быть абстрагирована от самого Семейства.

Для конкретного случая PHP рассмотрите возможность проверки Symfony , который интенсивно использует шаблон DI, или вы можете рассмотреть возможность использования Symfony Dependency Injection framework , являющегося частью Symfony, но может использоваться как отдельный компонент. Также Фабьен Потенциер написал хорошую статью по этому вопросу.

Надеюсь, я смогу помочь!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...