I have a basic question which I'm sure was discussed, but I'd like to see contemporary opinions about. As mine change often I'll asume others' / style do too, and would like to get a window on the current trend, if you may.


When writing object oriented code it is considered good style to assume that someday the class will be inherited. Should the inheriting class redefined the constructor, new trouble arises.

A minimal example would be
package Person; sub new { bless { name => $_[1], job => $_[2] }, $_[0] } sub name { $_[0]{name} } sub job { $_[0]{job} } package Person::Resumefied; sub new { bless { name => $_[1], jobs => [ @$_[2] ] }, $_[0] } sub jobs { @{$_[0]{jobs}} }; sub job { $_[0]{jobs}[$#{$_[0]{jobs}}] } # the last job is assumed cur +rent
This way the original job method breaks, because the structure of the object has changed. But it's not in use, so that doesn't matter. But if we define a new method, which relies on the value of job:
package Person; # ... other methods and such sub cool { not $_[0]{job} =~ /programmer/ };
then calling the method cool, which needs the value of job, for a Person::Resumified object would break. As method calls are notorious for their speed it is not easy to find the method cool implemented so that it relies on the method job, and not on the hash value in the object:
sub cool { not $_[0]->job() =~ /programmer/ };
But (currently) i think this is better to do, especially if you're writing code which you intend to build on later. It is a safer way of working, obviously, but comes with a price - subroutine calls are slow in perl, and method calls are slower, not just known as such:
nothingmuch% perl use Benchmark qw(cmpthese); our $self = Person->new(); cmpthese(10_000_000,{ direct => 'my $var = $self->{job}', method => 'my $var = $self->job()', }); package Person; sub new { bless { job => 'not a programmer' },shift }; sub job { $_[0]{job} }; Benchmark: timing 10000000 iterations of direct, method... direct: 10 wallclock secs ( 9.70 usr + 0.00 sys = 9.70 CPU) @ 10 +30927.84/s (n=10000000) method: 39 wallclock secs (39.57 usr + 0.00 sys = 39.57 CPU) @ 25 +2716.70/s (n=10000000) Rate method direct method 252717/s -- -75% direct 1030928/s 308% --
That's kinda... err.. bumming.

If you write production code where one method may be used in tight loops, and that method uses other values from within the object, which way would you choose to use?

Is it safe to assume that under heavy usage, say, a popular website driven by some cgi, the safe way of doing stuff will not cost more, or reduce responsiveness?

Thanks in advance,

Update: theorbtwo quoted my typo, and thus I've noticed and fixed it.

-nuffin
zz zZ Z Z #!perl

Replies are listed 'Best First'.
Re: OOP safety
by chromatic (Archbishop) on Apr 19, 2003 at 21:49 UTC

    This is a balance between three things, arranged in order of increasing importance..

    • Performance
    • Ease of coding
    • Ease of maintenance

    Given a goal, a profiler, and a little bit of smarts, it's easy to make a simple program faster. It's usually harder to make a complex program more maintainable.

    In a CGI environment, unless you're doing something really complex that requires hundreds of method calls in a tight loop, your bottleneck will probably instead be the cost of forking and execing the program.

Re: OOP safety
by theorbtwo (Prior) on Apr 19, 2003 at 22:17 UTC

    You say it all in your last (body) paragraph:

    Is it safe to assume that undef heavy usage, say, a popular website driven by some cgi, the safe way of doing stuff will not cost more, or reduce responsiveness?
    Under heavy usage, when speed is important, don't use objects when they don't clear things up by a large bit, like here. Instead, use hashrefs, which are much cheaper then either a method or function call. However, your apositive gives you away. Don't assume that's a matter of your CGI being slow. And even if it is, don't assume that the best way to do is to make the script harder to read. Instead, going to mod_perl will have more benifit for lest cost.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Re: OOP safety
by perrin (Chancellor) on Apr 19, 2003 at 23:30 UTC
    Safer is always slower. You have the same issue in other languages like Java, where internal methods could access properties directly or call other methods.

    That issue is neither here nor there. You had to run 10 million method accesses to get an amount of time that was reasonable to measure. That's not a performance problem. My rule of thumb is that classes are allowed to access properties directly within their own class only. An inheriting class should use accessors.

    The real problem that you're stumbling on here is that it's basically not possible to inherit from a class in Perl without understanding the data structures of the class you're inheriting from, at least enough to avoid stepping on them. That is an annoyance, but hasn't kept me from using OO in Perl. I rarely use inheritance, and when I do it is usually between two classes that I know well.

Re: OOP safety
by Abigail-II (Bishop) on Apr 20, 2003 at 00:09 UTC
    The fault in your example lies entirely by the package package Person::Resumefied. The code as given has only one way of setting the job attribute, and that's by calling the subroutine Person::new. Your subclass is taking over, it's constructor is defining all the objects attributes. It's wrong to depend on a superclass to have access to the attributes if you take control yourself.

    Of course, the entire idea of doing OO programming, and scribble all your attributes in a share hash is utterly flawed, and is as bad as using nothing but global variables, no namespaces and certainly no strict, but that's a rant I've given many times already.

    Abigail

Re: OOP safety
by castaway (Parson) on Apr 20, 2003 at 10:43 UTC
    Maybe one shouldnt rely on it, but I'd hope that anybody writing a subclass of one of my classes would be calling $self->SUPER::new(@_) and so at least initialize my class the way it should be, instead of just overwriting the 'new' method. Theres not much use in inheriting, if you're not using what the module provides. Of course, that doesn't stop the overwritten method killing the contents of the object, there are ways to hide them, if you're that worried about it (theres a lovely chapter in the Camel about objects :)..
    I guess you either hide everything, document properly, or do it another way.. As the book says, Perl doesn't lock you out, it just hopes you're polite enough not to wander into its space. (or something similar..)

    C.

      ++! At the very least, the base class should be setting things up for its children. Also, imho, a base class should also provision for children to add attributes without the child having to know how they're being added. Maybe, some method the child can call in either new or in some bare-block that will add an attribute and mutator or complain if it's a redefinition?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: OOP safety
by zby (Vicar) on Apr 20, 2003 at 07:12 UTC
    Person::Resumefied could just use another hash field for it's job attribut. I think this is a more direct version of what perrin and Abigail-II meant. Of course by encapsulation you are not supposed to know what fields are already taken by the Person class. So the real problem stems from the fact that Perl objects don't have private attributes.