如何让 Moose 返回子类实例而不是它自己的类,以实现多态性

发布于 2024-09-05 05:26:22 字数 535 浏览 11 评论 0原文

我想创建一个泛型类,其构建器不会返回该泛型类的实例,而是返回专用子类的实例。

由于 Moose 会自动构建对象,所以我不明白这是否可能,以及如何使用 Moose 语法创建 Moose 类并具有这种行为。

例如: 用户询问: $file = Repository->new(uri=>'sftp://blabla') .... 并返回一个 `Repository::_Sftp`` 实例,

用户将使用 $file 就好像它是一个 Repository 实例,而不需要知道真正的子类(多态性)

注意:
根据要求,也许我应该更清楚我想要实现的目标:
我的类的目的是能够添加新的存储库方案(例如通过 sftp),只需创建一个“隐藏”Repository::_Stfp 类,并在存储库构造函数中添加一个案例,根据 url 工厂正确的专用对象。存储库就像一个虚拟基类,提供专门对象将实现的接口。
所有这些都是为了添加新的存储库方案,而不需要修改程序的其余部分:它会在不知不觉中处理专用实例,就好像它是存储库实例一样。

I want to create a generic class, whose builder would not return an instance of this generic class, but an instance of a dedicated child class.

As Moose does automatic object building, I do not get to understand if this something possible, and how to create a Moose class with Moose syntax and having this behaviour.

e.g.:
The user asks: $file = Repository->new(uri=>'sftp://blabla') .... and is returned an `Repository::_Sftp`` instance

User would use $file as if it is a Repository instance, without the need to know the real subclass (polymorphism)

Note:
As requested, maybe i should have been more clear about what i was trying to achieve:
The purpose of my class is to be able to add new Repository schemes (eg over sftp), by simply creating an "hidden" Repository::_Stfp class, and adding a case in the Repository constructor to factory the correct specialized object depending of url. Repository would be like a virtual base class, providing an interface that specialized objects would implement.
All of this is for adding new repository schemes without having the rest of the program to be modified: it would unknowingly deal with the specialized instance as if it is a Repository instance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我喜欢麦丽素 2024-09-12 05:26:22

new 构建构建器。您想要一些其他方法来实际返回构建的对象。

这是一个例子:

  class RepositoryBuilder {
     has 'allow_network_repositories' => (
         is       => 'ro',
         isa      => 'Bool',
         required => 1,
     );

     method build_repository(Uri $url) {
        confess 'network access is not allowed'
            if $url->is_network_url && !$self->allow_network_repositories;

        my $class = $self->determine_class_for($url); # Repository::Whatever
        return $class->new( url => $url );
     }
  }

  role Repository { <whatever }

  class Repository::File with Repository {}
  class Repository::HTTP with Repository {}

在这里,构建器和构建的对象是不同的。建造者是一个
真实的对象,带有参数,可以定制构建
对象视情况而定。然后,“构建”的对象是
仅返回方法的值。这允许您构建其他
建设者根据情况而定。 (建造者的问题
功能的一个缺点是它们非常不灵活——很难教它们
一个新的特殊情况。构建器对象仍然存在这个问题,
但至少你的应用程序可以创建一个子类,实例化它,
并将这个对象传递给任何需要创建对象的东西。但
在这种情况下,依赖注入是更好的方法。)

此外,您构建的存储库不需要继承
任何东西,它们只需要一个标签来表明它们是存储库。
这就是我们的 Repository 角色的作用。 (您需要添加
此处的 API 代码以及应重用的任何方法。但要小心
关于强制重用——您确定所有标记有
存储库角色会想要那个代码吗?如果没有,只需将代码放入
另一个角色并将其应用于需要该角色的类
功能。)

以下是我们如何使用我们创建的构建器。如果你不想
触摸网络:

my $b = RepositoryBuilder->new( allow_network_repositories => 0 );
$b->build_repository( 'http://google.com/' ); # error
$b->build_repository( 'file:///home/whatever' ); # returns a Repository::Foo

但是如果您这样做:

my $b = RepositoryBuilder->new( allow_network_repositories => 1 );
$b->build_repository( 'http://google.com/' ); # Repository::HTTP

