2009-08-15 11 views
5

Muszę napisać aplikację związaną z pamięcią masową w Perlu. Aplikacja musi przesłać pliki z lokalnego komputera do innych węzłów magazynowania. Obecnie metoda przesyłania to FTP, ale w przyszłości może to być bittorrent lub jakaś nieznana metoda przesyłania plików.Jak wdrożyć tabele wysyłki w Perlu?

Dla każdego pliku, który należy przesłać, znajduje się plik konfiguracyjny, który definiuje nazwę pliku, węzeł magazynowania, do którego plik będzie przesyłany i jaką metodę przesyłania należy użyć podczas przesyłania.

Oczywiście, można korzystać z następujących metod, aby rozwiązać mój problem:

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

Ale nawet moja podstawowa wiedza OO nauczyłem się w szkole, nadal czuję, że nie jest to dobry projekt. (Tytuł pytania może być trochę mylący, jeśli uważasz, że mój problem może zostać rozwiązany w sposób wdzięczny za pomocą rozwiązania innego niż OO, jest to całkiem w porządku dla mnie.Tak właściwie będzie lepiej, ponieważ mam ograniczoną wiedzę OO.)

Czy moglibyście mi doradzić w ogóle? Oczywiście, jeśli podasz także przykładowy kod, będzie to bardzo pomocne.

Odpowiedz

13

pierwsze, badanie równości ciąg w Perl jest eq, nie ==.

Jeśli masz metody do pracy, powiedzmy nazwany nieco i ftp,

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

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

Polecam dodać trochę więcej opisu tego, co się tutaj dzieje na wszelki wypadek, ale wciąż dobrą odpowiedź. –

+0

Nie ma potrzeby zdefiniowania, ponieważ żadna z fałszywych wartości nie jest poprawnym coderef. Powinieneś również wysłać ostrzeżenie, jeśli nie można znaleźć metody w tabeli odnośników. Alternatywą jest umieszczenie wszystkich metod w klasie i użycie 'can'. –

+0

@Sinan Ünür- A co jeśli $ trans_type eq "fronobulax?" Innymi słowy, typ, którego się nie spodziewał lub czego się nie spodziewał? – xcramps

1

OO byłoby przesadą. Moje rozwiązanie będzie prawdopodobnie wyglądać tak:

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

Wierzę, że potrzebujesz '' 'before your' & 'na swoich podprogramach w haszcie, w przeciwnym razie Perl przypisze wartość zwróconą przez' & ftp_transfer' do '$ transfer_sub {ftp}' zamiast odniesienia do podprogramu. –

+2

@Chris: \ & subname zwraca odwołanie do podmenu. Zobacz perlref, "Dokonywanie referencji" – derobert

+1

Bardzo rzadko przesadą jest posiadanie OO. I ten przykład screems zostać rozwiązany OO-mądry. – innaM

8

Można użyć skrótu dla tego ...

  1. Czy każda metoda transferu zarejestrować się w hash. Możesz to zrobić za pomocą OO (przez wywołanie metody w pewnej fabryce metod transferu) lub proceduralnie (po prostu zmień hasz zmienną pakietu, lub możesz nawet umieścić ją w pakiecie głównym, jeśli nie chcesz modularyzować).

    package MyApp::Transfer::FTP; 
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp; 
    sub do_ftp { ... } 
    1; 
    
  2. Każda metoda przesyłania wykorzystuje spójny interfejs API. Może to po prostu funkcja lub interfejs obiektowy.

  3. połączeń przeniesienie przez hash.

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

BTW: Metoda rejestr OO będzie wyglądać mniej więcej tak:

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; 

(Żaden z tym kodem jest testowany; wybaczcie brakujących średników)

+0

hash nadal ma problem z listą możliwych agentów przesyłania. Nie ma powodu, aby twardo kodować tę listę. Po prostu utwórz TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent, itd. Klasa fabryka może być wtedy odpowiedzialna za utworzenie odpowiedniej klasy. –

+2

