Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Inheritance: parent class determine which child class to use?

by fbicknel (Sexton)
on Jul 30, 2012 at 19:25 UTC ( #984496=perlquestion: print w/ replies, xml ) Need Help??
fbicknel has asked for the wisdom of the Perl Monks concerning the following question:

So, say you had a base class 'Car' and two classes inheriting from Car: 'Sedan' and 'SUV'.

If I want to instantiate an object, but don't know whether I have a Sedan or an SUV, can I ask the base class to figure that out for me and return the right object?

Is that good practice?

I was thinking along the lines of putting some code in the base class that could determine what type of object I should have, but then call child->new to get an object of that type and return it.

This is backward from most examples I've seen, where you call child->new and it calls SUPER::new, blessing this into the child class before returning it.

It may still be an inheriting class because it may share other methods with its base class. SUV and Sedan may both share the ->startEngine method, for example.

An alternative might be to provide a method in Car that determines which (Sedan/SUV), then have the implementor call ->new in the respective child class based on the result. Seemed tidier to have ->new in Car figure it out for me and call the appropriate child's ->new to obtain an object of the correct type.

It seems logical that the base class should be expert at determining what type of subclass might be appropriate for the job. To extend the analogy, the car's owner manual should be able to tell me what type of car I am in; without knowing what kind of car I'm sitting in as I browse the owner's manual. *ahem*

A more computer-related analogy (close to what I'm doing here): I have a mulitpather on my host, but don't know whether it's Powerpath or DMMP yet. I can write a bit of code to determine that easily enough... but then my implementing code doesn't know which class to call ->new on: DMMP->new or Powerpath->new?

I'm thinking have the base class ->new called: Multipath->new, and it will return an object of one type or the other based on that aforementioned bit of code. Then I can use that object from then on, not caring whether it's PP or DMMP: the methods from there on out would all be either like-named or I can use $obj->can to find out whether the feature is available if need be.

Comment on Inheritance: parent class determine which child class to use?
Re: Inheritance: parent class determine which child class to use?
by moritz (Cardinal) on Jul 30, 2012 at 19:35 UTC
    If I want to instantiate an object, but don't know whether I have a Sedan or an SUV, can I ask the base class to figure that out for me and return the right object?

    You certainly can.

    Is that good practice?

    This probably debatable. From a theoretical perspective, a parent class should not depend on its child classes, just the other way. Or formulated differently, you want to avoid cycles in your dependency graphs.

    From a practical point of view, there often needs to be some piece of code that needs to create as-specific-as-possible objects. Why not have put it in the parent class?

    An approach I found practical is to have some kind of "type registry", which is usually just a hash. On object creation, some kind of key looks into the hash, and then simply re-dispatches to new method of that type.

    package Car; has %car_by_type; sub new { my ($self, %opts) = @_; if ($opts{name} && $car_by_type{$opts{name}}) { $car_by_type{$opts{name}}->new(%opts); } else { die "Don't know how to make a new car"; } } method REGISTER_TYPE { my ($self, $name, $type) = @_; $car_by_type{$name} = $type; } package Car::BMW; our @ISA = qw/Car/; Car::BMW->REGISTER_TYPE('Z1', __PACKAGE__); Car::BMW->REGISTER_TYPE('Z3', __PACKAGE__); package main; my $z1 = Car->new(name => 'Z1');

    (untested)

      I do appreciate your reply. You said Car->new, then had a means of figuring out it was a BMW, so I'll take that as a + vote (not literally, of course).

      Got your caution about circular dependancies. This seems at least to be a limited breach of said contract. Just call Car->new once, then forevermore know that the returned object is typed according to your specifications. No harm in that? <grin>

Re: Inheritance: parent class determine which child class to use?
by tobyink (Abbot) on Jul 30, 2012 at 19:54 UTC

    Perhaps at least tangentially of interest: Perils of Plugins.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: Inheritance: parent class determine which child class to use?
by GrandFather (Cardinal) on Jul 30, 2012 at 20:35 UTC

    You are looking something rather like the (appropriately, given the example) factory pattern. The "method" used to create an instance is the factory.

    The following is based on Moritz' sample, cleaned up and tested: :-)

    use strict; use warnings; package Car; my %carByType; sub new { my ($class, %opts) = @_; return bless \%opts, $class; } sub factory { my (%opts) = @_; die "Don't know how to make a new car" if !exists $opts{name} || !exists $carByType{$opts{name}}; $carByType{$opts{name}}->new(%opts); } sub describe { my ($self) = @_; my ($make) = ref ($self) =~ m/^Car::(.*)/; print "$make $self->{name}\n"; } sub REGISTER_TYPE { my ($type, $name) = @_; $carByType{$name} = $type; } package Car::BMW; our @ISA = qw/Car/; Car::BMW->REGISTER_TYPE('Z1'); Car::BMW->REGISTER_TYPE('Z3'); package main; my $z1 = Car::factory(name => 'Z1'); $z1->describe();

    Prints:

    BMW Z1
    True laziness is hard work

      Thanks! I made some modifications and used the idea. Mine turned out something like:

      1 package Local::Multipath; ... 45 my %mpByType; 46 47 sub new { 48 my ($class, %opts) = @_; 49 return bless \%opts, $class; 50 } 51 52 sub factory { 53 my (%opts) = @_; 54 croak ("Missing parameters!") unless exists $opts{co}; 55 my $type = _whichType; 56 my $module = "Local::Multipath::$type"; 57 { 58 eval "use $module"; 59 } 60 croak "Failed to load module $module: $@\n" if $@; 61 $mpByType{$type}->new (%opts); 62 } 63 64 sub REGISTER_TYPE { 65 my ($type) = @_; 66 my ($name) = $type =~ m/Multipath::(.*)/; 67 $mpByType{$name} = $type; 68 }
      ...
      1 package Local::Multipath::DMMP; 2 use strict; 3 use warnings; ... 7 8 REGISTER_TYPE Local::Multipath::DMMP; ...
      ... somewhere else in code land ...
      my $mp = Local::Multipath::factory (co => $conf);

      The _whichType function returns 'DMMP' or 'Powerpath' after sniffing around for what we actually have.

      It's nearly the same as what you suggested, but I didn't need the added 'type' attribute in this case, so simpler.

      I could almost get away without the REGISTER_TYPE, but it has the added appeal of not having to hard code the things Multipath can handle. Just add a new module like the DMMP module shown to add new capabilities;

      Thanks for the suggestion: perfect synergy.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (16)
As of 2014-08-27 15:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (242 votes), past polls