Ох уж эти паттерны. Но весчь хорошая и нужная.
Если разобраться, то паттерны мы используем всегда, просто не знаем о том, что это уже давно описанный шаблон проектирования. Мы просто снова и снова изобретаем колесо. Зачем? Почему? А потому что не читаем книг.
Стратегия.
Что такое стратегия? Не буду перепечатывать то, что можно нагуглить, скажу просто.
Стратегия - не детализированный способ достижения поставленной цели.
Как мы обычно делаем?
У нас имеется набор алгоритмов, с помощью которых мы решаем поставленную задачу. Когда нам необходимо, мы берем нужный нам алгоритм и выполняем поставленную задачу.
Как это обычно выглядит?
Предположим нам необходимо показать пользователю* блок с контекстной рекламой. При этом необходимо ротировать и таргетировать блоки по тем данным что мы знаем о пользователе, к примеру: регион, пол, возраст и т.д.
Мы реализовали несколько алгоритмов ротации: равномерная ротация, вытесняющая, волновая и т.д.
Мы можем выбирать любой из этих алгоритмов, который мы хотим использовать в данный момент
if ($choice == ADV::EVEN) {
$blocks = &_getEvenDistribution($in_blocks);
}
elsif ($choice == ADV::UNDU) {
$blocks = &_getEjectDistribution($in_blocks);
}
..................
sub _getEvenDistribution
{
my $blocks= shift;
#нахождение нужных блоков
}
sub _getEjectDistribution
{
my $blocks= shift;
#нахождение нужных блоков
}
Что нам предлагает сделать паттерн Стратегия.
- Создать интерфейс (абстрактный класс) Strategy
- Создать конкретные стратигии (алгоритмы)
- Создать контекст
- Все это объединить
Давайте это и сделаем:
# базовый класс стратегии
package AbstractStrategy;
sub new {
my $self= shift;
# запрещаем создавать экземпляр этого класса
die "need object" if (!ref $self);
#что то делаем, если требуется
return $self;
}
# что то вроде абстрактного метода
sub rotate
{
my $self = shift;
die "can not invoke";
}
1;
# класс реализующий равномерную ротацию
package EvenStrategy
use base qw(AbstractStrategy);
sub new
{
my $class = shift;
my $self = bless {}, $class;
return $self->SUPER::new(@_);
}
#для простоты будем искать не группу блоков, а один
# группу можно найти, например обычной рекурсией,
#либо иным известным способом
# sub rotate($blocks)
# in $blocks - массив хешей блоков
# out $block - самый кликабельный
sub rotate
{
my ($self, $blocks) = @_;
my ($commonWeight, $rand) = (0, 0);
#будем считать что блоки отсортированы по весу
#если нет, то это просто сделать
for (@$blocks) {
$commonWeight += $_->{weight};
$_->{interval} = $commonWeight;
}
$rand = int(rand(0, $commonWeight));
#для простоты будем искать обычным перебором,
#если блоков много лучше применять быстрый поиск
for ($i = 0; $i < (scalar(@$blocks) -1); $i++) {
if( $rand < $blocks->[$i]{weight}) {
return $blocks->[$i];
}
}
}
1;
#класс реализующий вытесняющую ротацию
package EjectStrategy
use base qw(AbstractStrategy);
sub new
{
my $class = shift;
my $self = bless {}, $class;
return $self->SUPER::new(@_);
}
# sub rotate($blocks)
# in $blocks - массив хешей блоков
# out $block - самый кликабельный
sub rotate
{
my ($self, $blocks) = @_;
#сортируем по количеству кликов
@$blocks = sort { $a->{clicks} <=> $b->{clicks} } @$blocks;
return ( pop @$blocks);
}
1;
# Класс контекста
package AdvContext;
sub new
{
my $class = shift;
my $strategy = shift;
#запретим создание экземпляра, если нам подсунули
# обект у которого нет метода rotate
if(!UNIVESAL::can($strategy, 'rotate')) {
die "need strategy";
}
my $self = bless {strategy => $strategy }, $class;
return $self;
}
sub getBlock
{
my $self = shift;
my $blocks = shift;
retrun $self->{strategy}->rotate($blcoks);
}
1;
#обработкичи запросов пользователей
.............
sub evenHandler
{
my $context = AdvContext->new(EvenStrategy->new());
return $context->getBlock($global->blocks);
}
sub ejectHandler
{
my $context = AdvContext->new(EjectStrategy->new());
return $context->getBlock($global->blocks);
}
Следует отметить, ввиду специфики языка программирования Perl, класс AbstractStrategy является бесполезным классом. Его описание и использование можно опустить.
Когда следует использовать паттерн стратегия:
- имеется много родственных классов, отличающихся только поведением
- необходимо иметь несколько разных вариантов алгоритмав алгоритме содержатся данные, о которых клиент не должен знать паттерн стратегия позволяет не раскрывать сложные, специфичные для алгоритма структуры данных
- в классе определено много поведений, что представлено разветвленными условными операторами
На этой ноте хочется закончить.
В следующий раз рассмотрим паттерн Template Method.
* Так как основная моя деятельность связана с web-разработкой, примеры я буду брать из этой сферы.
6F8BE4F65AE4EF89E47FDFD31E564CB3