Pull to refresh
29
0
Bender Bending Rodriguez @bendingunit22

Специалист по всему

Send message
На HK есть четырехканальный wifi приемник, в качестве передатчика используется софт под ios или android. Софт крайне убогий, но для экспериментов вполне подойдет.
На самом деле, самый простой в управлении — комнатный вертолет соосник, вроде blade cx.
Олсьют юнит тестов за сколько проходит? Мы приближаемся к отметке в 3000 и время прохождения тестов радует не так сильно, как раньше.

Насколько просто на практике происходит миграция с Selenium 1.0 на WebDriver? До миграции гоняли тесты в браузере или использовали RC?
Еще можно провести опрос: взяли бы вы на работу человека с IT дипломом?
Т.е. человек без диплома — лентяй?
К вопросу о PS. Могу на перле порекомендовать :)
Кстати, это излюбленная отмазка программистов: сделал плохо, потому что нужно было быстро :)
Ну не можете — не пользуйтесь, я Вас не заставляю. :)

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

На самом деле все довольно понятно — имеющиеся модули, заявляющие реализацию DI — сложны для понимания и фантастически сложны в использовании, причем кода получается на меньше, а больше.

Вы можете вынести пользу из нашей дискуссии: показать пример, где использование KC было бы оправдано по сравнению с обычным кодом. А потом этот же пример разместить в документации на cpan: без KC, то же самое с KC и в чем профит. Я сходу придумать такого примера не смог, вам, как автору, должно быть проще.

«Мой модуль, использую, потому что хочу, не хотите — не используйте» тоже аргумент, конечно, но менее убедительный :)
Peco::Container, как вы верно подметили, позволяет декларативно объявить зависимости и в дальнейшем, на основе конфига, автоматически их разрешает. Именно это и является основной фичей IoCC.

Kaiten::Container позволяет декларативно (с довольно громоздким синтаксисом) объявить хендлеры и их свойства, при этом зависимости описываются, как вы говорите, императивно т.е. фактически просто пишется разрешающий их код. Т.е. KC не поддерживает автоматического разрешения зависимостей, делать это нужно руками.

Код с KC будет:
    my $config = {
         ExampleP => {
             handler  => sub {
                return DBI->connect( "dbi:ExampleP:", "", "", { RaiseError => 1 } ) 
    or die $DBI::errstr;
              },
             probe    => sub { return 1 },
             settings => { reusable => 1 }
         },
    };

А без KC:
sub getExampleP
{
    my $self = shift;

    return $self->{dbh} ||= DBI->connect( "dbi:ExampleP:", "", "", { RaiseError => 1 } ) 
        or die $DBI::errstr;
}

Сомнительная польза, даже с учетом разных вкусов :) И я сходу не могу придумать ни один пример, при котором использование вашей абстракции будет оправдано т.е. не усложнит мне жизнь, а упростит.
Суть не меняется потому, что и у IoCC и у Service Locator она одна — инверсия зависимостей. Разница в деталях, которую вы и без меня уловили. Хотите проще?

sub createCopier
{
    return copier->new('source' => systemLogWriter->new(),  
'destination' => consoleReader->new());
}


Нужен синглтон?
sub getCopier
{
    my $self = shift;

    $self->{copier} ||= copier->new('source' => systemLogWriter->new(),  
'destination' => consoleReader->new());
}


Полностью императивно и не нужен никакой Kaiten::Container :)
my $self = shift;
my $dbh = $self->dbh;

А откуда у вас в $self->dbh возьмется, собственно, экземпляр DBI?

Контейнеры же ничего не требуют.

Мне кажется, вы продолжаете путать IoCC и сервис локатор / реестр. Если класс не поддерживает DI, каким образом контейнер сможет инъектировать в него зависимость?

Я предлагаю переместится в одну ветку — ниже. Вроде там мы уже подошли совсем близко к тому, что, как написал выше koorchik: Kaiten::Container — это обычный Service Locator :)
Вы рассуждаете совершенно верно. Как я писал выше: одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Если вам часто необходим UrlFetcher с одинаковой конфигурацией вполне логично вынести его создание в метод getUrlFetcher / createUrlFetcher локатора, где и разрулить его зависимости. В своем примере (хендлер ExampleP) вы это и делаете, только не добавлением метода, а регистрацией хендлера, что по своей сути одно и то же.
Т.е. получается, что динамический локатор (который и diC для готовых к этому) — просто единственно разумная штука, если готов к некоторому размазыванию логики?

Одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Предположим, у вас есть класс UrlFetcher у которого есть статическая зависимость на LWP и DBI:
sub new
{
	$self->{dbh} = DBI->new();
	$self->{user_agent} = LWP::UserAgent->new();
}


Создание объекта в приложении выглядит следующим образом:
UrlFetcher->new();


Используя сервис локатор вы избавляетесь от статических зависимостей:
sub new
{
	my $locator = ServiceLocator::instance();
	$self->{dbh} = $locator->getDbh();
	$self->{user_agent} = $locator->getUserAgent();
}


Теперь, при необходимости, например, заменить DBI на SuperNewDBI достаточно положить его в локатор (ну и написать адаптер, если их интерфейсы с DBI не совпадают :) Процедура создания объекта в приложении не изменилась.

