2009-08-15 4 views
5

Perl에 저장 관련 앱을 작성해야합니다. 앱이 로컬 컴퓨터의 파일을 다른 저장 장치 노드에 업로드해야합니다. 현재 업로드 방법은 FTP이지만 추후에는 비트 토 런트 또는 알려지지 않은 수퍼 파일 전송 방법이 될 수 있습니다.Perl에서 디스패치 테이블을 구현하는 방법은 무엇입니까?

업로드해야하는 모든 파일에는 파일 이름, 파일을 업로드 할 저장 장치 노드 및 업로드 중에 사용할 전송 방법을 정의하는 구성 파일이 있습니다.

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

하지만 내 기본적인 OO 지식이 학교에서 배운 심지어로, 나는 아직도이 좋은 디자인 아니라고 느낄 :

은 물론, 내 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다. (질문 제목이 다소 오해 할 수 있습니다 .OI 솔루션이 아닌 문제로 정상적으로 문제를 해결할 수 있다고 생각하면 OO 지식이 제한되어 있으므로 사실 더 좋을 것입니다.)

그래서 너희들이 나에게 일반적으로 조언을 해줄 수 있니? 물론 샘플 코드도 함께 제공하면 큰 도움이 될 것입니다.

답변

13

첫째, Perl에서 문자열 동일성 테스트는 eq이 아니라 ==이 아닙니다. 당신이 일을하는 방법이있는 경우, 비트 및 ftp라는 말

,

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

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

여기에 어떤 일이 벌어지고 있는지에 대한 설명을 추가하는 것이 좋습니다. 그러나 여전히 좋은 대답입니다. –

+0

거짓 값이 유효한 코드 레퍼런스가 아니기 때문에 정의 할 필요가 없습니다. 또한 조회 테이블에서 메소드를 찾을 수없는 경우 경고를 표시해야합니다. 대안은 모든 메소드를 클래스에 넣고'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은 subname에 대한 참조를 반환합니다. perlref, "참고 자료 만들기"를 참조하십시오. – derobert

+1

일부 OO를하는 것은 너무 드물게 과장됩니다. 그리고이 예제는 현명하게 해결되었습니다. – innaM

8

당신이 해시를 사용할 수 있습니다 ...

  1. 는 각각의 전송 방법을 해시에 자신을 등록하게한다. 이 OO (일부 전송 메소드 팩토리에서 메소드를 호출하여) 또는 절차 적으로 (해시를 패키지 변수로 만들거나 모듈화하지 않으려는 경우에도 기본 패키지에 넣을 수 있습니다.) 할 수 있습니다.

    package MyApp::Transfer::FTP; 
    $MyApp::TransferManager::METHODS{ftp} = \&do_ftp; 
    sub do_ftp { ... } 
    1; 
    
  2. 각 전송 방법은 일관된 API를 사용합니다. 어쩌면 그저 함수 일 수도 있고 객체 인터페이스 일 수도 있습니다.

  3. 해시를 통해 전송을 호출합니다. BTW

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

: 객체 지향 등록 방법은 같은 것을 보일 것이다 :

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

해시는 가능한 전송 에이전트를 나열하는 문제가 여전히 있습니다. 이 목록을 하드 코딩 할 이유가 없습니다. TransferAgent :: FTP, TransferAgent :: SCP, TransferAgent :: BitTorrent 등을 생성하면됩니다. 그러면, 팩토리 클래스가 올바른 클래스를 인스턴스화 할 책임이 있습니다. –

+2

@Chas. Owens : 목록을 하드 코딩 할 곳은 어디입니까? 각 메소드 구현은 자체 등록을 담당합니다. config 파일을 사용하는 것이 어느 전송 모듈을로드하는지 지정하는 것이 매우 쉽습니다 (예 : 커스터마이징 수준을 원할 경우, 매우 의존성이 높은 모듈을 해제하려는 경우). 또는 지정된 디렉토리에 모든 .pm 파일을로드합니다 (if 당신은 그 수준의 마법을 원합니다) – derobert

