Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Как начать применять DI

Anna | 31.05.2014 | нет комментариев

Многократно сталкивался с суждением, что DI это кое-что трудное, огромное, неторопливое, подходящее только для «крупных» планов, а потому его применение реально на нынешней задаче (500 классов моделей, 300 классов контроллеров) безосновательно. Отчасти это связано с тем, что DI однозначно ассоциируется с пакетами как бы Symfony «The Dependency Injection Component», заведомо с лихвой покрывающими все допустимые варианты внедрения зависимостей.
Тут я хочу привести некоторый функциональный минимум, тот, что даст осознавание самой доктрины, чтобы показать, что сама инверсия зависимостей может быть довольно примитивна и лаконична.

Оглавление

Реализация составляет 2 класса из 500 строк кода:
SimpleDiClassManager – предоставляет информацию о классах. Для полновесной работы ему нужен кэшер (мы используем DoctrineCommonCacheApcCache), это дозволит не создавать отражений при всяком вызове скрипта. Разбирает аннотации для дальнейшей инъекции. Так же его допустимо применять в загрузчике, т.к. он хранит путь до файла класса.
SimpleDiServiceLocator – создает и инициализирует запрашиваемые у него сервисы. Именно данный класс изготавливает инъекции.
1) В простейшем случае, когда для класса не заданы никакие настройки, SimpleDiServiceLocator работает подобно паттерну multiton (он же Object Pool).

$service_locator->get('HelperTime');

2) Вариант внедрения через поле

class A
{
    /**
     * @Inject("HelperTime")
     * @var HelperTime
     */
   protected $helper_time;
}
$service_locator->get('A');

Такой вариант следует применять экстраординарно в контроллерах, т.к. для внедрения будет сделано отражение, что влияет на продуктивность в худшую сторону. Один класс на вызов скрипта с несколькими полями никак на время загрузки страницы не повлияют, но если это применять повсюду, потеря продуктивности будет абсолютно ощутима.
Тут хочется сделать отхождение в сторону Symfony. Там сходственное внедрение возможно:

  • в контроллерах для полей с всякий видимостью (в том числе protected, private) и это объясняется именно незначительным влиянием на эффективность, а помимо такого сам контроллер является контейнером сервисов (и имеет способ get() подобный нашему ServiceLocator::get());
  • в всяких классах (сервисах) для public полей, т.к. в этом случае не будет создаваться отражения, и будет применяться примитивное присвоение $service->field = $injected_service, что для private/protected полей приведет к исключению.

В нашей реализации отражение создается неизменно, внедрение неизменно будет заканчиваться удачно.
3) Внедрение через способ

class B 
{
    /**
     * @var HelperTime
     */
    protected $helper_time;

    /**
     * @Inject("HelperTime")
     * @param HelperTime $helper
     */
    public function setHelperTime($helper)
    {
        $this->helper_time = $helper;
    }
}
$service_locator->get('B');

Такой вариант особенно приемлем и наравне с внедрением через поле следует применять для установки зависимостей по умолчанию.
4) Внедрение через конфиг

$service_locator->setConfigs(array(
    'class_b_service' => array(
        'class' => 'B',
        'calls' => array(
            array('setHelperTime', array('@CustomHelperTime')),
        )
    )
));
$service_locator->get('class_b_service');

Это то, для чего и применяется внедрение зависимостей. Сейчас через настройки допустимо подменить применяемый в классе B хелпер, при этом сам класс B изменяться не будет.
5) Создание нового экземпляра класса. Когда нужно иметь несколько объектов одного класса, допустимо применение ServiceLocator в качестве фабрики

$users_factory = $service_locator;
$users_row = array(
    array('id' => 1, 'name' => 'admin'),
    array('id' => 2, 'name' => 'guest'),
);
$users = array();
foreach ($users_rows as $row) {
    $user = $users_factory->createService('User');
    $user->setData($row);
}

Пример

Возьмем произвольную пригодную библиотеку и испробуем внедрить в наш план. Возможен этоgithub.com/yiisoft/yii/blob/master/framework/utils/CPasswordHelper.php
Оказывается, мы не можем это сделать, потому что класс жестко завязан на абстолютно непотребные нам классы Yii и CException.

class CPasswordHelper
{
    …
    public static function generateSalt($cost=13)
    {
        if(!is_numeric($cost))
            throw new CException(Yii::t('yii','{class}::$cost must be a number.',array('{class}'=>__CLASS__)));

        $cost=(int)$cost;
        if($cost<4 || $cost>31)
            throw new CException(Yii::t('yii','{class}::$cost must be between 4 and 31.',array('{class}'=>__CLASS__)));

        if(($random=Yii::app()->getSecurityManager()->generateRandomString(22,true))===false)
            if(($random=Yii::app()->getSecurityManager()->generateRandomString(22,false))===false)
                throw new CException(Yii::t('yii','Unable to generate random string.'));

        return sprintf('$2a$%02d$',$cost).strtr($random,array('_'=>'.','~'=>'/'));
    }
}

Для того, Дабы сделать класс доступным для всякого плана, довольно было бы верно описать зависимости:

class CPasswordHelper
{

    /**
     * Тут я для краткости воспользуюсь public полями, вряд ли в данном случае это большее зло, 
     * чем вызов статических способов.
     * @Inject
     * @var YiiSecurityManager
     */ 
    public $securityManager;

    /**
     * Генератор ошибок
     * @Inject
     * @var YiiExceptor
     */ 
    public $exceptor;

    …

    public function generateSalt($cost=13)
    {
        if(!is_numeric($cost))
            $this->exceptor->create('yii','{class}::$cost must be a number.',array('{class}'=>__CLASS__));

        $cost=(int)$cost;
        if($cost<4 || $cost>31)
            $this->exceptor->create('yii','{class}::$cost must be between 4 and 31.',array('{class}'=>__CLASS__));

        if(($random=$this->securityManager->generateRandomString(22,true))===false)
            if(($random=$this->securityManager()->generateRandomString(22,false))===false)
                this->exceptor->create('yii','Unable to generate random string.');

        return sprintf('$2a$%02d$',$cost).strtr($random,array('_'=>'.','~'=>'/'));
    }
}

И завести класс – генератор исключений

class YiiExceptor
{
    public function create($a, $b, $c = null)
    {
        throw new CException(Yii:t($a, $b, $c));
    }
}

Завершение

Применение DI разрешает не задумываться над тем, в каком контексте будет применяться ваш модуль. Дает вероятность переносить обособленный класс в иной план без комплекта (Зачастую иерархического) зависимостей. При применении аннотаций вам не придётся заниматься очевидным созданием объектов и очевидной передачей параметров и сервисов в объект. И, безусловно, такой класс в разы проще поддается тестированию, нежели завязанный на статические способы либо очевидно создающий экземпляры класса, взамен применения фабрики.

Ссылки

Сам пример github.com/mthps/SimpleDi
Теория ru.wikipedia.org/wiki/Внедрение_зависимости
Одна из наилучших реализаций symfony.com/doc/current/components/dependency_injection/index.html

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB