представлять график разрешенных переходов статуса в Perl - PullRequest
6 голосов
/ 27 октября 2010

В нашем приложении есть что-то вроде логики проверки изменений статуса.

В настоящее время проверка выполняется уродливым оператором if

Я хочу заменить его на матрицу переходов:

my %allowed_status_changes = (
    1 => (2,5),     
    2 => (1,2,3,4,5),
    3 => (4,2),     
    4 => (3,2),     
    5 => (),        
);
my $is_allowed_transition = 
    $submitted_status ~~ $allowed_status_changes {$original_status};

if ($prerequestsites && !$is_allowed_transition) {
    return;
}

могут быть разрешены определенные переходытолько при дополнительном условии, поэтому мне понадобится что-то вроде

2 => ( 
    (target => 1)
    (target => 2, condition => $some_condition)
    (target => (3,4), condition => $other_condition), 
    (target => 5)
),

(на мой взгляд, это слишком долго)

Какую структуру вы бы использовали в этой ситуации, если бы вы сосредоточились на удобочитаемости иремонтопригодность?

Как вы будете анализировать его, чтобы проверить, разрешен ли переход?

Ответы [ 3 ]

2 голосов
/ 27 октября 2010

Если условия очень распространены (например, они есть почти у каждого разрешенного перехода), тогда ваша последняя структура в порядке, за исключением синтаксической ошибки представления хэш-ссылки с "()" вместо "{}".

Если условия встречаются редко, я бы предложил перейти к # 1, дополненному дополнительными конструкциями, похожими на ваши # 2.

Обратите внимание, что читаемость проверки кода ИМХО очень четкая, но объемная и не очень идиоматическая.

OTOH, ремонтопригодность матрицы высока - у вас есть краткий, но читаемый синтаксис из # 1, где условия не требуются, и четкий, но более длинный синтаксис для условий, который достаточно гибок для многих условий для многих настроек, таких как ваш # 2.

my %allowed_status_changes = (
    1 => [2,5],
    2 => [1,5,{targets=>[2], conditions=>[$some_condition]}
             ,{targets=>[3,4], conditions=>[$other_condition, $more_cond]}]
    3 => [4,2],     
    4 => [3,2],
    5 => [],        
);

sub is_allowed_transition {
    my ($submitted_status, $original_status ) = @_;
    foreach my $alowed_status (@$allowed_status_changes{$original_status}) {
        return 1 if !ref $alowed_status && $alowed_status == $submitted_status;
        if (ref $alowed_status) {
            foreach my $target (@$alowed_status{targets}) {
                foreach my $condition (@$alowed_status{conditions}) {
                    return 1 if check_condition($submitted_status
                                              , $original_status, $condition);    
                }
            }
        }
    }
    return 0;
}

if ($prerequestsites
  && !$is_allowed_transition($submitted_status, $original_status )) {
    return;
}
1 голос
/ 28 октября 2010

Во второй форме нет ничего плохого.Вы не сможете обойти тот факт, что вы должны где-то кодировать конечный автомат.На самом деле, я думаю, что если закодировать всю машину в одном месте, гораздо проще понять что-то со слишком большим количеством уровней абстракции, где вам нужно искать в n разных местах, чтобы понять процесс работы машины.

1 голос
/ 27 октября 2010

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

На этом этапе я бы, вероятно, потянулся к объекту и классу, к небольшому синтаксическому сахару.

my $transitions = TransitionGraph->new(); 
$transition->add( 1, { targets => [ 2, 5 ] }); 
$transition->add( 2, { targets => [ 1, 5 ] });
$transition->add( 2, { targets => [ 2 ],    conditions => [ $some_condition ] });
$transition->add( 2, { targets => [ 3, 4 ], conditions => [ $other_condition, $more_cond ]}); 
$transition->add( 3, { targets => [4,2] } );
$transition->add( 4, { targets => [3,2] } );
$transition->add( 5, { targets => [] } );

if( $transition->allowed( 1 , 3 )){ 

}

Реализация класса зависит от пользователя, но я бы использовал Moose.

Основным преимуществом этого является то, что вы инкапсулируете, как работает граф состояний, так что вы можете просто использовать его и беспокоиться о том, как граф работает отдельно от места его использования.

nb.в предложенном выше API add () создает новую запись, если она не существует, и обновляет эту запись, если она существует.Это оказалось проще, чем использование методов «upgrade» или «получить этот элемент, а затем изменить его».

Внутренне, это может сделать это, или что-то в этом роде:

sub add { 
   my ( $self , $input_state, $rules ) = @_;
   my $state;
   if ( $self->has_state( $input_state ) ) { 
       $state = $self->get_state( $input_state );
   } else { 
       $state = TransitionGraphState->new( source_id => $input_state );
       $self->add_state( $input_state, $state );
   }
   my $targets = delete $rules{targets};
   for my $target ( @$targets ) {
      $state->add_target( $target, $rules );
   }
   return $self;
}

sub allowed { 
    my ( $self, $from, $to )  = @_; 

    if ( not $self->has_state( $from ) ){ 
         croak "NO source state $from in transition graph";
    }
    my $state = $self->get_state( $from );
    return $state->allowed_to( $to );
}

Это также имеет классное преимущество: не требуется один конкретный код для работы на подузлах, вы можетесоздавайте отдельные экземпляры с их собственным поведением на тот случай, если вы хотите, чтобы одно состояние источника обрабатывалось по-разному.

 $transition->add_state( 2, $some_other_class_wich_works_like_transitiongraphstate );

Надеюсь, это полезно =).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...