2009-08-15 1 views
5

Мне нужно написать приложение, связанное с хранилищем, в Perl. Приложение должно загружать файлы с локальной машины на некоторые другие узлы хранения. В настоящее время метод загрузки - FTP, но в будущем он может быть bittorrent или неизвестным методом пересылки файлов.Как реализовать таблицы отправки в Perl?

Для каждого файла, который необходимо загрузить, есть файл конфигурации, который определяет имя файла, узел хранения, к которому будет загружен файл, и какой метод переноса должен использоваться во время загрузки.

Конечно, я могу использовать следующий метод, чтобы решить мою проблему:

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

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

Могли бы вы, ребята, дать мне совет вообще? Конечно, если вы предоставите некоторый пример кода, это будет большой помощью.

ответ

13

Во-первых, тестирование равенства строк в Perl равно eq, а не ==.

Если у вас есть методы, чтобы сделать работу, скажем, с именем бит и FTP,

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

my $proc = $proc{$trans_type}; 
$proc->() if defined $proc; 
+0

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

+0

Нет необходимости в определении, потому что ни одно из значений false не является допустимым кодом. Кроме того, вы должны выдать предупреждение, если метод не найден в таблице поиска. Альтернативой является использование всех методов в классе и использование 'can'. –

+0

@Sinan Ünür- А если $ trans_type eq "fronobulax?" Другими словами, типа, которого он не ожидал или не ожидал? – xcramps

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}}->(...); 
} 
+0

Я считаю, что вам нужно '' 'перед вашими' & 'в ваших подпрограммах в хеше, иначе я думаю, что Perl назначит значение, возвращаемое' & ftp_transfer', '$ transfer_sub {ftp}', а не ссылку на подпрограмму. –

+2

@Chris: \ & subname возвращает ссылку на имя. См. Perlref, «Making References» – derobert

+1

Очень редко излишнее количество ОО. И этот пример позволяет решать OO-мудрый. – innaM

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; 

(Ни один из этого кода не проверяются, пожалуйста, простите недостающую запятую)

+0

hash все еще имеет проблему, что вы перечисляете возможные агенты передачи. Нет причин для жесткого кодирования этого списка. Просто создайте TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent и т. Д. Тогда заводский класс может отвечать за создание экземпляра правильного класса. –

+2

@ Час. Оуэнс: Где я жестко кодирую список? Каждая реализация метода отвечает за регистрацию. Его довольно простой вариант с конфигурационным файлом указывает, какие загрузочные модули загружать (если вы хотите, чтобы этот уровень настройки, например, может быть, вы хотите отключить очень тяжелый модуль) или загружать все .pm-файлы в данный каталог (если вы хотите этот уровень магии) – derobert

+1

@derobert Как отдельные классы запускаются? Если у меня есть программа, которая должна переходить на несколько типов серверов, нужно ли указывать каждый тип как отдельный оператор 'use' в моей программе? Занятия не могут регистрироваться до тех пор, пока они не будут использованы. Это означает, что где-то вы жестко кодируете классы, которые может использовать данная программа (например, указанный файл конфигурации). Требуя класс только тогда, когда его просят, вам не нужен такой тип жесткого кодирования. –

6

Правильный дизайн здесь является фабрикой. Посмотрите, как это делает DBI. Вы закончите с классом TransferAgent, который создаст одно из любого числа классов TransferAgent::*. Очевидно, вам понадобится дополнительная проверка ошибок, чем приведенная ниже реализация. Использование такой фабрики означает, что вы можете добавлять новые типы агентов передачи без необходимости добавлять или изменять какой-либо код.

TransferAgent.Вторая половина дня - завод класс:

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; 
+0

Я не думаю, что вы хотите, чтобы эта строка 'use base 'TransferAgent' 'в классе FTP. Тем более, что ваш метод подключения к фабрике не будет работать в производном классе (получит неправильное значение класса или, что еще хуже, экземпляр). Возможно, вы хотели использовать '__PACKAGE__' вместо своих строк' require' и 'new'? – derobert

+0

Для этого также можно использовать Class :: Factory из CPAN. Это довольно маленький модуль, но очень простой в использовании и использовании. –

+0

@derobert Да, было уже поздно, и я еще не спал. У шаблона должен быть отдельный класс, чтобы получить базовую функциональность (это то, что я собирался для TransferAgent, чтобы быть в дополнение к тому, чтобы быть фабрикой). Я скорректировал код и немного изменил его, когда я бодрствую. –

1

Вы сказали, что сначала он будет использовать FTP и перейти к другим методам передачи позже. Я бы не стал «элегантным», пока вам не понадобится добавить вторую или третью технологию. Этот второй способ передачи никогда не понадобится. :-)

Если вы хотите сделать это как «научный проект», то здорово.

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

Оберните первый способ передачи в методе uploadFile. Добавьте a, если then else для второго метода. Получите элегантный и рефакторинг по третьему методу. К тому времени у вас будет достаточно примеров, что ваше решение, вероятно, будет довольно общим.

Конечно, мое главное, что второй и третий методы никогда не потребуются.

+3

Проблема с методом I-will-make-it-nice-last заключается в том, что к тому моменту, когда вам нужно сделать это хорошо, есть куча существующих программ, которые используют не очень приятный интерфейс. Конечно, вы должны всегда балансировать будущие потребности против простой необходимости сделать это. В этом случае шаблон проектирования завода хорошо понимается и его довольно просто реализовать, и вы потеряете очень мало времени, предоставляя приятный интерфейс для будущего. –

3

У меня есть несколько примеров в Mastering Perl в разделах динамических подпрограмм.

Смежные вопросы