【问题标题】:How can I create an object whose derived class is specified implicitly by the creation properties?如何创建其派生类由创建属性隐式指定的对象?
【发布时间】:2010-11-03 07:04:41
【问题描述】:

我正在寻找以下模式。 (我在 Perl 工作,但我认为该语言并不特别重要)。

有一个父类 Foo,以及子类 Bar、Baz、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;
}

【问题讨论】:

    标签: perl inheritance constructor design-patterns creation


    【解决方案1】:

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

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

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

    【讨论】:

    • 谢谢。在我看来,如果我必须做类似的事情,那么我的“注册”会更简单、更清晰。我希望有一个我错过的既定模式。
    • 决定取决于您要解决的用例。如果您想让您的 Foo 工厂对未知的 3rd-party 子类开放,那么您将进行查找。如果你生活在一个受控环境中,只支持你自己的代码,那么注册表就可以了。
    【解决方案2】:

    也许你可以用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,但这可能有点超出您的需求。

    【讨论】:

    • 谢谢。逻辑上的 Module::Pluggable 正是我想要的;但它可能不适合我们的情况,因为它可能不会出现在我们用户的机器上,所以我们必须使用我们自己的模块分发它。与我的注册解决方案相比,它也很“神奇”,所以可能不太清楚。但是感谢您引起我的注意。
    • 这有点神奇,但默认行为只会加载 MyClass::Plugin 命名空间中的类(并且该命名空间很容易覆盖)。因此,注册实际上与在该命名空间中创建模块相同。很明显,它是一个 CPAN 模块,但如果你现在可以在没有 CPAN(或大量轮子改造)的情况下在 Perl 中管理任何类型的严肃应用程序,我会感到惊讶。
    【解决方案3】:

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

    至少那会是我的偏好……从这个角度来看,您当前的父类(可能在您的子类中具有一些通用功能)可能会变成抽象或接口。

    然后单例可以管理所有子类的构造及其分布(如果它们不起作用,则克隆它们?)...此外,可以将子类移动到单独的 dll 中以促进分离。

    抱歉,这不是一个直接的解决方案。 我过去通过管理单例中的类列表来完成此操作,就像您在这里一样。单例背后的想法是,如果您确实想使用任何昂贵的反射,您只需执行一次。

    【讨论】:

    • 谢谢 - 它不是一个单例,它是一个抽象基类。一旦创建了对象,它们将通过多态性获得正确的方法,这就是当我担心还没有子类对象时如何处理的情况。
    【解决方案4】:

    看来您正试图让一个类既是基类又是工厂。别。使用 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);
    

    这样你的基类就不需要知道你的子类,只有你的工厂知道。并且子类也不需要相互了解,只有 Factory 需要知道尝试哪些子类以及按什么顺序尝试的详细信息。

    【讨论】:

    • 通过这种方法,您甚至可以在需要了解完整子类集的代码中占有一席之地。我认为这比注册表解决方案还要糟糕。如果要添加/删除子类,则必须将其他地方的某些代码一致地修改。因此,您的想法不会像模式通常所针对的那样增加应对变化的灵活性。
    • @mkoeller,让工厂自动加载特定命名空间ala Module::Pluggable 中的一组类是微不足道的。我通常更喜欢明确说明这些事情以避免意外,但这更多的是品味问题,而不是应对变化的灵活性。灵活性意味着很容易添加可以处理该字符串的新子类。这并不意味着如果它们存在于文件系统上,它会自动处理它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    • 2013-08-30
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 2020-08-06
    • 2018-04-08
    相关资源
    最近更新 更多