现在您已经有了一个可以按照您喜欢的方式构建对象的构建器,
您只需要在其他代码中使用这些对象即可。所以最后一块
谜题中指的是其他中的“任何”类型的存储库对象
代码。很简单,您使用 does 而不是 isa

class SomethingThatHasARepository {
    has 'repository' => (
       is       => 'ro',
       does     => 'Repository',
       required => 1,
    );
}

就完成了。

new builds the builder. You want some other method to actually return the built object.

Here's an example:

  class RepositoryBuilder {
     has 'allow_network_repositories' => (
         is       => 'ro',
         isa      => 'Bool',
         required => 1,
     );

     method build_repository(Uri $url) {
        confess 'network access is not allowed'
            if $url->is_network_url && !$self->allow_network_repositories;

        my $class = $self->determine_class_for($url); # Repository::Whatever
        return $class->new( url => $url );
     }
  }

  role Repository { <whatever }

  class Repository::File with Repository {}
  class Repository::HTTP with Repository {}

Here, the builder and the built object are distinct. The builder is a
real object, complete with parameters, that can be customized to build
objects as the situation demands. Then, the "built" objects are
merely return values of a method. This allows you to build other
builders depending on the situation. (A problem with builder
functions is that they are very inflexible -- it's hard to teach them
a new special case. This problem still exists with a builder object,
but at least your application can create a subclass, instantiate it,
and pass this object to anything that needs to create objects. But
dependency injection is a better approach in this case.)

Also, there is no need for the repositories you build to inherit from
anything, they just need a tag indicating that they are repositories.
And that's what our Repository role does. (You will want to add the
API code here, and any methods that should be reused. But be careful
about forcing reuse -- are you sure everything tagged with the
Repository role will want that code? If not, just put the code in
another role and apply that one to the classes that require that
functionality.)

Here's how we use the builder we created. If, say, you don't want to
touch the network:

my $b = RepositoryBuilder->new( allow_network_repositories => 0 );
$b->build_repository( 'http://google.com/' ); # error
$b->build_repository( 'file:///home/whatever' ); # returns a Repository::Foo

But if you do:

my $b = RepositoryBuilder->new( allow_network_repositories => 1 );
$b->build_repository( 'http://google.com/' ); # Repository::HTTP

Now that you have a builder that builds the objects the way you like,
you just need to use these objects in other code. So the last piece
in the puzzle is referring to "any" type of Repository object in other
code. That's simple, you use does instead of isa:

class SomethingThatHasARepository {
    has 'repository' => (
       is       => 'ro',
       does     => 'Repository',
       required => 1,
    );
}

And you're done.

秋千易 2024-09-12 05:26:22

不(不直接)。一般来说,在 Moose 中,调用 CLASS->new ,其中 CLASS 是 Moose::Object 将返回 CLASS 的实例。

您能否更详细地描述您想要实现的目标,以及为什么您认为这是您想要的?您可能想要构建一个工厂类——当您调用它的方法时,它将调用适当的类的构造函数并将该对象返回给您,而无需关心您所使用的特定类型。回来:

package MyApp::Factory::Repository;

sub getFactory
{
     my ($class, %attrs);

     # figure out what the caller wants, and decide what type to return
     $class ||= 'Repository::_Sftp';
     return $class->new(attr1 => 'foo', attr2 => 'bar', %attrs);
}

my $file = MyApp::Factory::Repository->getFactory(uri=>'sftp://blabla');

No (not directly). In general in Moose, calling CLASS->new where CLASS isa Moose::Object will return an instance of CLASS.

Can you describe in more detail what you are trying to achieve, and why you think this is what you want? You probably want to build a factory class -- when you call a method on it, it will call the appropriate class's constructor and return that object to you, without you having to be concerned with the particular type you get back:

package MyApp::Factory::Repository;

sub getFactory
{
     my ($class, %attrs);

     # figure out what the caller wants, and decide what type to return
     $class ||= 'Repository::_Sftp';
     return $class->new(attr1 => 'foo', attr2 => 'bar', %attrs);
}

my $file = MyApp::Factory::Repository->getFactory(uri=>'sftp://blabla');
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文