6

我需要在 Perl 中编写一个与存储相关的应用程序。该应用程序需要将文件从本地机器上传到其他一些存储节点。目前上传方式是FTP,未来可能是bittorrent或者一些未知的超级文件传输方式。

对于每个需要上传的文件,都有一个配置文件,其中定义了文件名、文件上传到的存储节点以及上传时应该使用什么传输方式。

当然,我可以使用以下方法来解决我的问题:

{
  if ( $trans_type == "ftp" ) { ###FTP the FILE}
  if ( $trans_type == "bit" ) { ###BIT the FILE}
  ### etc ###
}

但是即使我在学校学到了基本的OO知识,我仍然觉得这不是一个好的设计。(题主可能有点误导,如果你觉得我的问题可以用非OO的解决方案优雅的解决,对我来说还可以。其实会更好,因为我的OO知识有限。)

所以大家能给我一些一般的建议吗?当然,如果你也提供一些示例代码,这将是一个很大的帮助。

4

7 回答 7

13

首先,Perl 中的字符串相等性测试是eq,而不是==

如果你有方法来完成这项工作,比如命名位和 ftp,

my %proc = (
    bit => \&bit,
    ftp => \&ftp,
);

my $proc = $proc{$trans_type};
$proc->() if defined $proc;
于 2009-08-15T08:42:53.247 回答
8

您可以为此使用哈希...

  1. 让每个传输方法在哈希中注册自己。您可以执行此 OO(通过在某些传输方法工厂上调用方法)或程序上(只需将散列设置为包变量,或者如果您不想模块化,甚至可以将其放在主包中)。

    package MyApp::Transfer::FTP;
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp;
    sub do_ftp { ... }
    1;
    
  2. 每种传输方法都使用一致的 API。也许它只是一个函数,或者它可能是一个对象接口。

  3. 通过哈希调用传输。

    sub do_transfer {
        # ...
        my $sub = $MyApp::TransferManager::METHODS{$method}
            or croak "Unknown transfer method $method";
        $sub->($arg1, $arg2, ...);
        # ...
    }
    

顺便说一句: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;

(此代码均未经过测试;请原谅缺少分号)

于 2009-08-15T08:44:35.507 回答
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;
于 2009-08-15T08:55:22.943 回答
3

在有关动态子例程的部分中,我有几个掌握 Perl的示例。

于 2009-08-15T17:07:44.437 回答
2

请参阅实现调度表MJD 在 HOP 中对调度表的处理

于 2009-08-15T13:28:57.270 回答
1

您最初说它将使用 FTP 并稍后转移到其他传输方式。在您真正需要添加第二或第三技术之前,我不会变得“优雅”。可能永远不需要第二种转移方法。:-)

如果你想把它作为一个“科学项目”来做,那就太好了。

我厌倦了看到 OO 设计模式使永远不会出现的问题的解决方案变得复杂。

将第一个传输方法包装在一个 uploadFile 方法中。为第二种方法添加 if then else。在第三种方法上获得优雅和重构。到那时,您将拥有足够多的示例,您的解决方案可能会非常通用。

当然,我的主要观点是,可能永远不需要第二种和第三种方法。

于 2009-08-15T12:16:25.403 回答
1

OO 将是矫枉过正。我的解决方案可能看起来像这样:

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}}->(...);
}
于 2009-08-15T08:44:26.053 回答