Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

How to sub class-es

by exilepanda (Pilgrim)
on May 15, 2020 at 02:54 UTC ( #11116797=perlquestion: print w/replies, xml ) Need Help??

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

For example, I have Robo class, which subclass from Robo::Arm Robo::Head Robo::Feet at once ( and each has it's own new() constructor ).

After lot of readings, I learned I can use Exporter and then push @ISA, or use base, or parent to obtain methods from other class(modules).

However, if a method is not solely depends on themselves, I mean, the being called method require attributes defined in the class's own constructor to work properly, the tricks of above will not work. The result is that I can access the method, but the method won't work properly.

Is that any trick that you can kick on the constructor ahead, and then calling those parent methods with correct object reference? Thank you very much for any clue.

----------------------------------

Update: Thank you everyone that point out and I am agree that it is not an is-a scenario, but has-a, which is very likely what I am asking, but I have to point out that I do not have enough time to learn Moose at this moment.

Though, here's some code I can demonstrate.

package Robo::Arm; sub new { bless { } , shift } sub pick { print "I am grabbing " . $_[1] } 1; package Robo::Feet; sub new { bless { Position => [11,0] } , shift } sub walk { my $c = shift; my ( $x, $y ) = @{$c->{Position}} ; if ( $x > 10 || $y > 10 ) { print "Out of boundary. I stop walk" } else { print "I start walk from pos $x $y " } } 1; package Robo; use parent -norequire , 'Robo::Arm', 'Robo::Feet'; sub new { bless { Arm => new Robo::Arm(), Feet => new Robo::Feet(), }, shift } 1; package main; my $robo = new Robo; $robo -> pick ( "Apple" ) ; $robo -> walk ;
Now, look at the walk from Feet, that's what I mean, I can access the method, but the method won't run as expected. I expect it won't walk if the init value was considered. So, I have a very clumsy work around

package Robo::Arm; sub new { bless { } , shift } sub pick { print "I am grabbing " . $_[1] } 1; package Robo::Feet; sub new { bless { Position => [11,0] } , shift } sub walk { my $c = shift; my ( $x, $y ) = @{$c->{Position}} ; if ( $x > 10 || $y > 10 ) { print "Out of boundary. I stop walk" } else { print "I start walk at pos $x $y " } } 1; package Robo; sub AUTOLOAD { our $AUTOLOAD ; $AUTOLOAD =~ /([\w]+)$/; my $meth = $1; my $c = shift; my $can = undef; foreach ( keys %$c ) { if ( $c->{$_}->can ( $meth ) ) { eval "\$c->{\$_}->${meth} (\@_) " ; $can++ } } die "There's no $meth method anywhere" unless $can; } sub new { bless { Arm => new Robo::Arm(), Feet => new Robo::Feet(), }, shift } 1; package main; my $robo = new Robo; $robo -> walk ; $robo -> pick ( "Apple" ) ;

And that's what I am trying to ask a better way to do

Replies are listed 'Best First'.
Re: How to sub class-es
by Fletch (Chancellor) on May 15, 2020 at 03:44 UTC

    Your question's a bit vague so I may be off chasing untamed ornithoids here; you might provide some more concrete, actual code and get a more relevant answer. That caveat aside . . .

    It may be a side effect of your example wording but that almost sounds more like you want to model a has-a relationship rather than is-a. A Robo has an Arm, Feet, and a Head (of whatever cardinalities); but the whole in and of itself isn't a more specialized type of Arm. You'd then set up some sort of delegation on the Robo instance so that when you call aa Arm method it calls that method on the Arm instance belonging to it instead. If you're using Moose or the like there's going to be a handles declaration which you can say that calls to some set of methods should be forwarded to be called on the value in that slot. See Moose::Manual::Delegation for more details on wiring that up.

    You then don't need to worry about chaining superclass constructors and what not, you just create your part instances and pass those components to the containing class' constructor.

    Edit: Tweaked wording a bit. This SO thread (specifically the first answer's examples) may make more clear what I'm saying WRT inheritance versus composition.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Thank you so much for remind me that it is not an is-a condition, but has-a. Totally agree with that. Perhaps I should ask "how to do has-a correctly" =) And I've added the code into my post.
Re: How to sub class-es
by kcott (Bishop) on May 15, 2020 at 07:21 UTC

    G'day exilepanda,

    Firstly, I concur with ++Fletch's response. What follows is additional information.

    "... I have Robo class, which subclass from Robo::Arm Robo::Head Robo::Feet ..."

    I may be misinterpreting your intent; however, I think you may want an OO structure something like this.

    • Robo - a top-level class implementing generic functionality.
      • Robo::Arm - a subclass of Robo which inherits generic functionality from Robo and implements specific functionality required by Robo::Arm objects.
      • Robo::Head - a subclass of Robo which inherits generic functionality from Robo and implements specific functionality required by Robo::Head objects.
      • Robo::Feet - a subclass of Robo which inherits generic functionality from Robo and implements specific functionality required by Robo::Feet objects.
      • Other subclasses of Robo which may become apparent as development progresses or for subsequent extension, e.g. Robo::Body. These would fit into the OO structure in the same way as the three subclasses already mentioned.
    • Robot - a separate top-level class which composes or aggregates (has-a relationships) Robo::* subclasses. This may be more involved than that; however, your OP description doesn't provide sufficient information to speculate further.

    I recommend you read "perlintro: OO Perl" and follow the links therein. In particular, perlootut provides, amongst other things, definitions of inheritance and composition.

    "... I learned I can use Exporter ... to obtain methods from other class(modules)."

    You don't say where you learned that: it's poor advice. If you look in the "What Not to Export" section of Exporter, you'll see the very first item in the list reads:

    "method names (because you don't need to and that's likely to not do what you want),"

    If you are hand-crafting all of your classes — which, unless it's for a learning exercise, I don't recommend — the parent pragma would probably be the way to go (note that the base pragma is generally discouraged).

    Take a look at "perlootut: PERL OO SYSTEMS". You haven't supplied enough information for a recommendation; perhaps try Moose and Moo first.

    "However, if a method is not solely depends on themselves, I mean, the being called method require attributes defined in the class's own constructor to work properly, the tricks of above will not work. The result is that I can access the method, but the method won't work properly."

    I don't understand what you're trying to describe in that paragraph. Showing some code pointing out what you want versus what you're getting would help greatly. Take a look at SSCCE.

    "Is that any trick that you can kick on the constructor ..."

    After following the advice above, you may find this is unnecessary. If you still need it, how it is achieved will depend on the code you use. I'm very much guessing here but, if you chose Moose, then something documented in "Moose::Manual::Construction: OBJECT CONSTRUCTION HOOKS" would likely be what you're after.

    — Ken

Re: How to sub class-es
by GrandFather (Sage) on May 15, 2020 at 07:52 UTC

    Since we are all making wild guesses as to what you actually want to achieve I thought I'd add my own version.

    If you are simulating or writing a control system for a robot (it comes to much the same thing) then Robo should be a container for a collection of RoboParts derived objects. Robo probably isn't a RoboPart, but that depends a bit on what your end game is. If the Robo instance takes external instructions from a console then it may well benefit from deriving from RoboPart. If instead it is a self contained instance that manages the various RoboPart objects then it doesn't derive derive from RoboPart.

    Maybe you'd like to tell us what you want to achieve and not how you think yo might best achieve it? Then we can help you get the class structure right to support your objective.

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      If talking out of the code, I'd take Robo as a control point. For example, if left foot steps forward, the right arm moves forward too, to balance the body weight. But under the arm itself, if certain angle is reached, it stops, and I will leave it to Robo::Arm handle by itself. Back to the code, I've updated to my post. Any advise for me? Thx!

        So my wild guess was actually pretty close to the mark. You don't need Moose for a has-a relationship, a simple array or hash is essentially that. You are back at the container of RoboPart derived objects I mentioned above. Your Robo object then is just a container that knows how to send messages between the different parts and tosses out the odd message of its own. Consider:

        use strict; use warnings; package RoboPart; sub new { my ($class, %options) = @_; return bless \%options, $class; } sub dispatch { my ($self, $verb, @params) = @_; my $handler = $self->can($verb); die ref $self . " can't $verb\n" if !defined $handler; return $handler->($self, @params); } sub does { my ($self, $verb) = @_; return $self->can($verb); } package RoboArm; use parent -norequire, 'RoboPart'; sub pick { my ($self, $target) = @_; print "I am grabbing $target\n"; } package RoboFeet; use parent -norequire, 'RoboPart'; sub walk { my ($self) = shift; my ($x, $y) = @{$self->{Position}} ; if ( $x > 10 || $y > 10 ) { print "Out of boundary. I stop walk\n"; } else { print "I start walk from pos $x $y\n"; } } package Robo; sub new { my ($class) = @_; return bless { Arm => RoboArm->new(), Feet => RoboFeet->new(Position => [11, 0]), }, $class; } sub send { my ($self, $verb, @params) = @_; for my $part (values %$self) { $part->dispatch($verb, @params) if $part->does($verb); } } package main; my $robo = Robo->new(); $robo->send("pick", "Apple"); $robo->send("walk");

        Prints:

        I am grabbing Apple Out of boundary. I stop walk
        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: How to sub class-es
by haukex (Bishop) on May 15, 2020 at 05:57 UTC

    I agree with Fletch that you'd have to show us an SSCCE for us to comment on what might be going wrong in your case. Subclassing generally works fine in Perl if you use the appropriate patterns and idioms. Note that Exporter has nothing to do with OO and should normally not be used in OO classes, and parent should be used instead of base unless you know for certain why you need the latter.

Re: How to sub class-es
by bliako (Prior) on May 15, 2020 at 11:32 UTC

    Warning: written before I saw you updated your question.

    I think what you are asking is: what happens to the list of attributes in $self ( where $self is something like this: $self = {... attributes ...}; bless $self => $class;) when you have multiple inheritance and therefore multiple constructors?

    There is no magic solution, but your life will be easier if the parent classes have a separate init() method to initialise, rather than initialising (i.e. filling $self with attributes - among other things) in the constructor.

    This allows you to create your own $self, and then pass it to each parent class' $self->init() to be filled with that class' attributes. And then to the next parent class, etc. This, of course, requires that all parent classes use different names for attributes so that one does not overwrite attributes of the other.

    That's a lot to ask. I use OOP whenever possible but only 1 in 100 times I will write a class thinking that it may be a parent in a multiple inheritance scenario (meaning: I wrote only 99 classes until now and not a single one of this flavour yet!). And only 1 in 10 I have a separate init(). (laziness, bad planning, oversight)

    In other words, if the set of classes you want to do multiple inheritance from are designed with multiple inheritance in mind, then your task is easy. Just create your $self, bless it with your child class as normal and then pass it in turn to each of your parent classes' init(). More-or-less. And add them to the ISA as you say in order to inherit their methods. Which again should not overwrite each other! And if they do, then ISA order is important to determine what overwrites what.

    With the above method it is very easy to introduce subtle bugs if a parent class suddenly changes a name or introduces private attributes which all of a sudden clash with other attributes and overwrite them. You will get no warning I am afraid.

    So, better keep each animal to its own cage. Which is the other choice you have: what others already suggested: create different objects for each of your parent classes and store them in your current class, separately. If you want to move a leg, you do $self->leg()->move(). Which seems pretty logical and intuitive to me rather than inheriting from both a Leg and an Arm and then calling $self->move(). That is meaningless right? But perhaps your usage is different.

    An addition to the above, not in my personal taste, is to hide from user the chaining of methods and allow $self->move_leg() (where sub move_leg { shift->leg()->move() }

    This book (Pro Perl by Peter Wainwright) explains it well: https://books.google.com/books?id=1bbjLxkBLaMC&pg=PA760&lpg=PA760 - and helped me in writing this.

    p.s. OOP is not panacea although it sounds like a good idea and in many cases it is, super really. In lot of other cases it does not come near reality and "naturality" unless you solve unsolvable dilemmas like the one faced above. Which I am sure billions of books have been written about. On the other hand, re-designing is a good tool too.

    5' Edit after I read your updated question. Perhaps in your constructor you can enquire each of the "parent classes" about their methods and alias them to your own package unless there is a naming clash. Something like this - (untested) - which is what you are trying to do in AUTOLOAD(?) but it automatically finds the parent methods:

    package Robo; use Class::Inspector; sub new { my $self = { Arm => new Robo::Arm(), Feet => new Robo::Feet(), }; bless $self => $class; for my $aparent (qw/Arm Feet/){ my $obj = $self->{$aparent}; for my $amethod (@{Class::Inspector->methods( $aparent, 'full', +'public' )}){ if( $self->can($amethod) ){ warn "method '$amethod' already ex +ists!!!!"; next } # this is totally pseudo-code and inject_method_into_self() # needs to be implemented but the spirit is obvious: inject_method_into_self($amethod, "new-name", sub { $self->{$a +method}->$amethod() } ); } } return $self }

    bw, bliako

Re: How to sub class-es
by 1nickt (Abbot) on May 15, 2020 at 14:30 UTC

    Hi, there are many great classical OO-design responses above.

    I would use Moo (never Moose!) and Roles. (You can also apply Roles to any class or object using Role::Tiny.

    Kills me but no time now to show an SSCCE for robots, but there are plenty of great examples around. I recommend SuperSearching this site for examples of Moo usage provided by tobyink (many use farm animals or humans, but I should think you can easily frobnicate that schema to your own :-) )

    Hope this helps!


    The way forward always starts with a minimal test.
Re: How to sub class-es
by CountZero (Bishop) on May 16, 2020 at 12:32 UTC
    but I have to point out that I do not have enough time to learn Moose at this moment
    I'm afraid you will soon find out that not learning an OO-framework (such as Moo) but rolling your own was a waste of time after all.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

    My blog: Imperial Deltronics
      I'm afraid you will soon find out that not learning an OO-framework (such as Moo) but rolling your own was a waste of time after all.

      Moo and friends have limitations and bring added complexity that not everyone may want. c.f. "Moose Made me a Bad OO Programmer" by Tadeusz Sośnierz

      I've done fine for a medium size project using Object::Tiny to create accessors, and Role::Tiny to separate out functionality of large ("God") classes.

Re: How to sub class-es
by perlfan (Vicar) on May 19, 2020 at 08:34 UTC
    Check out Conway's Object Oriented Perl. And for God's sake I hope you never find the time to learn Moose or Moo.
    package Robo; use strict; use warnings; sub new { bless { Arm => Robo::Arm->new(), # 'Arm' points to instance of Robo +::Arm Feet => Robo::Feet->new(), #'Feet' points to instance of Rob +o::Feet }, shift } 1; package main; my $robo = Robo->new(); $robo->{Feet}->walk(); $robo->{Arm}->pick( "Apple" );
    If you want to have walk and pick methods attached to Robo instances, then you have to create these as subroutines in package Robo.
    sub walk { my $self = shift; $self->{Feet}->walk(); return; } sub pick { my ($self, $thing) = @_; return $self->{Arm}->pick( $thing ); }
    Maybe that's what you're trying to do with AUTOLOAD, but my advice is to do it manually before you start trying to fold in methods from member classes using AUTOLOAD.

    Also, new X is not idiomatic and I am surprised still works in some cases. Do X->new(..) and don't look back.

      ++ for the simplicity of this answer. It answers the question at hand without going too much further. I wouldn't recommend this if you're trying to abstract out the robot's specific loadout (i.e. if you want to be able to use a robot instance without having to know if it has arms) but that's not what the question asked.
Re: How to sub class-es
by JediMasterT (Sexton) on May 21, 2020 at 12:39 UTC

    I think you've got plenty great and actually-useful answers to your question, so I'm gonna overthink it for a sec. You've already figured out that this is a "has-a" situation, so we can lean into that. I submit that you have a "RobotPart" class with the method "Register", meant to return a hash table of functions and names. You have a "RobotDelegate" class that collects all of these parts, registers them all, and houses the combined hash tables.

    Unfortunately I have to go and can't type out a demo of what I'm thinking, but hopefully I got the point across. Some things to thing about:

    • Do you want parts to call for other parts? (e.g. a hand asking a foot to move to a new place).
    • Do you want parts to have their own parts? (e.g. arms have hands, hands have fingers).
    • How will you make more complex actions. If, for instance, you want to make a "go get" function that walks to somewhere and then picks something. You might need a "RobotController" class to coordinate between different parts
    • Does this body had cardinality? you might need placement on the body

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (7)
As of 2020-11-30 12:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?