Comments 56
почему Perl так редко используется?
Когда приходится работать на этом языке, я спрашиваю, «Господи, за что?????».
Когда приходится работать на этом языке, я спрашиваю, «Господи, за что?????».
-7
Perl, увы, не знаю, но идея кажется интересной не только для него. Подскажите, что мы после
my $dbh = $container->get_by_name('ExampleP');
получим в dbh? Результат работы DBI->connect( "dbi:ExampleP:", "", "", { RaiseError => 1 } ) or die $DBI::errstr
или непосредственно вызов этого выражения?0
А! понял вопрос. Кажется.
Вызов выражения.
В момент описания конфига мы кладем полуфабрикат, он сырой.
В момент вызова get_by_name полуфабрикат размораживается.
Т.е. код НЕ будет исполнен до тех пор, пока его не вызовут в первый раз.
Вызов выражения.
В момент описания конфига мы кладем полуфабрикат, он сырой.
В момент вызова get_by_name полуфабрикат размораживается.
Т.е. код НЕ будет исполнен до тех пор, пока его не вызовут в первый раз.
0
То есть после выполнения
$dbh = $container->get_by_name('ExampleP');
в $dbh будет результат выполнения DBI->connect(...), но в отличии от простого $dbh = DBI->connect(...) можем в конфиге, например, тестовом указывать и что-то другое? Или же каждый раз когда мы будем использовать dbh в других выражениях вместо него каждый раз будет вызываться DBI->connect(...) типа макроса или переменной-функции? Так может понятнее :)0
Тут профит какой — можно где-то в начале спокойно взять данные из конфигурации СИСТЕМЫ, наляпять ВСЕ хендлеры, которые нуждаются в конфигурировании или сложны в построении и положить их в контейнер.
Потом просто пробрасываем контейнер туда, где он нужен (в аргументах вызова ли, создав синглтон с ним — не суть дела) и вынимаем из него готовый НУЖНЫЙ хендлер, вся бодяга с конфигурированием и прочим нам уже не нужна.
Вот как-то так.
Потом просто пробрасываем контейнер туда, где он нужен (в аргументах вызова ли, создав синглтон с ним — не суть дела) и вынимаем из него готовый НУЖНЫЙ хендлер, вся бодяга с конфигурированием и прочим нам уже не нужна.
Вот как-то так.
0
суть в том, что дважды вызвав $container->get_by_name('ExampleP') можно получить:
— один и тот же хендлер, если используется reusable => 1 и при втором вызове то, что у нас было сделано первый раз пройдет проверку |probe|
— два РАЗНЫХ хендлера, если не используется reusable или результат первого вызова не прошел проверку |probe| (ну мало ли, база отвалились)
Можно еще рассмативать эту штуку как универсальную фабрику, наверное.
— один и тот же хендлер, если используется reusable => 1 и при втором вызове то, что у нас было сделано первый раз пройдет проверку |probe|
— два РАЗНЫХ хендлера, если не используется reusable или результат первого вызова не прошел проверку |probe| (ну мало ли, база отвалились)
Можно еще рассмативать эту штуку как универсальную фабрику, наверное.
+1
Основная задача задача перечисленных Вами фреймворков — это решить проблему, которая существует в предложенном Вами решении.
Существуют два основных подхода для уменьшения связанности классов — Service Locator и Dipendency Injection. В каждом есть свои плюсы и минусы, выбор, естественно, зависит от задач.
Kaiten::Container — это обычный Service Locator, то есть — это некий глобальный объект, который предоставляет доступ к основным подсистемам приложения.
Основные проблемы такого подхода:
1. Почти все классы зависят от этого объекта.
2. Для того, чтобы увидеть зависимости класса придется пройтись по коду и просмотреть к каким сервисам есть обращения.
3. Доступ к сервис локатору равносилен доступу ко всем подсистемам приложения.
В случае с Dipendency Injection мы передаем все зависимости в класс снаружи — в конструктор или через сеттеры. Следовательно, всегда есть возможность, глянув на API класса, выявить его зависимости.
Класс получается независим от глобальных объектов, удобен для тестирования(всегда можно в конструктор передать Mock-объект)… Но такой подход, естественно, усложняет конструирование объектов, особенно когда имеются древовидные зависимости. В такой ситуации и приходят на помощь фреймворки типа Bread::Board, например. Они берут на себя конструирование объектов, а зависимости всех объектов описываются в файле конфигурации.
Существуют два основных подхода для уменьшения связанности классов — Service Locator и Dipendency Injection. В каждом есть свои плюсы и минусы, выбор, естественно, зависит от задач.
Kaiten::Container — это обычный Service Locator, то есть — это некий глобальный объект, который предоставляет доступ к основным подсистемам приложения.
Основные проблемы такого подхода:
1. Почти все классы зависят от этого объекта.
2. Для того, чтобы увидеть зависимости класса придется пройтись по коду и просмотреть к каким сервисам есть обращения.
3. Доступ к сервис локатору равносилен доступу ко всем подсистемам приложения.
В случае с Dipendency Injection мы передаем все зависимости в класс снаружи — в конструктор или через сеттеры. Следовательно, всегда есть возможность, глянув на API класса, выявить его зависимости.
Класс получается независим от глобальных объектов, удобен для тестирования(всегда можно в конструктор передать Mock-объект)… Но такой подход, естественно, усложняет конструирование объектов, особенно когда имеются древовидные зависимости. В такой ситуации и приходят на помощь фреймворки типа Bread::Board, например. Они берут на себя конструирование объектов, а зависимости всех объектов описываются в файле конфигурации.
+6
О, спасибо за классный ответ.
Ну, начнем с того, что в решении проблемы не возникает.
1. Остановитесть в одном шаге от класса, который не должен зависеть от контейнера, выньте из него (контейнера) все что вам нужно и стройте по классической схеме.
2. Не вижу разницы между кодом подготовки контейнера и классическим вариантом, вы же в одном месте все собираете.
3. Типа да? В смысле с чего такой странный вывод? Ни одна подсистема не «стрельнет», пока ее не вызовут, явно или в deep dependicies resolving, коя есть с версии 0.25.
Что до тестирования — соберите с Mock- ами или поменяйте один из хендлеров на нужный Вам, делов-то.
KC теперь поддерживает кострукции типа
Делаете remove-add и все.
Вот посмотрите еще раз на пример и объясните, где я делаю по-другому?
Мы строим ExampleP хендлер, нычим его и достаем потом, как он понадобится.
типа
Просто хендлер может и не понадобится, тогда он и НЕ отработает. Или его далеко тащить надо. Вот и все.
Ну, начнем с того, что в решении проблемы не возникает.
1. Остановитесть в одном шаге от класса, который не должен зависеть от контейнера, выньте из него (контейнера) все что вам нужно и стройте по классической схеме.
2. Не вижу разницы между кодом подготовки контейнера и классическим вариантом, вы же в одном месте все собираете.
3. Типа да? В смысле с чего такой странный вывод? Ни одна подсистема не «стрельнет», пока ее не вызовут, явно или в deep dependicies resolving, коя есть с версии 0.25.
Что до тестирования — соберите с Mock- ами или поменяйте один из хендлеров на нужный Вам, делов-то.
KC теперь поддерживает кострукции типа
my $config = {
examplep_config => {
handler => sub { { RaiseError => 1 } },
probe => sub { 1 },
settings => { reusable => 1 },
},
examplep_dbd => {
handler => sub { "dbi:ExampleP:" },
probe => sub { 1 },
settings => { reusable => 1 },
},
ExampleP => {
handler => sub {
my $c = shift;
my $dbd = $c->get_by_name('examplep_dbd');
my $conf = $c->get_by_name('examplep_config');
return DBI->connect( $dbd, "", "", $conf ) or die $DBI::errstr;
},
probe => sub { shift->ping() },
settings => { reusable => 1 }
},
};
Делаете remove-add и все.
В случае с Dipendency Injection мы передаем все зависимости в класс снаружи
Вот посмотрите еще раз на пример и объясните, где я делаю по-другому?
Мы строим ExampleP хендлер, нычим его и достаем потом, как он понадобится.
типа
my $dbh = $container->get_by_name('ExampleP');
my $person = Person->new( dbh => $dbh );
Просто хендлер может и не понадобится, тогда он и НЕ отработает. Или его далеко тащить надо. Вот и все.
+1
Мне кто-нибудь может объяснить зачем нужен IoC и DI в интерпретируемом языке?
-1
Владение методиками IoC и DI очищает карму, а строчный eval — авидья и еще больше раскручивает колесо сансары.
+1
Я спрашивал зачем он нужен в интерпретируемом языке, а не зачем владение методиками. Зачем в java я представляю, а зачем в перле это нет.
-2
А чем отличается интерпретируемый язык от компилируемого, если вы не используете генерацию кода «на лету»?
0
К примеру тем что его можно изменять существенно быстрее. А учитывая как громоздко выглядит DI в perl без аннотаций его необходимость довольно сомнительна.
-2
так для легкости DI и был написан сабжевый модуль :)
изменять-то быстрее, но, право слово, это не повод для хардкода и прочей ереси.
DI дает гибкость и меньшую связанность.
изменять-то быстрее, но, право слово, это не повод для хардкода и прочей ереси.
DI дает гибкость и меньшую связанность.
0
Хотя бы для изоляции при юнит-тестировании, для подстановки стабов и моков вместо реальных объектов.
Можно, конечно, — по крайней мере в PHP, в Perl не знаю, — в тестах создавать объекты через рефлексию без вызова конструктора, затем вручную их настраивать моками и стабами, — опять же через рефлексию, так как к приватным свойствам у тестов доступа нет, — и гонять тесты, но это ещё больше кода выходит, чем DI через конструкторы или сеттеры, не говоря о контейнерах.
Можно, конечно, — по крайней мере в PHP, в Perl не знаю, — в тестах создавать объекты через рефлексию без вызова конструктора, затем вручную их настраивать моками и стабами, — опять же через рефлексию, так как к приватным свойствам у тестов доступа нет, — и гонять тесты, но это ещё больше кода выходит, чем DI через конструкторы или сеттеры, не говоря о контейнерах.
0
Мне кажется в описанных perl-реализациях DI (как пример) есть концептуальная ошибка. Пусть у нас есть приложение (MyApp) и мы хотим в него внедрить внешний ресурс — например хэндлер к бд (db_handler, экземпляр DB) или к логгеру (logger_handler, экземпляр Log), с последующей возможностью подменить любой из хэндлеров (мы любим тесты). То при передачи в MyApp хэндлеров db_handler и logger_handler получится, что мы передаём непосредственно экземпляр класса DB и Log. Вот тут и кроется фундаментальная ошибка. При замене DB на DB2 мы будем обязаны реализовать в классе DB2 методы с точно таким же функционалом как в DB. То есть «свобода выбора» призрачная.
А что нужно? Как хороший пример — реализация в java. Я не владею этим языком, но изучение темы в сети дало вот что. Между приложением MyApp и классами DB и Log должен быть «интерфейс». Пусть у нас это будут iDB и iLog соответсвенно. Пусть в нашем приложении от Log необходим только метод error. Тогда интерфейс iLog должен иметь алиас error который вызывает Log->error или Log2->i_am_error в зависимости от того, какой внешний класс нам нужен. Я опустил, что интерфес так же должен управлять передачей аргументов по установленным правилам.
И вот теперь мое приложение MyApp будет использовать систему логирования или записи в БД независимо от реализации классов. Приложение будет использовать интерфейс, который гарантирует наличие необходимых методов.
У нас в perl например Bread::Board (http://search.cpan.org/dist/Bread-Board/) так же передаёт непосредственно хэндлеры внешних классов DB и Log в приложение. Это вроде как не совсем то, что нужно.
Может быть потому, что нет подобных реализаций и не используют?
А что нужно? Как хороший пример — реализация в java. Я не владею этим языком, но изучение темы в сети дало вот что. Между приложением MyApp и классами DB и Log должен быть «интерфейс». Пусть у нас это будут iDB и iLog соответсвенно. Пусть в нашем приложении от Log необходим только метод error. Тогда интерфейс iLog должен иметь алиас error который вызывает Log->error или Log2->i_am_error в зависимости от того, какой внешний класс нам нужен. Я опустил, что интерфес так же должен управлять передачей аргументов по установленным правилам.
И вот теперь мое приложение MyApp будет использовать систему логирования или записи в БД независимо от реализации классов. Приложение будет использовать интерфейс, который гарантирует наличие необходимых методов.
У нас в perl например Bread::Board (http://search.cpan.org/dist/Bread-Board/) так же передаёт непосредственно хэндлеры внешних классов DB и Log в приложение. Это вроде как не совсем то, что нужно.
Может быть потому, что нет подобных реализаций и не используют?
+1
Спасибо за коммент.
Вот тут накидал небольшой примерчик с использованием KC github.com/Meettya/Kaiten-Container/blob/master/ex/simple_example.pl
Суть в чем — интерфейс можно реализовать отдельным враппером, и отдавать в контейнер враппер, а в нем (враппере) — реализовывать обертку, но по мне это пустой перевод байтов.
Можно поступить намного проще — дать САМОМУ контейнеру решать, что отдавать, сделав обертку над КОНТЕЙНЕРОМ. Причем при его использовании, в тестах например, нужно разрешить только те зависимости, которые используются. То, что не используется просто игнорируется, они не требуются для работы самого контейнера.
Можно подсунуть mock, если что-то слишком громоздко для реализации, подменив любой хендлер.
Т.е. реально мое решение БОЛЕЕ демократично, чем интерфейс. Оно не настаивает на реаоизации всего и работает по принципу «позднего связывания».
Или я чего-то еще не понял?
Вот тут накидал небольшой примерчик с использованием KC github.com/Meettya/Kaiten-Container/blob/master/ex/simple_example.pl
Суть в чем — интерфейс можно реализовать отдельным враппером, и отдавать в контейнер враппер, а в нем (враппере) — реализовывать обертку, но по мне это пустой перевод байтов.
Можно поступить намного проще — дать САМОМУ контейнеру решать, что отдавать, сделав обертку над КОНТЕЙНЕРОМ. Причем при его использовании, в тестах например, нужно разрешить только те зависимости, которые используются. То, что не используется просто игнорируется, они не требуются для работы самого контейнера.
Можно подсунуть mock, если что-то слишком громоздко для реализации, подменив любой хендлер.
Т.е. реально мое решение БОЛЕЕ демократично, чем интерфейс. Оно не настаивает на реаоизации всего и работает по принципу «позднего связывания».
Или я чего-то еще не понял?
+2
Понимаете, если нет прокладки между рулём и сиденьем, которая будет страховать приложение от изменений снаружи в обязательном порядке, то выходит, что это просто «красивая обёртка над синглтоном» со списком хэндлеров. Ради чего вообще использовать распределённую систему с изолированными компонентами?
Вы сказали что враппер (интерфейс) — пустой перевод байт. Нет. Это одна из составляющих стабильности и успеха.
Но для _старта_ или _быстрой_сборки_ можно использовать прозрачное проксирование методов внешнего ресурса через интерфейс (но он должен быть с самомго начала). Опционально должно быть «исключение» интерфейса, а не его включения.
Но это всё теория. Если мы задумываемся о использовании этих концепций, значит у нас уже можно разбить систему на составляющие, которые со старта дожны быть изолированными.
Вы сказали что враппер (интерфейс) — пустой перевод байт. Нет. Это одна из составляющих стабильности и успеха.
Но для _старта_ или _быстрой_сборки_ можно использовать прозрачное проксирование методов внешнего ресурса через интерфейс (но он должен быть с самомго начала). Опционально должно быть «исключение» интерфейса, а не его включения.
Но это всё теория. Если мы задумываемся о использовании этих концепций, значит у нас уже можно разбить систему на составляющие, которые со старта дожны быть изолированными.
+1
Нет, это не «просто «красивая обёртка над синглтоном» со списком хэндлеров».
Это синглтон, DI контейнер, абстракный интерфейс, система тестирования компонентов — все вместе или каждое по отдельности. Что захотите, то делать и будет.
Еще раз попытаюсь объяснить — я не говорю «интерфейс отстой», я говорю «KC сам по себе абстракный интерфейс, ему не нужны обертки ниже по течению».
Потом — компоненты могут быть связаны, могут быть изолированны. Сами решаете. Если зависимость не разрешена — все умрет. Пазрешаена одна из сотни и вы пользуетесь только ей — оно будет работать. И ничто не мешает собрать большую систему из этих компонентов, зависимости внутри которых разрешены на 1%, если это тот процент, что вам нужен. Для теста, к примеру.
Это синглтон, DI контейнер, абстракный интерфейс, система тестирования компонентов — все вместе или каждое по отдельности. Что захотите, то делать и будет.
Еще раз попытаюсь объяснить — я не говорю «интерфейс отстой», я говорю «KC сам по себе абстракный интерфейс, ему не нужны обертки ниже по течению».
Потом — компоненты могут быть связаны, могут быть изолированны. Сами решаете. Если зависимость не разрешена — все умрет. Пазрешаена одна из сотни и вы пользуетесь только ей — оно будет работать. И ничто не мешает собрать большую систему из этих компонентов, зависимости внутри которых разрешены на 1%, если это тот процент, что вам нужен. Для теста, к примеру.
+2
В приведённом примере я хочу заменить логгер на логгер2
Который отличается так:
В приложении (package main) используется вызов
Можно понятным и простым способом изменить контейнер/конфиги так, чтобы вызов в приложении не изменился?
Который отличается так:
#===================================
package LoggerEngine2;
#===================================
# ...
sub output2 {
my $self = shift;
my $message = shift;
say( ( $self->level ? 'DEBUG ON: ' : 'DEBUG OFF: ' ) . $message );
}
# ...
В приложении (package main) используется вызов
$logger->output( 'it is worked at - ' . $full_name );
Можно понятным и простым способом изменить контейнер/конфиги так, чтобы вызов в приложении не изменился?
0
Хороший вопрос.
Действительно, в таком случае мне нужен интерфейсный модуль, по-другому транслировать разноименные методы кажется и не получится.
Как-то примено так — gist.github.com/1505271
Но! если для тестирования приложения мы откатывамся на LoggerEngine — реализация ILogger нам уже не нужна. Вся логика может быть оставлена, а модуль отсутствовать — контейнер в состоянии эмулировать (не слишком умно, но все же) интерфейс.
Вот такми образом — gist.github.com/1505295
Да, как вы понимаете, все вызовы add(*Logger) в main только для наглядности, ничто не мешает выкинуть их в ClobalConstructor, и подключать их там, в процессе create_container, после создания основной базы.
В таком случае все изменения затронут только ClobalConstructor + модуль ILogger, ничего не попишешь, внести изменения ТОЛЬКО в одном месте не получится.
Действительно, в таком случае мне нужен интерфейсный модуль, по-другому транслировать разноименные методы кажется и не получится.
Как-то примено так — gist.github.com/1505271
Но! если для тестирования приложения мы откатывамся на LoggerEngine — реализация ILogger нам уже не нужна. Вся логика может быть оставлена, а модуль отсутствовать — контейнер в состоянии эмулировать (не слишком умно, но все же) интерфейс.
Вот такми образом — gist.github.com/1505295
Да, как вы понимаете, все вызовы add(*Logger) в main только для наглядности, ничто не мешает выкинуть их в ClobalConstructor, и подключать их там, в процессе create_container, после создания основной базы.
В таком случае все изменения затронут только ClobalConstructor + модуль ILogger, ничего не попишешь, внести изменения ТОЛЬКО в одном месте не получится.
+1
Ну вот, теперь интереснее. Молодцом!
0
Ну в общем как-то так это выглядит в «причесанном» варианте.
github.com/Meettya/Kaiten-Container/blob/master/ex/change_engine_example.pl
Теперь это пример тоже кусок теста. Все всуп дело, все в дело.
github.com/Meettya/Kaiten-Container/blob/master/ex/change_engine_example.pl
Теперь это пример тоже кусок теста. Все в
0
В посте описан обычный service locator. Это альтернатива IoC контейнерам, которая имеет как свои преимущества, так и недостатки. Принципиальная разница между ними: SL — это pull подход, IoCC — push.
0
Т.е. я верно понимаю, чтобы у нас было «IoCC — push» нам нужен контейнер, в котором на момент его запуска гвоздями прибито все, что ему может пригодится?
Если я не прав — можно коротенький пример или ссылку?
Если я не прав — можно коротенький пример или ссылку?
0
Судя по вашим комментариям выше, вы вполне адекватно реагируете на критику, поэтому отвечу развернуто.
Все, что нужно от DIc — возможность положить туда кусок кода и позднее получить результат его выполнения.
Здесь вы описали паттерн Registry. Его основная задача — что-то сохранить (значение, ссылку, код), чтоб потом это что-то отдать. Если из реестра сделать синглтон — получится Service Locator. Основная задача локатора — минимизировать количество статических зависимостей. Устранить их полностью не получится, т.к. остается минимум одна зависимость — сам локатор. Объекты самостоятельно обращаются к сервис локатору для получения зависимостей, поэтому данный подход называется pull (lookup). Выглядит это следующим образом:
my $locator = ServiceLocator::instance();
my $base_url = $locator->getConfig()->get('base_url');
IoC контейнеры исповедуют другую идеологию. Классы реализуют простейший механизм внедрения зависимостей — через конструктор, реже через сеттер. Статических зависимостей, как в случае с сервис локатором, нет вообще. Контейнер предварительно конфигурируется, а потом фактически выступает в роли глобальной фабрики. При этом контейнер самостоятельно (это важно), на основе конфига, прокидывает зависимости в ничего не подозревающие объекты, поэтому такой подход называется push. Т.е. IoCC, как и следует из названия, — это контейнер, управляющий зависимостями.
Если интересна теория, рекомендую доклад Сергея Юдина с phpconf 2007 и статью Фаулера. Выше тоже хорошо описали разницу.
Все, что нужно от DIc — возможность положить туда кусок кода и позднее получить результат его выполнения.
Здесь вы описали паттерн Registry. Его основная задача — что-то сохранить (значение, ссылку, код), чтоб потом это что-то отдать. Если из реестра сделать синглтон — получится Service Locator. Основная задача локатора — минимизировать количество статических зависимостей. Устранить их полностью не получится, т.к. остается минимум одна зависимость — сам локатор. Объекты самостоятельно обращаются к сервис локатору для получения зависимостей, поэтому данный подход называется pull (lookup). Выглядит это следующим образом:
my $locator = ServiceLocator::instance();
my $base_url = $locator->getConfig()->get('base_url');
IoC контейнеры исповедуют другую идеологию. Классы реализуют простейший механизм внедрения зависимостей — через конструктор, реже через сеттер. Статических зависимостей, как в случае с сервис локатором, нет вообще. Контейнер предварительно конфигурируется, а потом фактически выступает в роли глобальной фабрики. При этом контейнер самостоятельно (это важно), на основе конфига, прокидывает зависимости в ничего не подозревающие объекты, поэтому такой подход называется push. Т.е. IoCC, как и следует из названия, — это контейнер, управляющий зависимостями.
Если интересна теория, рекомендую доклад Сергея Юдина с phpconf 2007 и статью Фаулера. Выше тоже хорошо описали разницу.
0
Я еще как адекватно реагирую на критику! :) Пошел за обрезом :)
Сегодня в ночи перечитал Фаулера еще раз, и познал Дао. Вроде бы получается так, если совсем на пальцах:
— Registry & Service Locator — это тупо внешний для модулей хеш, с которым работает application (и никогда не модули, еcли по классике)
— Dependency Injection — это тупо свойство самого модуля (это если прям по Ф.), выражающееся в том, что нужную ЕМУ зависимость модуль ждет свыше(через конструктор, сеттер или интерфейс), а не стоит сам.
И то и другое по Ф. является IoC, с точки зрения application, т.к. измененяется привычная канва построения логики.
Соответственно, понятие «DI фреймверка» по сути дела означает, что мы создаем наши классы не обычным путем, а с костылями. И без этих костылей оно упадет. И уже готовые классы не обладаюшие DI-свойствами, туда не запихнуть без изменений. Если по тупому — это source filter или препроцессор, строящий дополнительный код.
Получается, что в perl DI фреймверк по сути не нужен, т.к. интерфейсов нет, а значит авто-разрешения (на уровне модуля) не будет, а без этого смысла в каком-то фремверке нет, ведь только-то и надо, что в классе вместо
И теперь плавно еще раз к топиксабжу — вот вернитесь к этому примеру — habrahabr.ru/blogs/perl/134891/#comment_4480367 и не противореча самому себе в абзаце
попробуйте объяснить мне, чем мы не IoCC для DBI, который принимает DBD как атрибут конструктора?
Дальше —
Да какая фик разница, как контейнер получает конфиг? Снаружи или читает сам — результат-то один и тот же. И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта? Ничего не подозревающими они будут если вы снаружи насильно перезагрузите конкретный (здесь — антоним абстрактного) конструктор ну или добавите сеттер, которого раньше не было.
PS. Для меня эти архитектурные дебри стойко ассоциируются с дебатами вокруг «Вороне как-то бог послал кусочек сыра» с целью выяснить, какой именно бог, и являлась ли ворона прихожанкой именно этой церкви, как был осуществлен «посыл» сыра — прямым телепортом, сотворением или результатом действия третьих лиц, размером кусочка и, что немаловажно, сортом сыра. Уж простите :)
Сегодня в ночи перечитал Фаулера еще раз, и познал Дао. Вроде бы получается так, если совсем на пальцах:
— Registry & Service Locator — это тупо внешний для модулей хеш, с которым работает application (и никогда не модули, еcли по классике)
— Dependency Injection — это тупо свойство самого модуля (это если прям по Ф.), выражающееся в том, что нужную ЕМУ зависимость модуль ждет свыше(через конструктор, сеттер или интерфейс), а не стоит сам.
И то и другое по Ф. является IoC, с точки зрения application, т.к. измененяется привычная канва построения логики.
Соответственно, понятие «DI фреймверка» по сути дела означает, что мы создаем наши классы не обычным путем, а с костылями. И без этих костылей оно упадет. И уже готовые классы не обладаюшие DI-свойствами, туда не запихнуть без изменений. Если по тупому — это source filter или препроцессор, строящий дополнительный код.
Получается, что в perl DI фреймверк по сути не нужен, т.к. интерфейсов нет, а значит авто-разрешения (на уровне модуля) не будет, а без этого смысла в каком-то фремверке нет, ведь только-то и надо, что в классе вместо
my $dbh = DBI->new();
надо написатьmy $dbh = shift;
И теперь плавно еще раз к топиксабжу — вот вернитесь к этому примеру — habrahabr.ru/blogs/perl/134891/#comment_4480367 и не противореча самому себе в абзаце
IoC контейнеры исповедуют другую идеологию. Классы реализуют простейший механизм внедрения зависимостей — через конструктор, реже через сеттер.
попробуйте объяснить мне, чем мы не IoCC для DBI, который принимает DBD как атрибут конструктора?
Дальше —
При этом контейнер самостоятельно (это важно), на основе конфига, прокидывает зависимости в ничего не подозревающие объекты, поэтому такой подход называется push.
Да какая фик разница, как контейнер получает конфиг? Снаружи или читает сам — результат-то один и тот же. И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта? Ничего не подозревающими они будут если вы снаружи насильно перезагрузите конкретный (здесь — антоним абстрактного) конструктор ну или добавите сеттер, которого раньше не было.
PS. Для меня эти архитектурные дебри стойко ассоциируются с дебатами вокруг «Вороне как-то бог послал кусочек сыра» с целью выяснить, какой именно бог, и являлась ли ворона прихожанкой именно этой церкви, как был осуществлен «посыл» сыра — прямым телепортом, сотворением или результатом действия третьих лиц, размером кусочка и, что немаловажно, сортом сыра. Уж простите :)
0
По первой части комментария. Инъекция зависимостей (DI) и сервис локатор являются формами инверсии зависимостей. С сервис локатором может работать не только верхний уровень приложения (например, контроллер в MVC) но и классы модели. Банальный пример: класс модели через локатор самостоятельно вытягивает dbh.
И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта?
Классы знают о своих зависимостях, это очевидно. Но они ничего не знают о том, каким образом эти зависимости будут в них прокинуты. Поэтому они даже не подозревают о существовании контейнера, именно это имелось в виду.
только-то и надо, что в классе вместо my $dbh = DBI->new(); надо написать my $dbh = shift;
Здесь вы просто устранили статическую зависимость использовав инъекцию зависимости (допустим, через конструктор), не больше. Но в дальнейшем при использовании класса эту зависимость необходимо разрешить: передать в конструктор объект DBI. Это вам придется делать либо руками, либо на помощь придет контейнер. В последнем случае зависимость будет разрешена автоматически, достаточно в контейнер добавить следующее:
Именно для этого нужен контейнер: для автоматического разрешения зависимостей. Пример с dbh слишком простой. Обычно класс A требует в конструктор экземпляр класса B, тот в свою очередь ожидает D, C, а C хочет объект E. Это обычная картина при использовании DI. В таких случаях профит от использования контейнера становится более очевидным :) Но, повторюсь, контейнер — это просто способ разрешения зависимостей. DI можно (а может и нужно) практиковать без использования контейнера.
Если воспользоваться локатором, то my $dbh = DBI->new() меняется на:
Статическая зависимость от DBI исчезает, но класс, как и прежде, скрывает информацию о своих зависимостях внутри себя и самостоятельно их разрешает. Разница по сравнению с push подходом в DI очевидна.
И как можно называть объекты «ничего не подозревающими», если они в курсе своей зависимости от инжекта?
Классы знают о своих зависимостях, это очевидно. Но они ничего не знают о том, каким образом эти зависимости будут в них прокинуты. Поэтому они даже не подозревают о существовании контейнера, именно это имелось в виду.
только-то и надо, что в классе вместо 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 очевидна.
0
Я ступил.
вместо
надо написать не
а конечно же
в таком случае инжект у нас и случается «прозрачно», как Вы и того и настаиваете и как требует здравый смысл.
и это выполняет Ваше DI
эквивалентным
Таки от чего мы начали-то.
DI требует, черт побери, правильно спроектированного класса, и если legacy-класс не Di-ably то вот тут то и сказочке конец и надо делать рефакторинг.
Контейнеры же ничего не требуют, прикручиваются как захочешь (к legacy — сбоку, но немедленого рефакторинга не требуют) и могут быть закинуты на любую глубину (нового кода, конечно), насколько наглость позволит.
А единственная разница между Вашими и моими примерами — Ваши декларативные, мои императивные, и отражают только предпочтения аффтора.
вместо
my $dbh = DBI->new();
надо написать не
my $dbh = shift;
а конечно же
my $self = shift;
my $dbh = $self->dbh;
в таком случае инжект у нас и случается «прозрачно», как Вы и того и настаиваете и как требует здравый смысл.
и это выполняет Ваше DI
$container->register('foo', class => 'Foo', inject => 'new', arguments => ['dbh_production']);
эквивалентным
$container->{'foo'} = Foo::new($container->{'dbh_production'});
Таки от чего мы начали-то.
DI требует, черт побери, правильно спроектированного класса, и если legacy-класс не Di-ably то вот тут то и сказочке конец и надо делать рефакторинг.
Контейнеры же ничего не требуют, прикручиваются как захочешь (к legacy — сбоку, но немедленого рефакторинга не требуют) и могут быть закинуты на любую глубину (нового кода, конечно), насколько наглость позволит.
А единственная разница между Вашими и моими примерами — Ваши декларативные, мои императивные, и отражают только предпочтения аффтора.
0
my $self = shift;
my $dbh = $self->dbh;
А откуда у вас в $self->dbh возьмется, собственно, экземпляр DBI?
Контейнеры же ничего не требуют.
Мне кажется, вы продолжаете путать IoCC и сервис локатор / реестр. Если класс не поддерживает DI, каким образом контейнер сможет инъектировать в него зависимость?
Я предлагаю переместится в одну ветку — ниже. Вроде там мы уже подошли совсем близко к тому, что, как написал выше koorchik: Kaiten::Container — это обычный Service Locator :)
my $dbh = $self->dbh;
А откуда у вас в $self->dbh возьмется, собственно, экземпляр DBI?
Контейнеры же ничего не требуют.
Мне кажется, вы продолжаете путать IoCC и сервис локатор / реестр. Если класс не поддерживает DI, каким образом контейнер сможет инъектировать в него зависимость?
Я предлагаю переместится в одну ветку — ниже. Вроде там мы уже подошли совсем близко к тому, что, как написал выше koorchik: Kaiten::Container — это обычный Service Locator :)
0
Теперь что касается вашего кода. Изначально вы представили даже не сервис локатор, а обычный реестр, наделив его возможностью создавать синглтоны. В дальнейшем вы показали пример с разрешением зависимостей, но на самом деле, никакого автоматического разрешения зависимостей нет. Вы повесили хендлер, который самостоятельно вытянул (запулил) из реестра ранее определенные значения.
С использованием абстрактного контейнера в вакууме, который реализует управление зависимостями, и несколько упрощая, ваш пример из комментария выглядел бы следующим образом:
$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. Как вы код хайлайтите? :)
С использованием абстрактного контейнера в вакууме, который реализует управление зависимостями, и несколько упрощая, ваш пример из комментария выглядел бы следующим образом:
$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. Как вы код хайлайтите? :)
0
С использованием абстрактного контейнера в вакууме...
слишком много магии.
Да и Bread::Board тоже не вариант, который зачем-то три раза, три раза, три раза описывает одну и ту-же литеральную константу. Там есть dependencies блок, только от него смысла нет, потому что дальше в block-inject он опять же дергает то-же самое. Бестолковое создание алиасов, ИМХО.
container 'Database' => as {
service 'dsn' => "dbi:sqlite:dbname=my-app.db";
service 'username' => "user234";
service 'password' => "****";
service 'dbh' => (
block => sub {
my $s = shift;
DBI->connect(
$s->param('dsn'),
$s->param('username'),
$s->param('password'),
) || die "Could not connect";
},
dependencies => wire_names(qw[dsn username password])
);
};
более того, по сути запись
service 'logger' => (
class => 'FileLogger',
dependencies => [
depends_on('log_file_name'),
]
);
100% эквивалентна псевдокоду
$self->registry->{'logger'} = FileLogger->new( $self->registry->{'log_file_name'});
так зачем плодить сущности сверх необходимого?
Т.е. получается, что динамический локатор (который и diC для готовых к этому) — просто единственно разумная штука, если готов к некоторому размазыванию логики?
P.S. можно использовать html-теги -> <source lang="perl"> </source>
0
Т.е. получается, что динамический локатор (который и diC для готовых к этому) — просто единственно разумная штука, если готов к некоторому размазыванию логики?
Одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Предположим, у вас есть класс UrlFetcher у которого есть статическая зависимость на LWP и DBI:
Создание объекта в приложении выглядит следующим образом:
Используя сервис локатор вы избавляетесь от статических зависимостей:
Теперь, при необходимости, например, заменить DBI на SuperNewDBI достаточно положить его в локатор (ну и написать адаптер, если их интерфейсы с DBI не совпадают :) Процедура создания объекта в приложении не изменилась.
Но осталась одна проблема: UrlFetcher теперь имеет статическую зависимость от ServiceLocator. Если вы используете класс у себя в приложении — в этом нет ничего страшного, но если вы захотите выложить его на cpan, придется выкладывать и ServiceLocator. Поэтому довольно часто применяют DI:
а на верхнем уровне (в контроллере, например) разрешают зависимость:
Вот еще хорошая статья на тему.
Одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Предположим, у вас есть класс 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()
);
Вот еще хорошая статья на тему.
0
а на верхнем уровне (в контроллере, например) разрешают зависимость
так а что мешает разрешать ее не в контроллере, а в «локаторе» (будем именовать это нечто единообразно) тоже?
В Вашем случае контроллер должен знат о том, как достать Dbh и UserAgent — 2 единицы знания.
Если мы разрешаем зависимости в «локаторе», то контроллер дожен знать только как достать UrlFetcher — 1 единица знания.
Как бы чем не профит-то?
0
Вы рассуждаете совершенно верно. Как я писал выше: одна из задач локатора — предоставить удобный доступ к часто используемым объектам. Если вам часто необходим UrlFetcher с одинаковой конфигурацией вполне логично вынести его создание в метод getUrlFetcher / createUrlFetcher локатора, где и разрулить его зависимости. В своем примере (хендлер ExampleP) вы это и делаете, только не добавлением метода, а регистрацией хендлера, что по своей сути одно и то же.
0
Я предлагаю переместится в одну ветку — ниже
ОК.
А откуда у вас в $self->dbh возьмется, собственно, экземпляр DBI?
ЭЭЭ… да вопрос не в том, откуда у коровы седло, а в том, что она с ним будет делать.
Если класс не предполагает
$self->dbh
то пихать в него что-то бессмысленно. Ну запихнули, и чЕ?
Вроде там мы уже подошли совсем близко к тому, что, как написал выше koorchik: Kaiten::Container — это обычный Service Locator :)
Да я как бы согласен, но только при одном условии — т.к. функционал KC абсолютно идентичен по сути всем остальным реализациям — то мы просто сходимся на том, что в perl, на сомом-то деле, нет ни одной реализации DIс и все, что у нас есть — это ServiceLocator-ы разной затейливости синтаксиса.
Так ведь? :)
0
А, еще минуточку. Дочитал Вашу же ссылочку.
Хорошо, вот объясните на пальцах, где разница между там
и (псевдокодом)
Ну вот не вижу я разницы. Ну декларативно, ну императивно — суть-то не менятеся.
Хорошо, вот объясните на пальцах, где разница между там
<beans>
<bean id="reader"
class="com.copier.consoleReader"/>
<bean id="writer"
class="com.copier.systemLogWriter"/>
<bean id="copier"
class="com.copier.copier">
<property name="source">
<ref bean="reader"/>
</property>
<property name="destination">
<ref bean="writer"/>
</property>
</bean>
</beans>
и (псевдокодом)
'reader' => { handler => sub ( consoleReader->new() )},
'writer' => { handler => sub ( systemLogWriter->new() )},
'copier' => { handler => sub ( my $c = shift; copier->new( 'source' => $c->get('reader'), 'destination' => $c->get('writer')) )}
Ну вот не вижу я разницы. Ну декларативно, ну императивно — суть-то не менятеся.
0
Суть не меняется потому, что и у IoCC и у Service Locator она одна — инверсия зависимостей. Разница в деталях, которую вы и без меня уловили. Хотите проще?
Нужен синглтон?
Полностью императивно и не нужен никакой Kaiten::Container :)
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 :)
0
а теперь представим, что systemLogWriter — объект составной и может оказаться дохлым в процессе его сборки или умереть в кеше.
добавляем probe и получаем KC :)
короче, на вкус и цвет все фломастеры разные.
Ниубидили.
добавляем probe и получаем KC :)
короче, на вкус и цвет все фломастеры разные.
Ниубидили.
0
Peco::Container, как вы верно подметили, позволяет декларативно объявить зависимости и в дальнейшем, на основе конфига, автоматически их разрешает. Именно это и является основной фичей IoCC.
Kaiten::Container позволяет декларативно (с довольно громоздким синтаксисом) объявить хендлеры и их свойства, при этом зависимости описываются, как вы говорите, императивно т.е. фактически просто пишется разрешающий их код. Т.е. KC не поддерживает автоматического разрешения зависимостей, делать это нужно руками.
Код с KC будет:
А без KC:
Сомнительная польза, даже с учетом разных вкусов :) И я сходу не могу придумать ни один пример, при котором использование вашей абстракции будет оправдано т.е. не усложнит мне жизнь, а упростит.
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;
}
Сомнительная польза, даже с учетом разных вкусов :) И я сходу не могу придумать ни один пример, при котором использование вашей абстракции будет оправдано т.е. не усложнит мне жизнь, а упростит.
0
Мы начинаем топтаться на месте, ИМХО.
В целесообразности использования конфигов в данном случае сомневается сам Фаулер. Вот как то так.
«автоматически их разрешает» -> делает некие магические пассы, позволяя не писать new(). Для меня сомнительный плюс.
«Т.е. KC не поддерживает автоматического разрешения зависимостей» -> не делает магических пассов. Для меня ощутимый плюс.
Ну не можете — не пользуйтесь, я Вас не заставляю. :)
Благодарю за местами интересную дисскуссию, но дальнейшее ее продолжение — время на ветер.
Peco::Container, как вы верно подметили, позволяет декларативно объявить зависимости и в дальнейшем, на основе конфига, автоматически их разрешает.
В целесообразности использования конфигов в данном случае сомневается сам Фаулер. Вот как то так.
«автоматически их разрешает» -> делает некие магические пассы, позволяя не писать new(). Для меня сомнительный плюс.
«Т.е. KC не поддерживает автоматического разрешения зависимостей» -> не делает магических пассов. Для меня ощутимый плюс.
Ну не можете — не пользуйтесь, я Вас не заставляю. :)
Благодарю за местами интересную дисскуссию, но дальнейшее ее продолжение — время на ветер.
0
Ну не можете — не пользуйтесь, я Вас не заставляю. :)
Вы просили критику — вы ее получили, причем вполне конструктивную. Я вам продимострировал пример, из которого видно что, простой код с использованием KC стал больше и сложнее для понимания. А вы с этим вроде бы и пытались бороться.
На самом деле все довольно понятно — имеющиеся модули, заявляющие реализацию DI — сложны для понимания и фантастически сложны в использовании, причем кода получается на меньше, а больше.
Вы можете вынести пользу из нашей дискуссии: показать пример, где использование KC было бы оправдано по сравнению с обычным кодом. А потом этот же пример разместить в документации на cpan: без KC, то же самое с KC и в чем профит. Я сходу придумать такого примера не смог, вам, как автору, должно быть проще.
«Мой модуль, использую, потому что хочу, не хотите — не используйте» тоже аргумент, конечно, но менее убедительный :)
Вы просили критику — вы ее получили, причем вполне конструктивную. Я вам продимострировал пример, из которого видно что, простой код с использованием KC стал больше и сложнее для понимания. А вы с этим вроде бы и пытались бороться.
На самом деле все довольно понятно — имеющиеся модули, заявляющие реализацию DI — сложны для понимания и фантастически сложны в использовании, причем кода получается на меньше, а больше.
Вы можете вынести пользу из нашей дискуссии: показать пример, где использование KC было бы оправдано по сравнению с обычным кодом. А потом этот же пример разместить в документации на cpan: без KC, то же самое с KC и в чем профит. Я сходу придумать такого примера не смог, вам, как автору, должно быть проще.
«Мой модуль, использую, потому что хочу, не хотите — не используйте» тоже аргумент, конечно, но менее убедительный :)
0
Кстати, конфиги из примера очень сильно ухудшают читабельность кода и размазывают бизнес-логику. Плюс не совсем понятно, как пишутся модульные тесты на хендлеры. Сравните:
против:
где профит? :)
package ServiceLocator;
...
sub getDbh
{
my $self = shift;
$self->{dbh} ||= DBI->connect(...);
}
…
my $dbh = $service_locator->getDbh();
против:
my $config = {
dbi => {
handler => sub {
return DBI->connect()
},
settings => { reusable => 1 }
}
}
...
my $dbh = $container->get_by_name('dbi');
где профит? :)
0
Sign up to leave a comment.
Почему в Perl так редко используется IoC, DI и магическая пилюля Kaiten::Container