2012-06-08 14 views
6

मैंने Document नामक एक साधारण Moose आधारित कक्षा लिखी है। इस वर्ग में दो विशेषताएं हैं: name और homepageपर्ल/मूस - मैं गतिशील रूप से किसी विधि के विशिष्ट कार्यान्वयन को कैसे चुन सकता हूं?

कक्षा को do_something() नामक एक विधि प्रदान करने की आवश्यकता है जो homepage विशेषता के आधार पर विभिन्न स्रोतों (जैसे वेबसाइट या विभिन्न डेटाबेस) से टेक्स्ट पुनर्प्राप्त और लौटाता है।

चूंकि do_something() के लिए पूरी तरह से अलग कार्यान्वयन का एक बहुत हो जाएगा, मैं उन्हें विभिन्न संकुल/कक्षाओं में करना चाहते हैं और इन कक्षाओं में से प्रत्येक को पता होना चाहिए अगर यह homepage विशेषता के लिए जिम्मेदार है या अगर यह नहीं है ।

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'; 

एक वर्ग जो do_something() के लिए एक डिफ़ॉल्ट implmenentation प्रदान करता है और आमतौर पर इस्तेमाल किया तरीकों (एक HTTP GET अनुरोध की तरह) Document::Fetcher कहा जाता है:

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 

लेकिन वहाँ इस कोड के साथ कम से कम दो मुद्दे हैं:

  1. मैं एक रखने की जरूरत है _build_fetcher में सभी ज्ञात कार्यान्वयन की सूची। मैं एक तरीका पसंद करूंगा जहां कोड नामस्थान Document::Fetcher:: के नीचे स्वचालित रूप से प्रत्येक लोड मॉड्यूल/कक्षा से चयन करेगा। या शायद इस तरह के प्लगइन "रजिस्टर" करने का एक बेहतर तरीका है?

  2. फिलहाल पूरा कोड थोड़ा फूला हुआ दिखता है। मुझे यकीन है कि लोगों ने पहले इस तरह की प्लगइन प्रणाली लिखी है। MooseX में कोई चीज़ नहीं है जो वांछित व्यवहार प्रदान करता है?

उत्तर

7

क्या आप देख रहे हैं एक Factory विशेष रूप से एक Abstract Factory है। आपके कारखाने वर्ग के लिए निर्माता यह निर्धारित करेगा कि इसके तर्कों के आधार पर कौन सा कार्यान्वयन वापस आ जाएगा।

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

_build_fetcher में तर्क Document::Fetcher::Factory->new में जाना होगा। यह Fetchers को आपके दस्तावेज़ों से अलग करता है। दस्तावेज के बजाय यह जानने के लिए कि कौन सी Fetcher कार्यान्वयन की आवश्यकता है, यह समझने के लिए, Fetchers खुद को कर सकते हैं।

फैक्टरी को सूचित करने में सक्षम होने वाली फ़ैचर भूमिका रखने का आपका मूल पैटर्न यदि यह इससे निपटने में सक्षम है तो आपकी प्राथमिकता लोगों को कारखाने को बदलने के बिना नए Fetchers जोड़ने की अनुमति देनी है। नीचे की तरफ, Fetcher :: फैक्टरी को पता नहीं हो सकता है कि एकाधिक Fetchers किसी दिए गए यूआरएल के लिए मान्य हो सकता है और यह एक दूसरे से बेहतर हो सकता है।

अपने Fetcher :: फैक्ट्री में हार्ड कोड किए गए Fetcher कार्यान्वयन की एक बड़ी सूची होने से बचने के लिए, प्रत्येक Fetcher भूमिका स्वयं लोड होने पर Fetcher :: फैक्टरी के साथ पंजीकृत है।

my %Registered_Classes; 

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

    $Registered_Classes{$registeree}++; 

    return; 
} 

sub registered_classes { 
    return \%Registered_Classes; 
} 

आप शायद दस्तावेज़, आम Fetchers का एक समूह पहले से लोड करता है, तो आप अपने केक चाहते हैं और यह भी खाना कुछ है सकते हैं।

+0

मैंने GRASP जैसे सिद्धांतों के बारे में भी सोचा नहीं है। किसी भी तरह से मैंने ऐसा किया जो मूस के साथ "एक अच्छा तरीका" प्रतीत होता था। अब जब आपने उनका उल्लेख किया है तो यह एक अमूर्त कारखाने का उपयोग करने के लिए निश्चित रूप से सही अर्थ बनाता है। मुझे अभी भी यकीन नहीं है कि प्रत्येक भूमिका कैसे पंजीकृत करें। क्या इसे किसी प्रकार की सिंगलटन कक्षा की आवश्यकता नहीं होगी? अभी मैं कुछ हद तक हैकी समाधान का उपयोग कर रहा हूं: '% दस्तावेज़ :: Fetcher ::' की जांच कर रहा हूं। –

+1

@ सेबेस्टियनस्टम्पफ आपको चीजों को जटिल बनाने की ज़रूरत नहीं है क्योंकि मूस लोगों के पास क्लास डेटा के खिलाफ दार्शनिक क्रोध है, और न ही आपको वैश्विक चर के लिए पहुंचना है। सामान्य encapsulation अभी भी काम करता है। – Schwern

+0

मैंने इसे अंत में कारक के मेटा क्लास में एक विशेषता जोड़कर थोड़ा और अधिक "मूसिश" हल किया जिसमें 'fetrayhers 'नामक' ArrayRef [Str] 'विशेषता है। तो मैं सिर्फ '__PACKAGE __-> मेटा-> fetchers-> add' कह सकता हूं। :-) –

संबंधित मुद्दे

 संबंधित मुद्दे