На HK есть четырехканальный wifi приемник, в качестве передатчика используется софт под ios или android. Софт крайне убогий, но для экспериментов вполне подойдет.
Ну не можете — не пользуйтесь, я Вас не заставляю. :)
Вы просили критику — вы ее получили, причем вполне конструктивную. Я вам продимострировал пример, из которого видно что, простой код с использованием 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 она одна — инверсия зависимостей. Разница в деталях, которую вы и без меня уловили. Хотите проще?
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;
}
а на верхнем уровне (в контроллере, например) разрешают зависимость:
По первой части комментария. Инъекция зависимостей (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 очевидна.
Теперь что касается вашего кода. Изначально вы представили даже не сервис локатор, а обычный реестр, наделив его возможностью создавать синглтоны. В дальнейшем вы показали пример с разрешением зависимостей, но на самом деле, никакого автоматического разрешения зависимостей нет. Вы повесили хендлер, который самостоятельно вытянул (запулил) из реестра ранее определенные значения.
С использованием абстрактного контейнера в вакууме, который реализует управление зависимостями, и несколько упрощая, ваш пример из комментария выглядел бы следующим образом:
Пример Peco::Container — позиционируемый как «Light Inversion of Control (IoC) container». Попробуйте взять последнюю строку и проследить за происходящим в обратном порядке.
Это не проблема реализации (по сравнению с тем же Bread::Board, Peco::Container, действительно, очень легкий). Проблема в самих контейнерах: их конфигурирование довольно сложно как на этапе создания, так и на этапе поддержки.
Мы используем в своих проектах динамический сервис локатор, написанный по мотивам toolkit-a из замечательного фреймворка limb, без малого пять лет, и полностью им довольны. Вам, судя по тому что вы пытаетесь получить, а не как это называете, тоже ближе именно такой полход.
Судя по вашим комментариям выше, вы вполне адекватно реагируете на критику, поэтому отвечу развернуто.
Все, что нужно от DIc — возможность положить туда кусок кода и позднее получить результат его выполнения.
Здесь вы описали паттерн Registry. Его основная задача — что-то сохранить (значение, ссылку, код), чтоб потом это что-то отдать. Если из реестра сделать синглтон — получится Service Locator. Основная задача локатора — минимизировать количество статических зависимостей. Устранить их полностью не получится, т.к. остается минимум одна зависимость — сам локатор. Объекты самостоятельно обращаются к сервис локатору для получения зависимостей, поэтому данный подход называется pull (lookup). Выглядит это следующим образом:
my $locator = ServiceLocator::instance();
my $base_url = $locator->getConfig()->get('base_url');
IoC контейнеры исповедуют другую идеологию. Классы реализуют простейший механизм внедрения зависимостей — через конструктор, реже через сеттер. Статических зависимостей, как в случае с сервис локатором, нет вообще. Контейнер предварительно конфигурируется, а потом фактически выступает в роли глобальной фабрики. При этом контейнер самостоятельно (это важно), на основе конфига, прокидывает зависимости в ничего не подозревающие объекты, поэтому такой подход называется push. Т.е. IoCC, как и следует из названия, — это контейнер, управляющий зависимостями.
Если интересна теория, рекомендую доклад Сергея Юдина с phpconf 2007 и статью Фаулера. Выше тоже хорошо описали разницу.
Насколько просто на практике происходит миграция с Selenium 1.0 на WebDriver? До миграции гоняли тесты в браузере или использовали RC?
Вы просили критику — вы ее получили, причем вполне конструктивную. Я вам продимострировал пример, из которого видно что, простой код с использованием KC стал больше и сложнее для понимания. А вы с этим вроде бы и пытались бороться.
На самом деле все довольно понятно — имеющиеся модули, заявляющие реализацию DI — сложны для понимания и фантастически сложны в использовании, причем кода получается на меньше, а больше.
Вы можете вынести пользу из нашей дискуссии: показать пример, где использование KC было бы оправдано по сравнению с обычным кодом. А потом этот же пример разместить в документации на cpan: без KC, то же самое с KC и в чем профит. Я сходу придумать такого примера не смог, вам, как автору, должно быть проще.
«Мой модуль, использую, потому что хочу, не хотите — не используйте» тоже аргумент, конечно, но менее убедительный :)
Kaiten::Container позволяет декларативно (с довольно громоздким синтаксисом) объявить хендлеры и их свойства, при этом зависимости описываются, как вы говорите, императивно т.е. фактически просто пишется разрешающий их код. Т.е. KC не поддерживает автоматического разрешения зависимостей, делать это нужно руками.
Код с KC будет:
А без KC:
Сомнительная польза, даже с учетом разных вкусов :) И я сходу не могу придумать ни один пример, при котором использование вашей абстракции будет оправдано т.е. не усложнит мне жизнь, а упростит.
Нужен синглтон?
Полностью императивно и не нужен никакой Kaiten::Container :)
my $dbh = $self->dbh;
А откуда у вас в $self->dbh возьмется, собственно, экземпляр DBI?
Контейнеры же ничего не требуют.
Мне кажется, вы продолжаете путать IoCC и сервис локатор / реестр. Если класс не поддерживает DI, каким образом контейнер сможет инъектировать в него зависимость?
Я предлагаю переместится в одну ветку — ниже. Вроде там мы уже подошли совсем близко к тому, что, как написал выше koorchik: Kaiten::Container — это обычный Service Locator :)
Одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Предположим, у вас есть класс UrlFetcher у которого есть статическая зависимость на LWP и DBI:
Создание объекта в приложении выглядит следующим образом:
Используя сервис локатор вы избавляетесь от статических зависимостей:
Теперь, при необходимости, например, заменить DBI на SuperNewDBI достаточно положить его в локатор (ну и написать адаптер, если их интерфейсы с DBI не совпадают :) Процедура создания объекта в приложении не изменилась.
Но осталась одна проблема: UrlFetcher теперь имеет статическую зависимость от ServiceLocator. Если вы используете класс у себя в приложении — в этом нет ничего страшного, но если вы захотите выложить его на cpan, придется выкладывать и ServiceLocator. Поэтому довольно часто применяют DI:
а на верхнем уровне (в контроллере, например) разрешают зависимость:
Вот еще хорошая статья на тему.
И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта?
Классы знают о своих зависимостях, это очевидно. Но они ничего не знают о том, каким образом эти зависимости будут в них прокинуты. Поэтому они даже не подозревают о существовании контейнера, именно это имелось в виду.
только-то и надо, что в классе вместо my $dbh = DBI->new(); надо написать my $dbh = shift;
Здесь вы просто устранили статическую зависимость использовав инъекцию зависимости (допустим, через конструктор), не больше. Но в дальнейшем при использовании класса эту зависимость необходимо разрешить: передать в конструктор объект DBI. Это вам придется делать либо руками, либо на помощь придет контейнер. В последнем случае зависимость будет разрешена автоматически, достаточно в контейнер добавить следующее:
Именно для этого нужен контейнер: для автоматического разрешения зависимостей. Пример с dbh слишком простой. Обычно класс A требует в конструктор экземпляр класса B, тот в свою очередь ожидает D, C, а C хочет объект E. Это обычная картина при использовании DI. В таких случаях профит от использования контейнера становится более очевидным :) Но, повторюсь, контейнер — это просто способ разрешения зависимостей. DI можно (а может и нужно) практиковать без использования контейнера.
Если воспользоваться локатором, то my $dbh = DBI->new() меняется на:
Статическая зависимость от 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 и статью Фаулера. Выше тоже хорошо описали разницу.