表示Perl中允许的状态转换图

时间:2010-10-27 13:07:26

标签: perl data-structures graph

我们的应用程序中存在类似状态更改检查逻辑的内容。

目前检查由丑陋的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 个答案:

答案 0 :(得分:2)

如果条件非常普遍(例如,几乎每个允许的转换都有它们)那么你的后一个结构就完全没问了,除了用“()”而不是“{}”表示hashref的语法错误。

如果条件很少,我建议使用#1,使用类似于#2的可选结构进行扩充。

请注意,检查代码的可读性是恕我直言,虽然很多,但不是非常惯用。

OTOH,矩阵的可维护性很高 - 你从#1获得简洁但可读的语法,其中不需要任何条件,并且条件的语法清晰但更长,对于像#2这样的许多设置的许多条件都足够灵活。 / p>
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 :(得分:1)

虽然我在很大程度上同意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()创建一个新记录(如果不存在),并更新该记录(如果它存在)。事实证明这比使用“升级”方法或“获取此项目然后修改它”技术更简单。

在内部,它可以做到这一点,或类似的东西:

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 );

希望这有用=)。

答案 2 :(得分:1)

第二种形式没有错。您无法解决必须在某处对状态机进行编码的事实。实际上,我认为将整个机器编码在一个这样的地方更容易理解那些抽象层太多的东西,你需要在不同的地方查看以了解机器的流程。