2009-06-16 55 views
3

我正在寻找以下模式。 (我正在使用Perl,但我认为语言并不重要)。如何创建派生类由创建属性隐式指定的对象?

与父母班Foo,和儿童酒吧,巴兹,Bazza。

构建Foo的方法之一是解析一个字符串,并且该字符串的一部分将隐式指定要创建哪个类。例如,如果它启动'http:',那么它是一个Bar,但如果它不包含'[Date]',那么Baz喜欢它,等等。

现在,如果Foo知道它的所有孩子,以及什么字符串是一个Bar,什么是Baz等,它可以调用适当的构造函数。但是基础班不应该有任何关于其子女的知识。

我想要的是Foo的构造函数能够依次尝试它的孩子,直到其中一个人说:“是的,这是我的,我会创造出这个东西”。

我知道,在一般情况下,这个问题没有明确定义,因为可能有多个孩子接受这个字符串,所以他们被称为重要的顺序:忽略这个,并假设字符串的特征是只有一个子类会喜欢字符串。

我想出的最好的方法是让子类在初始化时对基类进行“注册”,以便它获取构造函数列表,然后遍历它们。但有没有更好的方法,我错过了?

示例代码:

package Foo; 

my @children; 

sub _registerChild 
{ 
    push @children, shift(); 
} 

sub newFromString 
{ 
    my $string = shift; 
    foreach (@children) { 
    my $object = $_->newFromString(@_) and return $object; 
    } 
    return undef; 
} 

package Bar; 
our @ISA = ('Foo'); 

Foo::_registerChild(__PACKAGE__); 

sub newFromString 
{ 
    my $string = shift; 
    if ($string =~ /^http:/i) { 
    return bless(...); 
    } 
    return undef; 
} 

回答

5

也许你可以用Module::Pluggable来实现这个?这将消除注册的需要。

我之前采用的方法是使用Module :: Pluggable加载我的子模块(这使我可以通过简单地编写和安装它们来添加新的子模块)。每个子类都有一个构造函数,可以返回一个有福的对象或undef。你循环你的插件,直到你得到一个对象,然后返回它。

喜欢的东西:

package MyClass; 
use Module::Pluggable; 

sub new 
{ 
    my ($class, @args) = @_; 
    for my $plugin ($class->plugins) 
    { 
     my $object = $plugin->new(@args); 
     return $object if $object; 
    } 
} 

Class:Factory很好,但可能是有点洁癖您的需求。

+0

谢谢。逻辑模块:: Pluggable非常好,正是我所期待的;但它可能不适合我们的情况,因为它可能不会出现在我们用户的机器上,所以我们必须使用我们自己的模块进行分发。 与我的注册解决方案相比,这也是“神奇的”,所以它可能不太清楚。 但是,感谢您的关注。 – 2009-06-16 14:27:48

+0

这是有点神奇,但默认行为只会加载MyClass :: Plugin命名空间中的类(并且该命名空间很容易覆盖)。因此,注册与在该名称空间中创建模块的效果完全相同。 很显然,它是一个CPAN模块,但如果您现在可以在没有CPAN(或大量的车轮重新创建)的情况下管理Perl中的任何严重应用程序,我会感到惊讶。 – 2009-06-16 15:06:05

0

您可以实现类Foo一个任意查找算法,即搜索现有的子类。也许基于子类提供的配置文件,或者你可能想到的任何其他机制。

然后Foo类会在运行时检测现有的客户类并依次调用它们。

另外,您可以缓存查找结果并接近您已经描述过的注册表解决方案。

+0

谢谢。在我看来,如果我必须这样做,那么我的“注册”就更简单,更清晰。我希望有一个我已经错过的既定模式。 – 2009-06-16 11:56:29

+0

决定归结于您想要解决的用例。如果你想让你的Foo工厂开放给未知的第三方儿童班,那么你会做一个查询。 如果你生活在一个受控的环境中,只支持你自己的代码,那么注册表就没事了。 – mkoeller 2009-06-17 13:55:15

0

如果您对您的父类的评论不包含有关chilidren的信息以及您将方法委托给类自身的子类适合性的任务,那么从父类中选择类选择可能是正确的并为此任务创建一个单例。

至少这将是我的偏好...从这个当前的父类(假设在你的子类中有一些共同的功能)可能会变成抽象的或者是一个接口。

然后,单身人士可以管理所有子类的构建及其分布(如果它们不起作用克隆它们)......此外,子类可以移入单独的dll以促进分离。

对不起,这不是一个直接的解决方案。 我在过去通过管理单例中的类列表,就像你在这里一样。单身人士背后的想法是,如果你想使用任何昂贵的反射,你只需要做一次。

1

看来你试图让一个类既是基类又是工厂。别。使用2个独立的课程。事情是这样的:

package Foo; 

package Bar; 
use base 'Foo'; 

package Baz; 
use base 'Foo'; 

package Bazza; 
use base 'Foo'; 

package Factory; 
use Bar; 
use Baz; 
use Bazza; 

sub get_foo { 
    my ($class, $string) = @_; 
    return Bar->try($string) || Baz->try($string) || Bazza->try($string); 
} 

,然后用它喜欢:

my $foo = Factory->get_foo($string); 

这样的基础类并不需要了解你的子类,只有你的工厂做。而且儿童班也不需要彼此了解,只有工厂需要了解哪些儿童班的详细信息以及按照哪个顺序进行。