+1

@derobert 개별 클래스는 어떻게 실행됩니까? 여러 서버 유형으로 전송해야하는 프로그램이있는 경우 프로그램에서 각 유형을 별도의 '사용'문으로 지정해야합니까? 클래스는 사용될 때까지 스스로 등록 할 수 없습니다. 이는 어딘가에서 주어진 프로그램이 사용할 수있는 클래스 (예 : 지적한 설정 파일)를 하드 코딩하고 있음을 의미합니다. 그런 종류의 하드 코딩이 필요하지 않은 경우에만 수업을 요구함으로써. –

6

올바른 디자인 공장입니다. DBI이 어떻게 처리하는지 살펴보십시오. 어떤 TransferAgent::* 클래스 중 하나를 인스턴스화하는 TransferAgent 클래스로 마무리 할 것입니다. 분명히 아래의 구현보다 많은 오류 검사를 원할 것입니다. 이와 같은 팩토리를 사용하면 코드를 추가하거나 수정할 필요없이 새로운 유형의 전송 에이전트를 추가 할 수 있습니다.

TransferAgent.오후 - 공장 클래스 : A (모의) FTP 클라이언트 구현 : - -

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 :

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.pmTransferAgent::* 클래스의 기본 기능이 포함되어

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

FTP 클래스의 'Use base "TransferAgent"줄을 사용하고 싶지 않습니다. 특히 팩토리 연결 메소드가 파생 클래스에서 작동하지 않기 때문에 (클래스의 잘못된 값을 얻거나, 대신 인스턴스를 악화시킬 수 있습니다). 어쩌면 당신의'require'와'new' 라인에서'__PACKAGE__'을 쓰려고했던 것일까 요? – derobert

+0

CPAN에서 Class :: Factory를 사용할 수도 있습니다. 매우 작은 모듈이지만 구현 및 사용이 매우 쉽습니다. –

+0

@derobert 예, 늦었고 아직 잠을 자지 않았습니다. 패턴은 기본 기능을 가져 오기 위해 별도의 클래스를 가져야합니다 (TransferAgent가 팩토리가 될 수 있도록하려는 것입니다). 코드를 수정하고 깨어났다. –

1

처음에는 FTP를 사용하고 나중에 다른 전송 방법으로 이동한다고하셨습니다. 실제로 두 번째 또는 세 번째 기술을 추가해야만 "우아함"을 얻을 수 있습니다. 두 번째 전송 방법은 절대로 필요하지 않을 수 있습니다. :-)

당신이 "과학 프로젝트"로 그것을하고 싶다면.

결코 도착하지 않는 문제에 대한 해결책을 복잡하게하는 OO 디자인 패턴을 보면서 지겨워합니다.

uploadFile 메서드에서 첫 번째 전송 메서드를 래핑합니다. 두 번째 메소드에 대해 if, else를 추가하십시오. 세 번째 방법에서 우아하고 리팩터링하십시오. 그때까지는 솔루션이 꽤 일반적 일 것이라는 충분한 사례를 보게 될 것입니다.

물론, 필자의 주요 요점은 두 번째와 세 번째 방법이 절대로 필요하지 않을 수 있다는 것입니다.

+3

I-will-make-it-nice-later 방식의 문제점은 좋지 않은 인터페이스를 사용하는 기존 프로그램이 많다는 것입니다. 물론, 당신은 항상 미래의 요구와 그것을 끝내기위한 간단한 필요의 균형을 이루어야합니다. 이 경우 공장 설계 패턴을 잘 이해하고 구현하기가 쉽고 미래를위한 멋진 인터페이스를 제공하는 데는 거의 시간을 낭비하지 않을 것입니다. –

3

동적 서브 루틴의 섹션에서 Mastering Perl에 몇 가지 예가 있습니다.

관련 문제