Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

object oriented Perl advice / constructor creation in child class

by smarthacker67 (Beadle)
on Jul 10, 2018 at 07:33 UTC ( [id://1218223]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,

I have my lib structure as follows. I have created every module file to follow Object-oriented concepts (OOPs).

Now I wish to inherit every method by package A, B, C & D as they are needed always to interact with the app.

I have strong feeling that the way I am creating the constructor in C1.pm is definitely not followed by PERL/OOPs.

In the end, I would like to do:- if I create an object for C1 it should (inherit and) give me access to all the methods available A, B, C, D

I am seeking for advice on how this should be achieve in more perlish way taking into OOP's in consideration.

lib/ | ----A.pm #has method new | ----B.pm #has method new | ----C.pm--- #has method new | | | ----C1.pm #has method new | -----D.pm #has method new #############################################
Package C1; use File::FindLib 'lib'; use A; use B; use C; use D; sub new { return bless({ _a => A->new->bar, _b => B->new->bar1, _c => C->new(), _d => D->new(), }, shift); } sub foo { my ($self) = (@_); my $a = $self->{_a}; my $b = $self->{_b}; my $c = $self->{_c}; my $d = $self->{_d}; $a->some_method1(); { .... #set of operations } } sub abc { my ($self) = (@_); my $a = $self->{_a}; my $b = $self->{_b}; my $c = $self->{_c}; my $d = $self->{_d}; $c->some_method2(); { .... #set of operations } }

Replies are listed 'Best First'.
Re: object oriented Perl advice / constructor creation in child class
by haj (Vicar) on Jul 10, 2018 at 09:09 UTC

    Well, your example doesn't actually show inheritance. Your constructor creates a "has-a" relationship, because your C1 objects will have one object of each of the classes A, B, C and D as attributes. This is a solid pattern in many cases, but it doesn't give you direct access to the methods of the four classes.

    I'd say that today's perlish way to do OOP is Moose or its lightweight cousin Moo, and I definitely recommend those if you have several classes to inherit from. Here's a short example:

    # A.pm package A; use 5.014; use Moo; sub say_hello { my $self = shift; my ($name) = @_; say "Hello, $name!"; } 1;
    # C.pm package C; use 5.014; use Moo; has 'name' => (is => 'rw'); sub say_goodbye { my $self = shift; say 'Goodbye, ', $self->name, '!'; } 1;
    # C1.pm package C1; use 5.014; use strict; use warnings; use Moo; extends 'C'; use A; has '_a' => (is => 'ro', default => sub { A->new() }, handles => [qw(say_hello)] ); 1;
    Now you can do things like this:
    use strict; use warnings; use C1; my $c = C1->new(name => 'John Doe'); $c->say_hello('World'); $c->say_goodbye;

    So, your C1 objects have direct access to both the say_hello method from A.pm, and the say_goodbye method from C.pm.

    • Access to say_goodbye is done with inheritance, by the extends keyword of Moo. With inheritance you have access to all attributes and all methods of the parent class, so if you have more than one parent, you need to take care for possible conflicts.
    • Access to say_hello is achieved with method delegation. In that case, the A object needs to be created and has its own attributes, and the package must be loaded. On the plus side, you have control which methods of A you want to expose through the public interface of C1, and there's no problem with conflicts. You can, of course, always call the methods of A indirectly with $c->_a->say_hello('World').

    Note that Moo (and Moose) will take care for object constructors: You don't write new methods!

    If you want to do object inheritance without an object framework, you do it like this:

    package C1; use parent (qw(A B C D));
    However, in that case you need to take care for calling parent constructors and conflict resolution yourself, which is why I don't expand on this.
      Yes, I agree its time to use Moose.

      My wild guess says

      Tie::Hash might be something I can utilize here but since never used before so finding it difficult use. Can Tie::Hash be used ?

        Sorry, but I don't see how Tie::Hash would help you here.

        In another reply you write that all your classes A to D already exist and have their own new methods, so Moo(se) inheritance (which makes Moose classes inherit from other Moose classes) isn't easily done either. As others have explained, Perl's method resolution with mro might help to some extent, but I doubt that it is worth the trouble. In any case you need to understand which of the methods you want to have available through the interface of C::C1 objects, especially with regard to their new methods.

        On whatever I've seen so far, I'd go for the delegation approach. The the existing classes don't have to be Moo(se) classes, and you explicitly implement the methods you want to make available through your C::C1 objects, and you can decide who should construct your A to D objects.

        package C::C1; # That's the perlish name for the module use File::FindLib 'lib'; use A; use B; use C; use D; sub new { return bless({ _a => A->new->bar, # I don't understand bar here _b => B->new->bar1, # nor bar1 here _c => C->new(), _d => D->new(), }, shift); } # now for any method of A: sub a_method_1 { my $self = shift; $self->_a->a_method 1(@_); } # etc # And for any method of B: sub b_method_1 { my $self = shift; $self->_a->b_method 1(@_); } # etc # Repeat for C and D, ad libidum.

        Maybe you want your users to read/write A to D? Here's an example for D:

        sub new_d { my $self = shift; if (@_) { $self->{_d} = D->new(@_); } return $self->{_d}; }
        Someone using C::C1 would write like this:
        use C::C1; my $c1 = C::C1->new; $c1->a_method_1(); # will execute A::a_method_1 $c1->b_method_1(); # will execute B::b_method_1 $c1->new_d(@params) # will execute $self->{_d} = D->new(@params)

        Moo or Moose can help to make your C/C1.pm more readable, because they have keywords for method delegation, as I've shown in my previous example. But before going deeper, I'd like to understand how the interface of your C/C1.pm should look like.

Re: object oriented Perl advice / constructor creation in child class
by kcott (Archbishop) on Jul 10, 2018 at 09:13 UTC

    G'day smarthacker67,

    While I assume you're just using "A", "B", etc. as examples, this generally isn't a good idea as you can run into name collisions; for instance, B.pm is a core module which you'll already have installed.

    I don't understand where "C1.pm" sits in your hierarchy. It seems to be both a file and directory which is a subdirectory of another file ("C.pm"). Obviously that can't be the case but I don't know what you want: "lib/C/C1.pm"?, "lib/C1.pm"?, something else?

    Here's some skeleton code to show how you might achieve this sort of thing with Moose. This is intended as an academic exercise: there's no suggestion that this is production-grade code; nor that Moose is the best choice for your specific application.

    $ cat lib/A.pm package A; use Moose; has bar => ( is => 'rw', default => 'A-bar', ); __PACKAGE__->meta->make_immutable;
    $ cat lib/C.pm package C; use Moose; sub whos_who { my ($self) = @_; print "whos_who() is in package: ", __PACKAGE__, "\n"; print "\$self is in package: ", ref($self), "\n"; return; } __PACKAGE__->meta->make_immutable;
    $ cat lib/D.pm package D; use Moose; has 'bar' => ( is => 'rw', default => 'D-bar', ); __PACKAGE__->meta->make_immutable;
    $ cat lib/C1.pm package C1; use Moose; extends 'C'; use A; use D; has a => ( is => 'ro', default => sub { A->new } ); has d => ( is => 'ro', default => sub { D->new } ); sub foo { my ($self) = @_; print $self->a, "\n"; print $self->a->bar, "\n"; print $self->d, "\n"; print $self->d->bar, "\n"; $self->whos_who; return; } __PACKAGE__->meta->make_immutable;
    $ cat ./acd.pl #!/usr/bin/env perl use strict; use warnings; use lib 'lib'; use C1; my $c1 = C1::->new(); $c1->foo();
    $ ./acd.pl A=HASH(0x25db7c0) A-bar D=HASH(0x25db880) D-bar whos_who() is in package: C $self is in package: C1

    Update (typo fix): s{lib/C/C.pm}{lib/C/C1.pm}

    — Ken

      Hi Sir,

      Thanks for this brief explanation I would definitely give a try.

      dir structure looks like
      lib/A.pm lib/B.pm lib/C.pm lib/C/C1.pm lib/D.pm
      Wanted to achieve deep inheritance where object of C1 should inherit methods of A,B,C, & D

        dir structure looks like

        ... lib/B.pm ... lib/C/C1.pm ...
        lib/B.pm

        As previously stated, B.pm will already exist on your system. Attempting to write new code with "package B;" will cause you all sorts of problems. You should avoid this name.

        lib/C/C1.pm

        That's fine. The class name will become "C::C1". My previously posted "lib/C1.pm" should be moved to "lib/C/C1.pm", and the first line should be changed to:

        package C::C1;

        In my "./acd.pl", you should change

        use C1; my $c1 = C1::->new();

        to

        use C::C1; my $c1 = C::C1::->new();

        After making those changes, note the difference in the last line:

        $ ./acd.pl A=HASH(0x9897d0) A-bar D=HASH(0x989890) D-bar whos_who() is in package: C $self is in package: C::C1

        The hexadecimal values in "X=HASH(0xhhhhhh)" will likely be different: that's not significant; you could get different values on separate runs using the same code.

        — Ken

Re: object oriented Perl advice / constructor creation in child class
by choroba (Cardinal) on Jul 11, 2018 at 07:15 UTC
    Perl supports multiple inheritance. In case several predecessors provide a method of the same name, Perl uses the "method resolution order", see mro for details.

    Example:

    MyA.pm

    package MyA; use warnings; use strict; sub new { bless { a => 'A' }, shift } sub a { shift->{a} } __PACKAGE__

    MyB.pm

    package MyB; use warnings; use strict; sub new { bless { b => 'B' }, shift } sub b { shift->{b} } __PACKAGE__

    MyC.pm

    package MyC; use warnings; use strict; use parent qw{ MyA MyB }; # Multiple inheritance, MyA takes precedenc +e __PACKAGE__

    script.pl

    #!/usr/bin/perl use warnings; use strict; use MyC; my $c = 'MyC'->new; print $c->a; print $c->b; # uninitialized value

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: object oriented Perl advice / constructor creation in child class
by Anonymous Monk on Jul 10, 2018 at 09:13 UTC

    From a design point of view, this looks like a task for Module::Pluggable. If all your modules A, B, C, D provide similar interface, you could just iterate over $self->plugins in your methods instead of spelling out special cases for A, B, C, D manually. Untested pseudo-Perl example:

    package Synergy; use Module::Pluggable instantiate => 'new'; sub new { my ($class, @opts) = @_; return bless [ $class->plugins(@opts) ], $class; # now your object contains objects of classes A, B, C, D # and whatever else was in Synergy/*.pm near Synergy.pm } sub process { my ($self, @args) = @_; return map { $_->process(@args) } @$self; # passes args to all sub-modules }

    On the other hand, if your modules all provide different methods with no intersection whatsoever and you just need to have all of them in one class, you could try to use base to inherit all their methods automatically. This way you only need to spell out your parent packages once.

    But if you need to call different parent methods from the same method depending on other circumstances... That sounds like a very complex wrapper. Maybe what you are trying to achieve can be achieved in a simpler way?

      Hi Sir,

      Thanks for your answer All these A,B,C & D modules have method new so will that cause some conflicts?

        Yes, calling multiple inherited SUPER::new() constructors would be a problem (I don't think mro would help, either), which is why I asked you for more details about modules you are trying to put together.
Re: object oriented Perl advice / constructor creation in child class
by pwagyi (Monk) on Jul 13, 2018 at 01:54 UTC

    Since you want C1 to inherit from all A,B,C and D, what you actually want may be 'Role' instead of inheritance? Especially if A,B,C,D serve different functionalities. With moose, you can also apply roles to instances.

Re: object oriented Perl advice / constructor creation in child class
by Anonymous Monk on Jul 10, 2018 at 12:17 UTC
    If you want a module C to give you access to methods of objects A, B, and D, all under one roof – which seems strange to me – then your object C would instantiate instances of A, B, and D as private properties, then expose methods (of C) which under-the-hood "reflected" those calls to the various objects that it is stashing. Callers would not perceive this: they would only call methods of C, not knowing exactly how they are implemented. But this seems to contradict the "DRY" principle, since in every method of C you would be "repeating yourself."

      More useless drivel from something called "sundial".

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (2)
As of 2024-04-25 20:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found