views:

150

answers:

2

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.

+1  A: 

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');
Ether
This is kind of what i had before, which i was also using with Class::Accessor. And someone in a question i asked yesterday about C:A mentioned i should try Moose (answer not related to this case). So that is why i was trying to "moosify" my kind of factory.As for the purpose, i added a note to the main question
+6  A: 

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.

jrockway
mmh... let me understand this role thing, back to the Moose docs... Also, my first thought about it, since i just begin with Moose, is whether it is still better to do bad Moose than not Moose
@alex: if you follow the examples in Moose::Manual::*, you can use most of the features of Moose without running into difficulty. It's only when you start using `meta` that things get crazy :)
Ether
ok, understood, but i am curious about the subtle differences:a) A Moose base class 'Repository' with all the base attributes; Specialized Moose classes 'Repository::_Sftp' that "extends"/inherits the base class; A moose(or not) factory 'RepositoryBuilder', with a method to create me an instance of the specialized class, that can be agnostically manipulated as a 'Repository'.b) The same as a), but with a role instead of the base class <- which is what you proposed, right ? c) and d) MooseX::AbstractFactory or MooseX::ABC , but these are not available by default (i take centos5 for ref)