2009-08-15 16 views
5

Ho bisogno di scrivere un'app relativa allo storage in Perl. L'app deve caricare file dal computer locale su altri nodi di archiviazione. Attualmente, il metodo di caricamento è FTP, ma in futuro potrebbe essere bittorrent o qualche metodo sconosciuto di trasferimento di file super.Come si implementano le tabelle di invio in Perl?

Per ogni file che deve essere caricato, esiste un file di configurazione che definisce il nome del file, il nodo di archiviazione su cui verrà caricato il file e quale metodo di trasferimento dovrebbe essere utilizzato durante il caricamento.

Naturalmente, posso utilizzare il seguente metodo per risolvere il mio problema:

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

Ma anche con la mia conoscenza di base OO imparato a scuola, mi sento ancora che questo non è un buon progetto. (Il titolo della domanda potrebbe essere un po 'fuorviante.Se pensi che il mio problema possa essere risolto con grazia con una soluzione non OO, per me va bene. In realtà sarà migliore, dato che ho una conoscenza OO limitata.)

Quindi potresti darmi qualche consiglio in generale? Naturalmente, se fornisci anche del codice di esempio, questo sarà di grande aiuto.

risposta

13

In primo luogo, il test di uguaglianza delle stringhe in Perl è eq, non ==.

Se si dispone di metodi per fare il lavoro, dire il nome bit e ftp,

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

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

Vorrei raccomandare di aggiungere un po 'più di descrizione di cosa sta succedendo qui per ogni evenienza, ma comunque una buona risposta. –

+0

Nessuna necessità definita perché nessuno dei falsi valori è un coder valido. Inoltre, dovresti emettere un avviso se non è possibile trovare il metodo nella tabella di ricerca. Un'alternativa è mettere tutti i metodi in una classe e usare 'can'. –

+0

@Sinan Ünür- Che dire se $ trans_type eq "fronobulax?" In altre parole, un tipo che non si aspettava o non aveva previsto? – xcramps

1

OO sarebbe un errore. La mia soluzione sarebbe probabilmente simile a questa:

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

Credo che sia necessario un '' 'prima di' & 'sulle subroutine nell'hash, altrimenti penso che Perl assegnerà il valore restituito da' & ftp_transfer' a '$ transfer_sub {ftp}' piuttosto che un riferimento alla subroutine. –

+2

@Chris: \ & subname restituisce un riferimento al subname. Vedere perlref, "Creazione di riferimenti" – derobert

+1

È molto raramente eccessivo avere un OO. E questo esempio sembra essere risolto OO-saggio. – innaM

8

è possibile utilizzare un hash per questo ...

  1. Avere ogni metodo di trasferimento in sé registrati nel hash. Puoi fare questo OO (chiamando un metodo su qualche metodo di trasferimento di fabbrica) o proceduralmente (basta rendere l'hash una variabile del pacchetto, o potresti anche metterlo nel pacchetto principale se non vuoi modularizzare).

    package MyApp::Transfer::FTP; 
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp; 
    sub do_ftp { ... } 
    1; 
    
  2. Ogni metodo di trasferimento utilizza un'API coerente. Forse è solo una funzione, o potrebbe essere un'interfaccia dell'oggetto.

  3. Chiamare il trasferimento tramite l'hash.

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

BTW: Il metodo registro OO sarebbe simile a questa:

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; 

(Niente di tutto questo codice viene testato; ti prego di perdonare e virgola mancanti)

+0

un hash ha ancora il problema che si stanno elencando i possibili agenti di trasferimento. Non vi è alcun motivo per codificare in modo rigido questa lista. Basta creare TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent, ecc. Una classe factory può quindi essere responsabile dell'istanziazione della classe corretta. –

+2

@Chas. Owens: dove sto hardcoding la lista? Ogni implementazione del metodo è responsabile della registrazione stessa. È abbastanza facile avere un file di configurazione per specificare quali moduli di trasferimento caricare (se si desidera quel livello di personalizzazione, ad es., Magari si desidera disattivare un modulo molto dipendente dalla dipendenza) o caricare tutti i file .pm in una determinata directory (se vuoi quel livello di magia) – derobert

+1

@derobert Come si fanno correre le singole classi? Se ho un programma che deve essere trasferito su più tipi di server, devo specificare ciascun tipo come istruzione separata "use" nel mio programma? Le classi non possono registrarsi fino a quando non vengono utilizzate. Ciò significa che da qualche parte stai codificando le classi che un dato programma può usare (come il file di configurazione che hai indicato). Richiedendo una classe solo quando è richiesta, non è necessario quel tipo di hardcoding. –

6

La corretta progettazione qui è una fabbrica. Dai un'occhiata a come lo gestisce lo DBI. Si finirà con una classe TransferAgent che crea un'istanza di un qualsiasi numero di classi TransferAgent::*. Ovviamente vorrai più controllo degli errori di quanto non fornisca l'implementazione qui sotto. Utilizzare una fabbrica come questa significa che è possibile aggiungere nuovi tipi di agenti di trasferimento senza dover aggiungere o modificare alcun codice.

TransferAgent.pm - la classe di fabbrica:

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 - contiene la funzionalità di base di una classe 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 - implementa un client (finto) 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 - come utilizzare 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

Non penso che tu voglia usare la riga "use base" TransferAgent "nella classe FTP. Soprattutto perché il tuo metodo factory connect non funzionerà in una classe derivata (otterrà il valore errato della classe, o peggio ancora un'istanza). Forse intendevi usare "__PACKAGE__" invece nelle tue linee 'require' e' new'? – derobert

+0

Per questo è anche possibile utilizzare Class :: Factory dal CPAN. È un modulo piuttosto piccolo, ma molto facile da implementare e utilizzare. –

+0

@derobert Sì, era tardi e non avevo ancora dormito. Il modello dovrebbe avere una classe separata per ottenere la funzionalità di base (che è ciò che intendevo che TransferAgent fosse oltre a essere la fabbrica). Ho corretto il codice e l'ho arricchito un po 'ora che sono sveglio. –

1

Hai detto inizialmente che utilizzerà FTP e passerà ad altri metodi di trasferimento in seguito. Non diventerei "elegante" finché non avrai effettivamente bisogno di aggiungere la seconda o terza tecnologia. Questo secondo metodo di trasferimento potrebbe non essere mai richiesto. :-)

Se vuoi farlo come un "progetto scientifico", allora fantastico.

Sono stanco di vedere i modelli di progettazione OO che complicano le soluzioni ai problemi che non arrivano mai.

Avvolgere il primo metodo di trasferimento in un metodo uploadFile. Aggiungi un if se non altro per il secondo metodo. Ottieni elegante e refactoring sul terzo metodo. A quel punto avrai abbastanza esempi che la tua soluzione sarà probabilmente abbastanza generica.

Naturalmente, il mio punto principale è che il secondo e il terzo metodo potrebbero non essere mai richiesti.

+3

Il problema con il metodo I-will-make-it-nice-last è che nel momento in cui è necessario renderlo bello ci sono un sacco di programmi esistenti che usano l'interfaccia non così bella. Certo, devi sempre bilanciare i bisogni futuri con la semplice necessità di farlo. In questo caso, il modello di progettazione di fabbrica è ben compreso ed è abbastanza semplice da implementare e perderai pochissimo tempo fornendo una bella interfaccia per il futuro. –

3

Ho diversi esempi in Mastering Perl nelle sezioni su subroutine dinamiche.

Problemi correlati