Как написать расширяемый код?
С планированием.Допустим, вы пишете алгоритм для построения набора точек.Вам нужен источник этих точек, место для их построения и алгоритм для интерполяции точек, которых нет в наборе.
(И просто примечание, предположим, что «график» означает «график» здесь, а не граф в дискретном математическом смысле.)
Давайте определим роли, которые представляют эти операции.Источник точек должен быть в состоянии предоставить нам точки:
package Graphomatic::PointSource;
use Moose::Role;
requires 'get_points'; # return a list of points
1;
Построитель должен позволить нам построить точку:
package Graphomatic::Plot;
use Moose::Role;
requires 'plot_point'; # plot a point
requires 'show_graph'; # show the final graph
1;
И интерполятор должен дать нам точкукогда даны две соседние точки:
package Graphomatic::Interpolate;
use Moose::Role;
requires 'interpolate_point';
1;
Теперь нам просто нужно написать наше основное приложение в терминах этих ролей:
package Graphomatic;
use Moose;
use Graphomatic::PointSource;
use Graphomatic::Plot;
use Graphomatic::Interpolate;
has 'source' => (
is => 'ro',
does => 'Graphomatic::PointSource',
handles => 'Graphomatic::PointSource',
required => 1,
);
has 'plot' => (
is => 'ro',
does => 'Graphomatic::Plot',
handles => 'Graphomatic::Plot',
required => 1,
);
has 'interpolate' => (
is => 'ro',
does => 'Graphomatic::Interpolate',
handles => 'Graphomatic::Interpolate',
required => 1,
);
sub run { # actually render and display the graph
my $self = shift;
my @points = $self->get_points; # delegated from the PointSource
for my $x (some minimum .. some maximum) {
my ($a, $b) = nearest_points( $x, @points );
$self->plot_point( $self->interpolate_point($a, $b, $x) );
}
$self->show_graph;
}
1;
Теперь просто определить некоторые реализации исходного кода.,Давайте прочитаем точки из файла:
package Graphomatic::PointSource::File;
use Moose;
use MooseX::FileAttribute;
# ensure, at compile-time, that this class is a valid point
# source
with 'Graphomatic::PointSource';
has_file 'dataset' => ( must_exist => 1, required => 1 );
sub get_points {
my $self = shift;
return parse $self->dataset->slurp;
}
1;
И нанесем на график систему Z Window:
package Graphomatic::Plot::Z;
use Moose;
use Z;
with 'Graphomatic::Plot';
has 'window' => ( is => 'ro', isa => 'Z::Window', lazy_build => 1);
sub _build_window { return Z->new_window }
sub plot_point {
my ($self, $point) = @_;
$self->window->plot_me_a_point_kthx($point->x, $point->y);
}
sub show_plot {
my $self = shift;
$self->window->show;
}
1;
И интерполируем с генератором случайных чисел (эй, я ленивый, и яне собираюсь искать бикубическую интерполяцию: P):
package Graphomatic::Interpolate::Random;
use Moose;
with 'Graphomatic::Interpolate';
sub interpolate_point {
my ($self, $a, $b, $x) = @_;
return 4; # chosen by fair dice roll.
# guaranteed to be random.
}
1;
Теперь мы можем собрать все части в рабочую программу:
use Graphomatic::PointSource::File;
use Graphomatic::Plot::Z;
use Graphomatic::Interpolate::Random;
my $graphomatic = Graphomatic->new(
source => Graphomatic::PointSource::File->new(
file => 'data.dat',
),
plot => Graphomatic::Plot::Z->new,
interpolate => Graphomatic::Interpolate::Random->new,
);
$graphomatic->run;
Теперь вы можете чисто настроить любой изчасти, не влияющие на другие части, просто путем реализации новых классов, которые «выполняют» требуемые роли.(Если они говорят «с ...» и не соответствуют требованиям, вы получите сообщение об ошибке, как только загрузите класс. Если вы попытаетесь использовать экземпляр в качестве параметра, который не «делает»Правильно, конструктор умрет.
Безопасность типов, это замечательно.)
Что касается обработки файлов конфигурации, просто как-то читайте имена и параметры, а затем:
my $interpolate_class = get_config('interpolate_class');
Class::MOP::load_class($interpolate_class);
my $interpolate = $interpolate_class->new( %interpolate_class_args );
my $graphomatic = Graphomatic->new( interpolate => $interpolate, ... );
MooseX :: YAML - хороший способ автоматизировать это.