=Intro
Покажу как реализуется этот паттерн (используя Perl) и перечислю основные мотивы применявшихся рефакторингов.
=Быстрое_погружение
Начну с примера в котором явно выражены архитектурные проблемы. Встречаем horror.pl:
#!/usr/bin/perl use lib::abs qw( base_lib lib ); use JIP::ToolSet; use Reporter; Reporter->new()->send_reports();
Reporter.pm:
package Reporter; use parent qw( Class::Base ); use JIP::ToolSet; use Carp qw( croak ); use ReportBuilder; use ReportSender; sub send_reports { my $self = shift; my @reports = ReportBuilder->new()->create_reports(); croak 'no reports exception' unless @reports; my $report_sender = ReportSender->new(); for my $each_report ( @reports ) { $report_sender->send( $each_report ); } } 1;
Устроен Reporter очень просто. В единственном методе send_reports() он получает список репортов, рапортует о проблемах если репорты отсутствуют, рассылает репорты адресатам. Даже у такой простой архитектуры может быть много врожденных пороков...
Тестируемость:
-- даже для случая когда @reports пуст (попытка вызвать исключение) придется изрядо напрячь извилины. плохое тестовое покрытие усложняет внесение изменений.
Связанность:
-- знает о том что именно ReportBuilder создает отчеты и умеет инстанцировать ReportBuilder.
-- знает о том что именно ReportSender отправляет отчеты и умеет инстанцировать ReportSender.
Сопротивление изменениям:
-- предположим что ReportSender надо заменить на другой concrete-репортер (например SMTP будет вытеснен XMPP).
-- предположим что у ReportBuilder появятся именованные параметры в конструкторе.
Таким образом получен довольно яркий пример нарушения принципа инверсии зависимостей.
=Работа_над_ошибками
Вношу простые изменения. Для начала заменю зависимость от конкретных реализаций ReportBuilder и ReportSender на зависимость от абстракций (а вот здесь важно учитывать специфику опубликованных интерфейсов). Классы "билдер" и "сендер" будут инстанцироваться вне send_reports() и экземпляры этих классов будут получены конструктором Reporter::new():
#!/usr/bin/perl use lib::abs qw( base_lib lib ); use JIP::ToolSet; use Reporter; use ReportBuilder; use ReportSender; Reporter->new( builder => ReportBuilder->new(), sender => ReportSender->new(), )->send_reports();
Реализация send_reports() стала еще короче и нагляднее:
package Reporter; use parent qw( Class::Base Class::Accessor::Fast ); use JIP::ToolSet; use Carp qw( croak ); __PACKAGE__->mk_accessors( qw( _builder _sender ) ); sub init { my ( $self, $config ) = @_; # обязательные именованные параметры croak 'required parameter "builder" not defined' unless exists $config->{'builder'}; croak 'required parameter "sender" not defined' unless exists $config->{'sender'}; # собственно инициализация свойств объекта $self->_builder( $config->{'builder'} ); $self->_sender( $config->{'sender'} ); return $self; } sub send_reports { my $self = shift; my @reports = $self->_builder->create_reports(); croak 'no reports exception' unless @reports; for my $each_report ( @reports ) { $self->_sender->send( $each_report ); } } 1;
Такой код уже намного легче тестировать. Новые свойства (_builder & _sender) я предпочел спрятать. Появилась зависимость от абстракций? Похоже что нет. Таков Perl. Надо помнить специфику опубликованных интерфейсов "билдер" и "сендер" тк в Perl отсутствуют такие усилители ООП-абстракций как interfaces в Java и .NET, зато есть смешные "роли" которыми можно играть подключив модули CPAN. Ниже смешной и бесполезный пример...
ReportBuilder.pm:
package ReportBuilder; use parent qw( Class::Base ); use Class::Roles role => 'create_reports'; use JIP::ToolSet; sub create_reports { my $self = shift; return qw( first_report second_report ); } 1;
ReportSender.pm:
package ReportSender; use JIP::ToolSet; use Class::Roles role => [ 'new', 'send' ]; sub new { # code } sub send { # code } 1;
В конструкторе Reporter появились проверки (does()):
sub init { my ( $self, $config ) = @_; # обязательные именованные параметры croak 'required parameter "builder" not defined' unless exists $config->{'builder'}; croak 'required parameter "sender" not defined' unless exists $config->{'sender'}; # проверка соответствия интерфейсу (роли) croak 'is not a ReportBuilder' unless $config->{'builder'}->does('ReportBuilder'); croak 'is not a ReportSender' unless $config->{'sender'}->does('ReportSender'); # собственно инициализация свойств объекта $self->_builder( $config->{'builder'} ); $self->_sender( $config->{'sender'} ); return $self; }
Вот теперь все готово. На практике реализован принцип инверсии зависимости. Репортер зависит только от интерфейсов. Все указанные проблемы решены. Можно писать более подробные тесты.
Десертный (как мне не хватает цветовой desert-темы в ЖЖ) кусок кода с фабрикой (о других вариантах в следующих статьях):
#!/usr/bin/perl use lib::abs qw( base_lib lib ); use JIP::ToolSet; use Reporter; use ReportBuilderFactory; use ReportSenderFactory; Reporter->new( builder => ReportBuilderFactory->instantiate('tratata_report'), sender => ReportSenderFactory->instantiate('XMPP'), # SMTP, STDOUT etc )->send_reports();
=Outro
Теперь сам принцип...Принцип, который применен при отделении верхнего уровня от всех остальных, называется принципом инверсии зависимостей: «Модули верхних уровней не должны зависеть от модулей нижних уровней — и те, и другие должны зависить от абстракций».
=cut
Читайте также
Последние новости
План занятий
Обучение детей от года до 3 лет плаванию, как правило, проходит в три этапа. На первом этапе ребенок должен адаптироваться к воде, избавиться от страха перед глубиной, неизвестной средой. Намного проще дети привыкают к бассейну, где есть бортики, вода теплая и прозрачная. Чуть сложнее дети адаптируются к открытым водоемам с темной и прохладной ...Читать далее »
Гимнастика от 2 лет до 2 лет 6 месяцев
1. Самостоятельная ходьба. 2. Бег вдогонку за взрослым или к взрослому в разном темпе. 3. Руки вверх, потянуться – «деревья большие большие», развести руки в стороны. 4. Ходьба по доске, приподнятой над полом на 15–20 см. 5. Приседания. 6. Подъем туловища с опорой на ладони в положении лежа на животе. 7. Хлопк...Читать далее »
Проблема: ожоги, ушибы, травмы
Немного повзрослев, ребенок начинает интересоваться источниками огня, что чревато ожогом. При незначительном ожоге необходимо поврежденный участок тела поместить под холодную проточную воду, а затем обработать антиожоговым аэрозолем. В период выздоровления применяют массаж, который способствует улучшению лимфо– и кровотока. В резуль...Читать далее »
Проблема: плоскостопие
Когда ребенок начинает ходить, его первые шаги могут быть омрачены плоскостопием. К сожалению, если эта болезнь наследственная, то волнения вполне обоснованны. И здесь без посещения детского врача ортопеда не обойтись. Его рекомендации относятся к укреплению подошвенного свода стопы вашего ребенка. Костная структура стопы ребенка ...Читать далее »
Рефлекторные движения
Рефлекторные гимнастические упражнения, В основе которых лежат врожденные двигательные рефлекторные реакции, проводятся первые три пять месяцев жизни ребенка, когда еще не утрачены безусловные двигательные рефлексы – «автоматическая походка», ладонно ротовой рефлекс, хоботковый рефлекс, сохранение равновесия, защитно оборонительные рефлексы. Рефлекторные гимнасти...Читать далее »
Техника выполнения: растирание
Прямолинейное растирание Выполняется концевыми фалангами одного или нескольких пальцев. Движение проводится прямолинейно одной рукой или обеими, иногда с отягощением. Круговое растирание Проводится с помощью круговых движений концевыми фалангами одного или нескольких пальцев. Кисть располагается с опорой на основании ладони, а манипуляции выполняют в сторону мизинца одной рук...Читать далее »
Проблема: пупочная грыжа
Пупочная грыжа – это патологическое состояние, в котором через несколько расширенное пупочное кольцо происходит выпячивание брюшины, сальника и даже кишечника. Причина заболевания следующая: вследствие дефекта передней брюшной стенки и пупочного кольца проявляется округлое или овальное выпячивание. Чаще факторами, провоцирующими повышение внутрибрюшного давления, являются кашель, запоры ...Читать далее »
