четверг, 23 апреля 2009 г.

Паттерн Strategy

Ох уж эти паттерны. Но весчь хорошая и нужная.

Если разобраться, то паттерны мы используем всегда, просто не знаем о том, что это уже давно описанный шаблон проектирования. Мы просто снова и снова изобретаем колесо. Зачем? Почему? А потому что не читаем книг.

Стратегия.
Что такое стратегия? Не буду перепечатывать то, что можно нагуглить, скажу просто. 
Стратегия - не детализированный способ достижения поставленной цели.

Как мы обычно делаем?
У нас имеется набор алгоритмов, с помощью которых мы решаем поставленную задачу. Когда нам необходимо, мы берем нужный нам алгоритм и выполняем поставленную задачу.

Как это обычно выглядит?
Предположим нам необходимо показать пользователю* блок с контекстной рекламой. При этом необходимо ротировать и таргетировать блоки по тем данным что мы знаем о пользователе, к примеру: регион, пол, возраст и т.д.
Мы реализовали несколько алгоритмов ротации: равномерная ротация, вытесняющая,  волновая и т.д.
Мы можем выбирать любой из этих алгоритмов, который мы хотим использовать в данный момент


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;

#нахождение нужных блоков
}


Что нам предлагает сделать паттерн Стратегия.
  1. Создать интерфейс (абстрактный класс) Strategy
  2. Создать конкретные стратигии (алгоритмы) 
  3. Создать контекст
  4. Все это объединить


Давайте это и сделаем:

# базовый класс стратегии
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

Комментариев нет:

Отправить комментарий