Но осталась одна проблема: UrlFetcher теперь имеет статическую зависимость от ServiceLocator. Если вы используете класс у себя в приложении — в этом нет ничего страшного, но если вы захотите выложить его на cpan, придется выкладывать и ServiceLocator. Поэтому довольно часто применяют DI:
sub new
{
	$self->{dbh} =shift;
	$self->{user_agent} = shift;
}


а на верхнем уровне (в контроллере, например) разрешают зависимость:
UrlFetcher->new(
ServiceLocator::instance()->getDbh(),
ServiceLocator::instance()->getUserAgent()
);


Вот еще хорошая статья на тему.
По первой части комментария. Инъекция зависимостей (DI) и сервис локатор являются формами инверсии зависимостей. С сервис локатором может работать не только верхний уровень приложения (например, контроллер в MVC) но и классы модели. Банальный пример: класс модели через локатор самостоятельно вытягивает dbh.

И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта?

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

только-то и надо, что в классе вместо my $dbh = DBI->new(); надо написать my $dbh = shift;

Здесь вы просто устранили статическую зависимость использовав инъекцию зависимости (допустим, через конструктор), не больше. Но в дальнейшем при использовании класса эту зависимость необходимо разрешить: передать в конструктор объект DBI. Это вам придется делать либо руками, либо на помощь придет контейнер. В последнем случае зависимость будет разрешена автоматически, достаточно в контейнер добавить следующее:
$container->register('foo', class => 'Foo', inject => 'new', arguments => ['dbh_production']);

Именно для этого нужен контейнер: для автоматического разрешения зависимостей. Пример с dbh слишком простой. Обычно класс A требует в конструктор экземпляр класса B, тот в свою очередь ожидает D, C, а C хочет объект E. Это обычная картина при использовании DI. В таких случаях профит от использования контейнера становится более очевидным :) Но, повторюсь, контейнер — это просто способ разрешения зависимостей. DI можно (а может и нужно) практиковать без использования контейнера.

Если воспользоваться локатором, то my $dbh = DBI->new() меняется на:
my $dbh = ServiceLocator::instance()->getDbh();

Статическая зависимость от DBI исчезает, но класс, как и прежде, скрывает информацию о своих зависимостях внутри себя и самостоятельно их разрешает. Разница по сравнению с push подходом в DI очевидна.
Теперь что касается вашего кода. Изначально вы представили даже не сервис локатор, а обычный реестр, наделив его возможностью создавать синглтоны. В дальнейшем вы показали пример с разрешением зависимостей, но на самом деле, никакого автоматического разрешения зависимостей нет. Вы повесили хендлер, который самостоятельно вытянул (запулил) из реестра ранее определенные значения.

С использованием абстрактного контейнера в вакууме, который реализует управление зависимостями, и несколько упрощая, ваш пример из комментария выглядел бы следующим образом:

$container->register('production_config', { RaiseError => 1 });
$container->register('sandbox_config', { RaiseError => 0 });
$container->register('dbd', 'dbi:ExampleP');
$container->register('dbh_production', class => 'DBI', inject => 'connect', arguments => ['production_config', 'dbd']);
$container->register('dbh_sandbox', class => 'DBI', inject => 'connect', arguments => ['sandbox_config', 'dbd']);

Пример Peco::Container — позиционируемый как «Light Inversion of Control (IoC) container». Попробуйте взять последнюю строку и проследить за происходящим в обратном порядке.

Это не проблема реализации (по сравнению с тем же Bread::Board, Peco::Container, действительно, очень легкий). Проблема в самих контейнерах: их конфигурирование довольно сложно как на этапе создания, так и на этапе поддержки.

Мы используем в своих проектах динамический сервис локатор, написанный по мотивам toolkit-a из замечательного фреймворка limb, без малого пять лет, и полностью им довольны. Вам, судя по тому что вы пытаетесь получить, а не как это называете, тоже ближе именно такой полход.

P.S. Как вы код хайлайтите? :)
Судя по вашим комментариям выше, вы вполне адекватно реагируете на критику, поэтому отвечу развернуто.

Все, что нужно от DIc — возможность положить туда кусок кода и позднее получить результат его выполнения.

Здесь вы описали паттерн Registry. Его основная задача — что-то сохранить (значение, ссылку, код), чтоб потом это что-то отдать. Если из реестра сделать синглтон — получится Service Locator. Основная задача локатора — минимизировать количество статических зависимостей. Устранить их полностью не получится, т.к. остается минимум одна зависимость — сам локатор. Объекты самостоятельно обращаются к сервис локатору для получения зависимостей, поэтому данный подход называется pull (lookup). Выглядит это следующим образом:

my $locator = ServiceLocator::instance();
my $base_url = $locator->getConfig()->get('base_url');


IoC контейнеры исповедуют другую идеологию. Классы реализуют простейший механизм внедрения зависимостей — через конструктор, реже через сеттер. Статических зависимостей, как в случае с сервис локатором, нет вообще. Контейнер предварительно конфигурируется, а потом фактически выступает в роли глобальной фабрики. При этом контейнер самостоятельно (это важно), на основе конфига, прокидывает зависимости в ничего не подозревающие объекты, поэтому такой подход называется push. Т.е. IoCC, как и следует из названия, — это контейнер, управляющий зависимостями.

Если интересна теория, рекомендую доклад Сергея Юдина с phpconf 2007 и статью Фаулера. Выше тоже хорошо описали разницу.
У вас все-равно остался обычный сервис локатор :)

Information

Rating
Does not participate
Registered
Activity