Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Using a factory class to return objects of the same class

by Amblikai (Scribe)
on May 21, 2019 at 14:35 UTC ( [id://11100317]=perlquestion: print w/replies, xml ) Need Help??

Amblikai has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks!

I have a question which might be considered a "stylistic" issue but i thought i would get some insight.

Essentially i have a class which requires some configuration (read an XML file to decide how to handle incoming data)

Lots of objects are then created of that class using a data structure which can be fairly arbitrary

So basically, i'm musing over the problem of seperating the incoming data with configuration options. Something like

my $obj=class->new({config_file => "file.xml", data => \%some_yummy_da +ta});

Which doesn't seem very elegant to me. So i was thinking of handling it slightly differently:

my $factory=class->new({config_file => "file.xml"}); my $obj=$factory->give_me_my_object(\%data);

But then THAT seems weird since its essentially a factory returning objects of it's own class. And on top of that, it raises questions like what would happen if i tried to access "factory only" methods through the produced objects etc.

Any thoughts? Am i over complicating this?

Thanks as always for any help!

Replies are listed 'Best First'.
Re: Using a factory class to return objects of the same class
by haukex (Archbishop) on May 21, 2019 at 15:48 UTC

    The concept of a factory class makes sense in statically typed languages like Java, where there may be multiple implementations of an interface and you want to hide the process of picking and instantiating an implementation of the interface in such a factory. In Perl, classes can take whatever arguments and return whatever values they please, so this concern applies less often. This means one should spend a little more time thinking about a useful and extensible API and class hierarchy, but I think one has to worry less about the implementation :-) In your case, either of the APIs you showed seems fine to me, however:

    since its essentially a factory returning objects of it's own class

    I don't see how this follows from your description or code - why would a factory return objects of its own class? Why shouldn't the factory return objects of a different class?

      Your comment about spending more time thinking about the API resonates with me, it seems that there are so many ways to accomplish the same thing in perl that i spend a lot of time like this thinking about how to go about the problem!

      In terms of why the factory would be returning objects of its own class, i thought of implementing it in such a way that the factory method and the constructor (which the factory would use) would both be methods of the same class.

      With some further thought, i think what i'm essentially trying to do, it to configure a class in one operation, before creating the objects (which all share that configuration) in multiple subsequent operations. It's probably something i could accomplish by using class methods/variables before using the constructor to create the object. But then i'm not sure how that would work without creating a class handle to begin with (an object)?

      All this is to avoid passing the data structure to the object as a single attribute, i'd rather create the object directly from the data structure. That seems cleaner to me.

      Its an interesting thought either way.

        the factory method and the constructor (which the factory would use) would both be methods of the same class

        In theory, it'd be possible for this "constructor" to return an object of a different class. But I don't see why the constructor would need to be a member of the factory class?

        Anyway, here's my interpretation of your description. Note how Object still has a formal new constructor, but it could be documented that users should not call it directly, but use Factory methods instead - it could even be named _new to make that even more clear.

        use warnings; use strict; package Factory { sub new { my ($class,%args) = @_; my $self = { config => _read_config( $args{config_file} ), }; return bless $self, $class; } sub _read_config { my $file = shift; my %config; # ... return \%config; } sub make_object_from_data { my ($self,$data) = @_; my %args; # complex code to build %args from $data using $self->{config} return Object->new(%args); } } package Object { sub new { my ($class,%args) = @_; my $self = \%args; return bless $self, $class; } # ... } my $factory = Factory->new( config_file => "file.xml" ); my $obj1 = $factory->make_object_from_data( { foo=>123 } ); my $obj2 = $factory->make_object_from_data( { bar=>456 } );

        However, looking at this, here's how I might have structured this code. It has the advantage that "Object->new" isn't hardcoded, making subclassing of that class easier. To me, it feels more in line with the requirements as far as you've described them.

        use warnings; use strict; package Config { sub new { my ($class,%args) = @_; my $self = {}; # code to read $args{config_file} into $self return bless $self, $class; } } package Object { sub new { my ($class,%args) = @_; my $self = _parse_data_with_config($args{config},$args{data}); return bless $self, $class; } sub _parse_data_with_config { my ($config, $data) = @_; my $self = {}; # complex code to build $self from $data using $config return $self; } # ... } my $config = Config->new( config_file => "file.xml" ); my $obj1 = Object->new( config => $config, data => { foo=>123 } ); my $obj2 = Object->new( config => $config, data => { bar=>456 } );

        Of course it'd be possible to add a method to Config like this:

        sub new_object { my ($self,$data) = @_; return Object->new( config=>$self, data=>$data ); } # ... call as: my $obj3 = $config->new_object( { quz=>789 } );

        ... and you'd have something very similar to my first example.

        As you can see, I've put the "complex code" into a sub in each case, and as you can tell, except for the variable names, these subs could be pretty much identical in both examples. This means that the surrounding code could be refactored more easily if you decide that a different API makes more sense. Unless you've got more complex requirements you haven't told us about, I'd suggest just starting with one API - once you actually start using your own API you will quickly notice whether it is good or a different API would be better. And if you've structured your code in a way that makes refactoring easier, you'll be less hesitant to do so.

        since you even used the word "configure", why not make a class variable, and have the Configure sub set that variable (or just write it directly)? You would have to have a lot more protection/error-checking than in this example, but the proof-of-concept could be:

        package MyClass; use warnings; use strict; our %Config; sub Configure { %Config = %{$_[0]}; } sub new { my $class = shift; my $self = {%Config}; my %args = @_; foreach (keys %args) { $self->{$_} = $args{$_}; } return bless $self, $class; } package main; use warnings; use strict; use Data::Dumper; MyClass::Configure( {group => 1, defined => 'values'}); my $x = MyClass->new(); my $y = MyClass->new(extra => 'here'); MyClass::Configure( {group => 2, defined => 0}); my $z = MyClass->new(third => 3); $/ = "\n"; print Dumper $_ for $x, $y, $z;
Re: Using a factory class to return objects of the same class
by choroba (Cardinal) on May 21, 2019 at 15:56 UTC
    Your question is too abstract. The Factory Method Pattern is usually used to create objects of different classes. If the resulting object is always of the same class, just built in a different way, you rather want a Builder.

    You can have a Factory of Builders, but I fear I used too much imagination so the following might not be usable for your situation. It also doesn't make any sense for the simple situation I created, but that's how OO programming works - without particular objects and behaviours, it's all hand waving. It's hard to abstract from something that's already been abstracted.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      If i was using a builder method(s) to handle the configuration of the object, how would i go about seperating the configuration from the incoming data (since the data is somewhat arbitrary)?

      I'm thinking of just using class methods/variables to set up the configuration of the class before creating objects

        Builder is not a method, it's an object. Its constructor takes the configuration, and its ->build method takes the data as an argument.

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11100317]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (6)
As of 2024-04-18 18:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found