我需要在Perl中编写一个与存储相关的应用程序。该应用程序需要将文件从本地计算机上载到其他一些存储节点。目前,上传方法是FTP,但将来它可能是bittorrent或一些未知的超文件传输方法。
对于每个需要上传的文件,都有一个配置文件,用于定义文件名,文件将上传到的存储节点以及上传过程中应使用的传输方法。
当然,我可以使用以下方法来解决我的问题:
{
if ( $trans_type == "ftp" ) { ###FTP the FILE}
if ( $trans_type == "bit" ) { ###BIT the FILE}
### etc ###
}
但即使我在学校学到了基本的OO知识,我仍然觉得这不是一个好的设计。 (问题标题可能有点误导。如果你认为我的问题可以用非OO解决方案优雅地解决,那对我来说就没问题。实际上它会更好,因为我对OO知识有限。)
那么你们一般可以给我一些建议吗?当然,如果你提供一些示例代码,这将是一个很大的帮助。
答案 0 :(得分:13)
首先,Perl中的字符串相等性测试是eq
,而不是==
。
如果你有方法来做这项工作,比如说bit和ftp,
my %proc = (
bit => \&bit,
ftp => \&ftp,
);
my $proc = $proc{$trans_type};
$proc->() if defined $proc;
答案 1 :(得分:8)
你可以使用哈希...
让每个传输方法在哈希中注册自己。您可以执行此OO(通过在某些传输方法工厂上调用方法)或在程序上执行(只需将哈希值设置为包变量,或者如果您不想模块化,甚至可以将其放在主包中)。
package MyApp::Transfer::FTP;
$MyApp::TransferManager::METHODS{ftp} = \&do_ftp;
sub do_ftp { ... }
1;
每种转移方法都使用一致的API。也许它只是一个函数,或者它可以是一个对象接口。
通过哈希调用传输。
sub do_transfer {
# ...
my $sub = $MyApp::TransferManager::METHODS{$method}
or croak "Unknown transfer method $method";
$sub->($arg1, $arg2, ...);
# ...
}
BTW:OO注册方法看起来像这样:
package MyApp::TransferManager;
use Carp;
use strict;
my %registered_method;
sub register {
my ($class, $method, $sub) = @_;
exists $registered_method{$method}
and croak "method $method already registered";
$registered_method{$method} = $sub;
}
# ...
1;
(此代码均未经过测试;请原谅丢失的分号)
答案 2 :(得分:6)
这里的正确设计是工厂。看看DBI
如何处理这个问题。最后,您将使用TransferAgent
类来实例化任意数量的TransferAgent::*
类中的一个。显然,您需要比下面提供的实现更多的错误检查。使用这样的工厂意味着您可以添加新类型的传输代理,而无需添加或修改任何代码。
TransferAgent.pm - 工厂类:
package TransferAgent;
use strict;
use warnings;
sub connect {
my ($class, %args) = @_;
require "$class/$args{type}.pm";
my $ta = "${class}::$args{type}"->new(%args);
return $ta->connect;
}
1;
TransferAgent/Base.pm
- 包含TransferAgent::*
类的基本功能:
package TransferAgent::Base;
use strict;
use warnings;
use Carp;
sub new {
my ($class, %self) = @_;
$self{_files_transferred} = [];
$self{_bytes_transferred} = 0;
return bless \%self, $class;
}
sub files_sent {
return wantarray ? @{$_[0]->{_files_sent}} :
scalar @{$_[0]->{_files_sent}};
}
sub files_received {
return wantarray ? @{$_[0]->{_files_recv}} :
scalar @{$_[0]->{_files_recv}};
}
sub cwd { return $_[0]->{_cwd} }
sub status { return $_[0]->{_connected} }
sub _subname {
return +(split "::", (caller 1)[3])[-1];
}
sub connect { croak _subname, " is not implemented by ", ref $_[0] }
sub disconnect { croak _subname, " is not implemented by ", ref $_[0] }
sub chdir { croak _subname, " is not implemented by ", ref $_[0] }
sub mode { croak _subname, " is not implemented by ", ref $_[0] }
sub put { croak _subname, " is not implemented by ", ref $_[0] }
sub get { croak _subname, " is not implemented by ", ref $_[0] }
sub list { croak _subname, " is not implemented by ", ref $_[0] }
1;
TransferAgent/FTP.pm
- 实现(模拟)FTP客户端:
package TransferAgent::FTP;
use strict;
use warnings;
use Carp;
use base "TransferAgent::Base";
our %modes = map { $_ => 1 } qw/ascii binary ebcdic/;
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_mode} = "ascii";
return $self;
}
sub connect {
my $self = shift;
#pretend to connect
$self->{_connected} = 1;
return $self;
}
sub disconnect {
my $self = shift;
#pretend to disconnect
$self->{_connected} = 0;
return $self;
}
sub chdir {
my $self = shift;
#pretend to chdir
$self->{_cwd} = shift;
return $self;
}
sub mode {
my ($self, $mode) = @_;
if (defined $mode) {
croak "'$mode' is not a valid mode"
unless exists $modes{$mode};
#pretend to change mode
$self->{_mode} = $mode;
return $self;
}
#return current mode
return $self->{_mode};
}
sub put {
my ($self, $file) = @_;
#pretend to put file
push @{$self->{_files_sent}}, $file;
return $self;
}
sub get {
my ($self, $file) = @_;
#pretend to get file
push @{$self->{_files_recv}}, $file;
return $self;
}
sub list {
my $self = shift;
#pretend to list remote files
return qw/foo bar baz quux/;
}
1;
script.pl
- 如何使用TransferAgent:
#!/usr/bin/perl
use strict;
use warnings;
use TransferAgent;
my $ta = TransferAgent->connect(
type => "FTP",
host => "foo",
user => "bar",
password => "baz",
);
print "files to get: ", join(", ", $ta->list), "\n";
for my $file ($ta->list) {
$ta->get($file);
}
print "files gotten: ", join(", ", $ta->files_received), "\n";
$ta->disconnect;
答案 3 :(得分:3)
我在Mastering Perl的动态子程序部分有几个例子。
答案 4 :(得分:2)
答案 5 :(得分:1)
sub ftp_transfer { ... }
sub bit_transfer { ... }
my $transfer_sub = { 'ftp' => \&ftp_transfer, 'bit' => \&bit_transfer, ... };
...
sub upload_file {
my ($file, ...) = @_;
...
$transfer_sub->{$file->{trans_type}}->(...);
}
答案 6 :(得分:1)
你说最初它将使用FTP并稍后转移到其他传输方法。在你真正需要添加第二种或第三种技术之前,我不会“优雅”。可能永远不需要第二种转移方法。 : - )
如果你想把它作为一个“科学项目”那么好。
我厌倦了看到OO设计模式使解决方案变得复杂,而这些问题从未到来。
在uploadFile方法中包装第一个传输方法。为第二种方法添加if then else。在第三种方法上获得优雅和重构。到那时,您将有足够的示例,您的解决方案可能非常通用。
当然,我的主要观点是可能永远不需要第二种和第三种方法。