2009-08-15 8 views
5

Ich muss eine Speicher verwandte App in Perl schreiben. Die App muss Dateien von der lokalen Maschine auf einige andere Speicherknoten hochladen. Momentan ist die Upload-Methode FTP, aber in Zukunft könnte es sich um eine Bittorrent-Methode oder eine unbekannte Super-File-Transfer-Methode handeln.Wie implementiere ich Versandtabellen in Perl?

Für jede Datei, die hochgeladen werden muss, gibt es eine Konfigurationsdatei, die den Dateinamen, den Speicherknoten, in den die Datei hochgeladen wird und welche Übertragungsmethode beim Hochladen verwendet werden soll.

Natürlich kann ich die folgende Methode verwenden, um mein Problem zu lösen:

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

Aber auch mit meiner grundlegenden OO Kenntnisse in der Schule gelernt haben, fühle ich mich immer noch, dass dies kein gutes Design ist. (Der Titel der Frage könnte ein wenig irreführend sein. Wenn Sie glauben, dass mein Problem mit einer Nicht-OO-Lösung gelöst werden kann, ist es für mich ganz in Ordnung. Tatsächlich ist es besser, da ich OO-Kenntnisse eingeschränkt habe.)

Könnten Sie mir generell einen Rat geben? Natürlich, wenn Sie auch einen Beispielcode zur Verfügung stellen, wird dies eine große Hilfe sein.

Antwort

13

Zuerst ist String Gleichheitsprüfung in Perl , nicht ==.

Wenn Sie Methoden haben die Arbeit zu tun, sagen Bit und ftp genannt,

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

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

Ich würde empfehlen, ein bisschen mehr Beschreibung von dem, was hier vor sich geht, nur für den Fall, aber immer noch gute Antwort. –

+0

Keine Notwendigkeit für definiert, da keiner der falschen Werte eine gültige Coderef ist. Außerdem sollten Sie eine Warnung ausgeben, wenn die Methode nicht in der Nachschlagetabelle gefunden werden kann. Eine Alternative besteht darin, alle Methoden in eine Klasse zu stellen und 'can' zu verwenden. –

+0

@Sinan Ünür- Was ist, wenn $ trans_type eq "fronobulax?" Mit anderen Worten, ein Typ, den er nicht erwartet hatte oder nicht erwartet hatte? – xcramps

1

OO wäre zu viel des Guten. Meine Lösung wäre wahrscheinlich so etwas wie folgt aussehen:

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

Ich glaube, Sie brauchen ein '' 'vor Ihrem' & 'auf Ihren Unterprogrammen im Hash, sonst denke ich, dass Perl den von' & ftp_transfer' zurückgegebenen Wert dem '$ transfer_sub {ftp}' und nicht einem Verweis auf das Unterprogramm zuweist. –

+2

@Chris: \ & subname gibt einen Verweis auf den Unternamen zurück. Siehe perlref, "Making References" – derobert

+1

Es ist sehr selten zu viel OO. Und dieses Beispiel scheint OO-weise gelöst zu sein. – innaM

8

Sie einen Hash für diesen Einsatz ...

  1. Lassen Sie jedes Transferverfahren selbst in der Hash registrieren. Sie können diese OO (durch Aufruf einer Methode in einer Transfermethodenfabrik) oder prozedural ausführen (machen Sie einfach den Hash zu einer Paketvariable, oder Sie könnten ihn sogar in das Hauptpaket einfügen, wenn Sie nicht modularisieren wollen).

    package MyApp::Transfer::FTP; 
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp; 
    sub do_ftp { ... } 
    1; 
    
  2. Jede Übertragungsmethode verwendet eine konsistente API. Vielleicht ist es nur eine Funktion, oder es könnte eine Objektschnittstelle sein.

  3. Die Übertragung über den Hash aufrufen.

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

BTW: Die OO-Register-Methode wie folgt aussehen würde:

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; 

(Keine dieser Code wird getestet, bitte vergib fehlende Semikolons) hier

+0

Ein Hash hat immer noch das Problem, dass Sie die möglichen Übertragungsagenten auflisten. Es gibt keinen Grund, diese Liste hart zu codieren. Erstellen Sie einfach TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent usw. Eine Factory-Klasse kann dann für die Instanziierung der richtigen Klasse verantwortlich sein. –

+2

