假如有一个需要读取的配置文件,格式如下:
CORES 12
LOGFILE /var/log/lihui.log
WWWDIR /home/wwwroot
…… ……
读取了这个配置文件,然后需要做一些行动,比如对CORES,某个系统要运行在12个CPU的核上;对于LOGFILE,所有的log日志信息全部要写在特定log文件lihui.log而不是系统的message里;对于WWWDIR,所有的页面文件,比如一堆php,js全部要安装到指定的目录当中,假如这个目录不存在必须得创建下
看到这些需求,可能立马一个巨大的if-else就来了:
sub read_config {
my ($config_file) = @_;
open my($pf), $config_file or return;
while(<$pf>){
chomp;
my ($directive, $rest) = split /\s+/, $_, 2;
if ($directive eq ‘CORES’) {
$cores = $rest;
} elsif ($directive eq ‘LOGFILE’){
open STDERR, ‘>>’, $rest
or die “Couldn’t open log file : $!; aborting”;
} elif ($directive eq ‘WWWDIR’){
chdir($rest) or die “Couldn’t chdir to $rest: $!; aborting”;
} else ………………….
}
}
函数一共有两个过程,第一步打开配置文件,每次从中读取一行,然后将每行分割成$directive和$rest,$rest就包括了指示的参数,比如文件目录,日志文件名等;第二部分就是通过if-else来一一检查$directive,来分别进行操作
如果让我来做,肯定也是这种做法,简洁直观;但是假如配置文件内容很多,这个函数就会变得十分庞大,因为有太多的if-else选项,假如有人想增加一个指示,分支结构就要添加一项,而且分支之间相互没有什么要做,这样的函数就违背了一条重要法则:相关的东西应该放在一起,而不相关的东西应该分开
因此可以有下面函数结构:读取和解析文件的部分应该与配置的指示呗识别后的执行动作给分开,而且实现各种不相关的指示的代码不应该一起挤进单个函数
sub read_config {
my ($config_file, $action) = @_;
open my($pf), $config_file or return;
while(<$pf>){
chomp;
my ($directive, $rest) = split /\s+/, $_, 2;
if (exists $action->{$directive}){
$action->{$directive}->{$rest};
} else {
die “Unrecognized directive $directive on line $.; aborting”;
}
}
}
和前面一样地打开,读取和解析配置文件,但不依赖巨大的if-else分支,而且还接受了一个额外的参数$action,它是一个行动表,read_config()每读取一个配置的指示,就将执行行动之一,这个表也就是分配表dispatch table,因为它包含了read_config()读文件时将要把控制分配到的函数,变量$rest意义和之前一样,如今作为一个参数传递给合适的行为函数
分配表可如下:
my $dispatch_table = {
CORES => \&set_cores,
LOGFILE => \&open_logfile,
WWWDIR => \&change_dir,
…… => ……,
}
它是一个散列,键是指示的名称,值是行为,指向当识别出了合适的指示名的时候调用的子例程;而这些行为函数仅仅接受变量$rest作为参数,比如如下:
sub set_cores {
$cores = shift;
}
sub open_logfile {
open STDERR, “>>”, $_[0]
or die “Couldn’t open log file $_[0]: $!; aborting”;
}
sub change_dir {
my ($dir) = @_;
chdir($dir)
or die “Couldn’t chdir to $dir: $! aborting”;
}
假如行为函数都很小,也可以将行为函数的内容直接给放到分配表的散列的值内容当中,但是感觉可读性不太好,也比较难看
通过转变撑一个分配表,消除了巨大的if-else树