6

と呼ばれる単純なMooseベースのクラスを作成しましたDocument。このクラスには、との2つの属性がnameありhomepageます。

このクラスは、属性do_something()に基づいてさまざまなソース(Webサイトやさまざまなデータベースなど)からテキストを取得して返すというメソッドも提供する必要があります。homepage

にはまったく異なる実装がたくさんあるので、do_something()それらを異なるパッケージ/クラスに入れたいと思います。これらの各クラスは、homepage属性に責任があるかどうかを知る必要があります。

これまでの私のアプローチには2つの役割があります。

package Role::Fetcher;
use Moose::Role;
requires 'do_something';
has url => (
    is => 'ro',
    isa => 'Str'
);

package Role::Implementation;
use Moose::Role;
with 'Role::Fetcher';
requires 'responsible';

呼び出されるクラスは、一般的に使用されるメソッド(HTTP GETリクエストなど)Document::Fetcherのデフォルトの実装を提供します。do_something()

package Document::Fetcher;
use Moose;
use LWP::UserAgent;
with 'Role::Fetcher';

has ua => (
    is => 'ro',
    isa => 'Object',
    required => 1,
    default => sub { LWP::UserAgent->new }
);

sub do_something {'called from default implementation'}
sub get {
    my $r = shift->ua->get(shift);
    return $r->content if $r->is_success;
    # ...
}

そして、次のような方法で責任を決定する特定の実装responsible()

package Document::Fetcher::ImplA;
use Moose;
extends 'Document::Fetcher';
with 'Role::Implementation';

sub do_something {'called from implementation A'}
sub responsible { return 1 if shift->url =~ m#foo#; }

package Document::Fetcher::ImplB;
use Moose;
extends 'Document::Fetcher';
with 'Role::Implementation';

sub do_something {'called from implementation B'}
sub responsible { return 1 if shift->url =~ m#bar#; }

Documentのクラスは次のようになります。

package Document;
use Moose;

has [qw/name homepage/] => (
    is => 'rw',
    isa => 'Str'
);

has fetcher => (
    is => 'ro',
    isa => 'Document::Fetcher',
    required => 1,
    lazy => 1,
    builder => '_build_fetcher',
    handles => [qw/do_something/]
);

sub _build_fetcher {
    my $self = shift;
    my @implementations = qw/ImplA ImplB/;

    foreach my $i (@implementations) {
        my $fetcher = "Document::Fetcher::$i"->new(url => $self->homepage);
        return $fetcher if $fetcher->responsible();
    }

    return Document::Fetcher->new(url => $self->homepage);
}

現在、これは正常に機能します。次のコードを呼び出すと:

foreach my $i (qw/foo bar baz/) {
    my $doc = Document->new(name => $i, homepage => "http://$i.tld/");
    say $doc->name . ": " . $doc->do_something;
}

期待どおりの出力が得られます。

foo: called from implementation A
bar: called from implementation B
baz: called from default implementation

ただし、このコードには少なくとも2つの問題があります。

  1. 既知のすべての実装のリストをに保持する必要があり_build_fetcherます。名前空間の下にロードされたすべてのモジュール/クラスからコードが自動的に選択する方法が好きDocument::Fetcher::です。または、これらの種類のプラグインを「登録」するためのより良い方法があるのでしょうか。

  2. 現時点では、コード全体が少し肥大化しているように見えます。私は人々が以前にこの種のプラグインシステムを書いたことがあると確信しています。MooseXには、目的の動作を提供するものがありませんか?

4

1 に答える 1

7

あなたが探しているのはファクトリー、具体的にはアブストラクトファクトリーです。Factoryクラスのコンストラクターは、引数に基づいて返す実装を決定します。

# Returns Document::Fetcher::ImplA or Document::Fetcher::ImplB or ...
my $fetcher = Document::Fetcher::Factory->new( url => $url );

のロジックはに_build_fetcherなりDocument::Fetcher::Factory->newます。これにより、フェッチャーがドキュメントから分離されます。ドキュメントが必要なFetcherの実装を把握する方法を知っている代わりに、Fetchersはそれを自分で行うことができます。

ファクトリを変更せずに新しいFetcherを追加できるようにすることが優先される場合は、Fetcherの役割がファクトリに対処できるかどうかをファクトリに通知できるという基本的なパターンが適切です。欠点として、Fetcher :: Factoryは、特定のURLに対して複数のFetcherが有効である可能性があり、一方が他方よりも優れている可能性があることを認識できません。

Fetcher :: FactoryにハードコーディングされたFetcher実装の大きなリストがないようにするには、各FetcherロールがロードされたときにFetcher::Factoryに登録するようにします。

my %Registered_Classes;

sub register_class {
    my $class = shift;
    my $registeree = shift;

    $Registered_Classes{$registeree}++;

    return;
}

sub registered_classes {
    return \%Registered_Classes;
}

ケーキが必要な場合は、ドキュメントなどの一般的なFetcherをプリロードして、それも食べることができます。

于 2012-06-09T00:47:18.833 に答える