Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Re: Names of attribute accessors (Moose)

by Athanasius (Archbishop)
on Oct 23, 2015 at 13:33 UTC ( [id://1145758]=note: print w/replies, xml ) Need Help??


in reply to Names of attribute accessors (Moose)

Hello v_melnik,

I’m having trouble understanding your OO design. In part, I suspect it’s a problem of terminology. For example, you say:

I'm passing the reference to the parent object to the child object...

But in OO there’s no such thing as a “parent object” or a “child object.” There are parent and child classes, related by inheritance, but the inheritance relation does not extend to objects. For example, if you had a parent class Dog and a child class Labrador_Retriever, you could create two objects:

my $rex = Dog->new(name => '$Rex'); my $fido = Labrador_Retriever->new(name => 'Fido');

but it would be wrong to refer to $rex as the “parent” of $fido in this case. Actually, it’s also usually a bad design to allow a parent class like Dog to be instantiated at all. Dog should be an abstract base class.

In Perl, there’s an additional source of confusion in the naming scheme of the module hierarchy. For example, if you use two classes like this:

use Foo; use Foo::Bar;

it seems natural to assume that Foo::Bar is a child class of Foo. But that needn’t be the case; Foo::Bar may be completely unrelated to Foo. The package name Foo::Bar simply tells Perl to search for the Bar package in a directory named Foo. To create an inheritance relationship (in Moose) you would normally use extends:

package Foo::Bar; use Moose; extends 'Foo';

Now to the real issue: inter-object communication. You write:

I'm passing the reference to the parent object to the child object, because I need the child to have access to the parent and its methods & accessors.

Why? If there’s a true inheritance relationship, then a child class object already has access to the methods in the parent class. In all other cases, communication between objects should be via the public interfaces of those objects. That — encapsulation — is a large part of what OO is all about! And if you have to subvert encapsulation to a large extent (as your use of attributes appears to be trying to do), that would tend to indicate a basic flaw in the OO design.

But I apologise if I’ve misunderstood the design issue behind your question. If so, will you please explain why standard, public-interface-based communication between objects is inadequate to your requirements? Also, exactly what problem is the attribute-based design you’ve outlined intended to solve? Try to provide a short, self-contained example showing communication between two or three objects.

Hope that helps,

Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Replies are listed 'Best First'.
Re^2: Names of attribute accessors (Moose)
by v_melnik (Scribe) on Oct 25, 2015 at 11:31 UTC

    Thank you very much for your comment!

    I'll try to explain why exactly I need to have access to the object that has initialized the current object (that's what I mean when I call it "the parent object"). For example, let's imagine we have some class for the jobs-queue runner (let's call it SomeFramework::JobsQueue::Executor) and some class for jobs. Is it bad to do something like this:

    package SomeFramework::JobsQueue::Executor; use Moose; use MooseX::Params::Validate; has queue { isa => 'SomeFramework::JobsQueue', required => 1, reader => 'get_queue', writer => '_set_queue' } # This attribute is being set by the framework when the framework # creates the SomeFramework::JobsQueue::Runner-based object sub execute { my($self, $job, $options) = validated_hash( \@_, job => { isa => 'SomeFramework::JobsQueue::Job' }, options => { isa => 'HashRef' } ); my $queue = $self->get_queue; $queue->mark_as_running($job->get_id); $job->execute(options => $options); $queue->mark_as_completed($job->get_id); }

    ? So, the queue-runner object is aware about the queue object it "belongs" to, so it can call some methods of the queue object.

    Or let's look at much more simple example:

    package SomeFramework::SomeSubsystem; use Moose; has 'some_framework' => { isa => 'SomeFramework', required => 1, reader => 'get_some_framework', writer => '_set_some_framework' } sub some_method { my $self = shift; $self->get_some_framework->get_logger->log_trace("Hello, world!"); }

    So, our object knows how to call the framework's object that has initialized that object, so it can call some methods of the framework's object and even some methods of other objects initialized and stored by the framework's object.

    If it's really bad, would you be so kind as to help me to understand why? Thank you!

    V.Melnik

      Ok, now I’m a little less confused. :-) “Parent/child” refers to an inheritance (ISA) relationship; but the design you are describing uses HASA relationships between objects. So far, so good.

      Now, let’s look at the simple example:

      sub some_method { my $self = shift; $self->get_some_framework->get_logger->log_trace("Hello, world!"); }

      The problem with this is that it breaks encapsulation. Specifically, SomeFramework::SomeSubsystem::some_method has to know not only the public interface of the SomeFramework object (which it owns), but also the private implementation detail that a SomeFramework object provides logging via a SomeFramework::Logger object. (Otherwise it won’t know how to call the log_trace method.)

      What if you later decide to change the way SomeFramework implements trace logging? You will have to change the method calls in classes such as SomeFramework::Subsystem, which ought not to be affected by such details. That’s a poor design, because it makes the code brittle. Better to access a single SomeFramework method and let the SomeFramework class delegate its implementation. For example, if file “SomeFramework.pm” is changed as follows:

      # SomeFramework.pm package SomeFramework; use Moose; use MooX::ProtectedAttributes; protected_has logger => ( is => 'ro', isa => 'SomeFramework::Logger', ); sub log_trace { my ($self, $msg) = @_; $self->logger->log_trace($msg); } 1;

      then SomeFramework::Subsystem::some_method can be defined like this:

      sub some_method { my $self = shift; $self->get_some_framework->log_trace("Hello, world!"); }

      The goal is for each class to keep its implementation details private and to expose only a public interface to its “clients.” (In a HASA relationship, the “owner” object is a client of the objects it owns.) This promotes encapsulation by de-coupling classes as much as possible. The end result is a design which is simpler, clearer, more flexible, and easier to maintain.

      Hope that helps,

      Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Thank you for the explaination!

        In my case it seems to be okay to call methods of objects whose references are accessible as attributes of some "upper" objects. If all these classes are well-documented, it shouldn't lead to any difficulties.

        By the way, Mojolicious has the same apporach, you can often see something like "my $host = $c->req->url->to_abs->host;" in the code of Mojolicious-based applications.

        V.Melnik

Log In?
Username:
Password:

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

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

    No recent polls found