@Chas. Owens: Gdzie jestem hardcoding na liście? Każda implementacja metody jest odpowiedzialna za rejestrację samej siebie. Łatwo jest mieć plik konfiguracyjny określający, które moduły transferowe mają zostać załadowane (jeśli chcesz taki poziom dostosowania, na przykład, być może chcesz wyłączyć moduł bardzo zależny od zależności) lub załadować wszystkie pliki .pm w danym katalogu (jeśli chcesz tego poziomu magii) – derobert

+1

@derobert Jak poszczególne klasy się uruchamiają? Jeśli mam program, który musi zostać przeniesiony do wielu typów serwerów, czy muszę określić każdy typ jako oddzielną instrukcję "use" w moim programie? Klasy nie mogą się zarejestrować, dopóki nie zostaną użyte. Oznacza to, że na dysku twardym kodowane są klasy, z których może korzystać dany program (na przykład wskazany plik konfiguracyjny). Wymagając klasy tylko wtedy, gdy zostaniesz o to poproszony, nie potrzebujesz tego typu hardcoding. –

6

Prawidłowa konstrukcja tutaj jest fabryką. Spójrz, jak obsługuje to DBI. Kończy się klasa TransferAgent, która tworzy instancję jednej z wielu klas TransferAgent::*. Oczywiście będziesz chciał więcej sprawdzania błędów niż zapewnia to poniższa implementacja. Korzystanie z takiej fabryki oznacza, że ​​możesz dodawać nowe typy agentów transferu bez konieczności dodawania ani modyfikowania żadnego kodu.

TransferAgent.pm - klasa fabryczne:

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 - zawiera funkcjonalność podstawową klasą 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 - implementuje klienta (makiety) 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 - jak korzystać z 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

Nie sądzę, że chcesz, aby ta "używana baza" była "TransferAgent" w klasie FTP. Zwłaszcza, że ​​twoja metoda podłączenia do fabryki nie działa w klasie pochodnej (otrzyma błędną wartość klasy lub, co gorsza, instancji zamiast). Może zamiast tego użyłeś '__PACKAGE__'' w liniach' require' i 'new'? – derobert

+0

Możesz również użyć Class :: Factory z CPAN. To całkiem mały moduł, ale bardzo łatwy w implementacji i użyciu. –

+0

@derobert Tak, było późno, a ja jeszcze nie spałem. Wzorzec powinien mieć oddzielną klasę, aby uzyskać podstawową funkcjonalność (co zamierzałem, aby TransferAgent był poza tym, że jest fabrycznie). Poprawiłem kod i odrobinę go ulepszyłem teraz, gdy się obudziłem. –

1

Powiedziałeś, że początkowo użyje FTP i przejdzie do innych metod przesyłania później. Nie stałbym się "elegancki", dopóki nie trzeba dodawać drugiej lub trzeciej technologii. Ta druga metoda transferu może nigdy nie być wymagana. :-)

Jeśli chcesz zrobić to jako "projekt naukowy", to świetnie.

Jestem zmęczony widokiem wzorów OO, które komplikują rozwiązania problemów, które nigdy nie nadchodzą.

Owiń pierwszą metodę przesyłania w metodzie uploadFile. Dodaj, jeśli nie, drugą metodę. Zdobądź elegancki i refaktor na trzeciej metodzie. Do tego czasu będziesz mieć wystarczająco dużo przykładów, że twoje rozwiązanie będzie prawdopodobnie dość ogólne.

Oczywiście, moim głównym punktem jest to, że druga i trzecia metoda może nigdy nie być wymagana.

+3

Problem z metodą I-will-make-it-nice-last polega na tym, że do czasu, gdy trzeba ją sprawić, jest kilka istniejących programów, które używają niezbyt fajnego interfejsu. Oczywiście musisz zawsze równoważyć przyszłe potrzeby z prostą potrzebą zrobienia tego. W takim przypadku fabryczny wzorzec projektowy jest dobrze zrozumiany i jest dość prosty w implementacji, a stracisz bardzo mało czasu, zapewniając przyjemny interfejs na przyszłość. –

3

Mam kilka przykładów w Mastering Perl w sekcjach dotyczących podprogramów dynamicznych.