@Chas. Owens: Wo schreibe ich die Liste fest? Jede Methodenimplementierung ist für die Registrierung selbst verantwortlich. Es ist ziemlich einfach, eine Konfigurationsdatei zu haben, die angibt, welche Transfermodule geladen werden sollen (wenn Sie diese Stufe der Anpassung wünschen, zB möchten Sie vielleicht ein sehr abhängigkeitsschwaches Modul ausschalten) oder alle .pm Dateien in ein bestimmtes Verzeichnis laden (wenn Du willst dieses Level an Magie) – derobert

+1

@derobert Wie werden die einzelnen Klassen selbst ausgeführt? Wenn ich ein Programm habe, das auf mehrere Servertypen übertragen werden muss, muss ich dann jeden Typ als separate 'use'-Anweisung in meinem Programm angeben? Klassen können sich erst registrieren, wenn sie verwendet werden. Das bedeutet, dass Sie irgendwo fest codieren, welche Klassen ein bestimmtes Programm verwenden kann (wie die Konfigurationsdatei, auf die Sie hingewiesen haben). Wenn Sie eine Klasse nur dann benötigen, wenn sie angefordert wird, brauchen Sie diese Art von Hardcoding nicht. –

6

Die richtige Design ist eine Fabrik. Werfen Sie einen Blick darauf, wie die DBI dies handhabt. Sie werden mit einer TransferAgent Klasse enden, die eine beliebige Anzahl von TransferAgent::* Klassen instanziiert. Offensichtlich wollen Sie mehr Fehler überprüfen, als die folgende Implementierung bietet. Wenn Sie eine Factory wie diese verwenden, können Sie neue Arten von Transferagenten hinzufügen, ohne Code hinzufügen oder ändern zu müssen.

TransferAgent.pm - die Factory-Klasse:

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 - enthält die Basisfunktionalität eines TransferAgent::* Klasse:

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 - implementiert eine (mock) FTP-Client:

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 - wie man TransferAgent benutzt:

#!/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

Ich glaube nicht, dass Sie wollen, verwenden Sie die Basis "TransferAgent" Zeile in der FTP-Klasse. Vor allem, da Ihre Factory-Verbindungsmethode in einer abgeleiteten Klasse nicht funktioniert (wird den falschen Wert der Klasse erhalten, oder schlimmer noch eine Instanz). Vielleicht wollten Sie stattdessen "__PACKAGE__" in Ihren "require" und "new" Zeilen verwenden? – derobert

+0

Sie können dafür auch Class :: Factory aus dem CPAN verwenden. Es ist ein ziemlich kleines Modul, aber sehr einfach zu implementieren und zu verwenden. –

+0

@derobert Ja, es war spät und ich hatte noch nicht geschlafen. Das Muster sollte eine separate Klasse haben, um die Basisfunktionalität zu erhalten (was ich für TransferAgent zusätzlich zur Fabrik wollte). Ich habe den Code korrigiert und es jetzt ein wenig ausgebessert, jetzt wo ich wach bin. –

1

Sie haben gesagt, dass es zunächst FTP verwenden und später zu anderen Übertragungsverfahren wechseln wird. Ich würde nicht "elegant" werden, bis Sie tatsächlich die zweite oder dritte Technologie hinzufügen müssen. Dieses zweite Übertragungsverfahren wird möglicherweise niemals benötigt. :-)

Wenn du es als "Wissenschaftsprojekt" machen willst, dann toll.

Ich bin es leid, OO Design-Muster zu sehen, die Lösungen für Probleme komplizieren, die nie ankommen.

Umbrechen Sie die erste Übertragungsmethode in eine uploadFile-Methode. Fügen Sie ein if dann else für die zweite Methode hinzu. Werden Sie elegant und überdenken Sie die dritte Methode. Bis dahin werden Sie genug Beispiele haben, dass Ihre Lösung wahrscheinlich ziemlich generisch sein wird.

Natürlich ist mein Hauptpunkt, dass die zweite und dritte Methode möglicherweise nie benötigt werden.

+3

Das Problem mit der Ich-mache-es-doch-schön-letzt-Methode ist, dass, wenn Sie es schön machen müssen, gibt es eine Reihe von vorhandenen Programmen, die die nicht so nette Schnittstelle verwenden. Natürlich müssen Sie die zukünftigen Bedürfnisse immer gegen die einfache Notwendigkeit ausgleichen. In diesem Fall ist das Fabrikdesignmuster gut verstanden und es ist ziemlich einfach zu implementieren, und Sie werden sehr wenig Zeit verlieren, indem Sie eine schöne Schnittstelle für die Zukunft bereitstellen. –

3

Ich habe mehrere Beispiele in Mastering Perl in den Abschnitten über dynamische Unterprogramme.

Verwandte Themen