2009-08-15 9 views
5

Necesito escribir una aplicación relacionada con el almacenamiento en Perl. La aplicación necesita cargar archivos desde la máquina local a otros nodos de almacenamiento. Actualmente, el método de carga es FTP, pero en el futuro puede ser bittorrent o algún método desconocido de transferencia de superarchivos.¿Cómo implemento las tablas de envío en Perl?

Para cada archivo que debe cargarse, hay un archivo de configuración que define el nombre del archivo, el nodo de almacenamiento al que se cargará el archivo y el método de transferencia que se debe utilizar durante la carga.

Por supuesto, puede utilizar el siguiente método para resolver mi problema:

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

Pero incluso con mis conocimientos básicos OO aprendido en la escuela, todavía siento que esto no es un buen diseño. (El título de la pregunta podría ser un poco engañoso. Si crees que mi problema puede resolverse correctamente con una solución que no sea de OO, está bien para mí. En realidad, será mejor, ya que tengo conocimiento limitado de OO)

Entonces, ¿podrían darme algunos consejos en general? Por supuesto, si proporciona un código de muestra también, esto será de gran ayuda.

Respuesta

13

En primer lugar, prueba de igualdad de cadenas en Perl es eq, no ==.

Si tiene métodos para hacer el trabajo, digamos llamado bit y ftp,

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

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

Recomendaría añadir un poco más de descripción de lo que está sucediendo aquí por las dudas, pero sigue siendo una buena respuesta. –

+0

No es necesario definirlo porque ninguno de los valores falsos es un valor de referencia válido. Además, debe emitir una advertencia si el método no se puede encontrar en la tabla de búsqueda. Una alternativa es poner todos los métodos en una clase y usar 'can'. –

+0

@Sinan Ünür- ¿Qué pasa si $ trans_type eq "fronobulax?" En otras palabras, ¿un tipo que no esperaba o no había anticipado? – xcramps

1

OO sería una exageración. Mi solución sería probablemente algo parecido a esto:

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

Creo que necesita un '' 'antes de su' & 'en sus subrutinas en el hash, de lo contrario, creo que Perl asignará el valor devuelto por' & ftp_transfer' a '$ transfer_sub {ftp}' en lugar de una referencia a la subrutina. –

+2

@Chris: \ & subname devuelve una referencia a subname. Ver perlref, "Hacer referencias" – derobert

+1

Rara vez es excesivo tener algo de OO. Y este ejemplo parece resolverse en OO. – innaM

8

Se puede utilizar un hash de esta ...

  1. Haga que cada método de transferencia de registrarse en el hash. Puedes hacer esto OO (llamando a un método en alguna fábrica de método de transferencia) o de procedimiento (simplemente haz que el hash sea una variable de paquete, o incluso podrías ponerlo en el paquete principal si no quieres modularizarlo).

    package MyApp::Transfer::FTP; 
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp; 
    sub do_ftp { ... } 
    1; 
    
  2. Cada método de transferencia utiliza una API consistente. Tal vez sea solo una función, o podría ser una interfaz de objeto.

  3. Llame la transferencia a través del hash.

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

Por cierto: El método de registro OO sería algo como esto:

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; 

(Ninguna de este código se prueba; por favor, perdona y comas que faltan)

+0

un hash todavía tiene el problema de que está enumerando los posibles agentes de transferencia. No hay ninguna razón para codificar esta lista. Simplemente cree TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent, etc. Una clase de fábrica puede ser responsable de instanciar la clase correcta. –

+2

@Chas. Owens: ¿Dónde estoy hardcoding la lista? Cada implementación de método es responsable de registrarse. Es bastante fácil tener un archivo de configuración que especifique qué módulos de transferencia cargar (si desea ese nivel de personalización, por ejemplo, tal vez desea apagar un módulo muy dependiente de la carga) o cargar todos los archivos .pm en un directorio determinado (si quieres ese nivel de magia) – derobert

+1

@derobert ¿Cómo se manejan las clases individuales? Si tengo un programa que necesita transferir a múltiples tipos de servidor, ¿tengo que especificar cada tipo como una declaración separada 'use' en mi programa? Las clases no se pueden registrar hasta que se usen. Eso significa que en algún lugar está codificando las clases que un programa determinado puede usar (como el archivo de configuración que señaló). Al requerir una clase solo cuando se solicita, no necesita ese tipo de hardcoding. –

6

El diseño correcto aquí es una fábrica Eche un vistazo a cómo lo maneja el DBI. Concluirá con una clase TransferAgent que ejemplifica una de una serie de TransferAgent::* clases. Obviamente, deseará más comprobación de errores de la que proporciona la implementación a continuación. Usar una fábrica como esta significa que puede agregar nuevos tipos de agentes de transferencia sin tener que agregar o modificar ningún código.

TransferAgent.pm - la clase de fábrica:

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 funcionalidad básica de una clase 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 cliente (simulacro) 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 - cómo utilizar 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

No creo que quiera esa línea 'use base 'TransferAgent" 'en la clase FTP. Especialmente porque su método de conexión de fábrica no funcionará en una clase derivada (obtendrá el valor incorrecto de la clase, o incluso una instancia peor). ¿Tal vez quiso usar '__PACKAGE__' en su lugar en' require' y 'new' lines? – derobert

+0

También puede usar Class :: Factory del CPAN para esto. Es un módulo bastante pequeño, pero muy fácil de implementar y usar. –

+0

@derobert Sí, era tarde y aún no había dormido. El patrón debe tener una clase separada para obtener la funcionalidad base (que es lo que pretendía que TransferAgent fuera además de ser la fábrica). He corregido el código y lo he desarrollado un poco ahora que estoy despierto. –

1

Usted dijo inicialmente que usará FTP y pasará a otros métodos de transferencia más tarde. No me pondría "elegante" hasta que realmente necesite agregar la segunda o tercera tecnología. Ese segundo método de transferencia puede no ser necesario. :-)

Si quieres hacerlo como un "proyecto de ciencia", entonces genial.

Estoy cansado de ver los patrones de diseño OO que complican las soluciones a los problemas que nunca llegan.

Envuelva el primer método de transferencia en un método uploadFile. Agregue un if if else para el segundo método. Obtenga elegante y refactor en el tercer método. Para entonces, tendrás suficientes ejemplos de que tu solución probablemente sea bastante genérica.

Por supuesto, mi punto principal es que el segundo y el tercer método pueden no ser necesarios.

+3

El problema con el método I-will-make-it-it-last es que cuando hay que hacerlo bien, hay un montón de programas existentes que usan la interfaz no tan agradable. Por supuesto, siempre debe equilibrar las necesidades futuras con la simple necesidad de hacerlo. En este caso, el patrón de diseño de la fábrica se entiende bien y es bastante fácil de implementar, y perderá muy poco tiempo proporcionando una interfaz agradable para el futuro. –

3

Tengo varios ejemplos en Mastering Perl en las secciones de subrutinas dinámicas.

Cuestiones relacionadas