Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Philosophy of a "new" method

by rastoboy (Monk)
on Feb 04, 2011 at 03:03 UTC ( [id://886125]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Brothers,

Lately I've really been getting into OO Perl (and OO programming in general) and I'm kindof having a hard time deciding, sometimes, what should go into my "new" method. Should it be a barebones outline of the object? Should it contain useful data per se?

I decided to consult some random cpan modules on my machine. For example, Archive::New:

sub new { my $class = shift; my $self = bless( { 'diskNumber' => 0, 'diskNumberWithStartOfCentralDirectory' => 0, 'numberOfCentralDirectoriesOnThisDisk' => 0, # shld be # +of members 'numberOfCentralDirectories' => 0, # shld be # +of members 'centralDirectorySize' => 0, # must re-compute on write 'centralDirectoryOffsetWRTStartingDiskNumber' => 0, # must re-compute 'writeEOCDOffset' => 0, 'writeCentralDirectoryOffset' => 0, 'zipfileComment' => '', 'eocdOffset' => 0, 'fileName' => '' }, $class ); $self->{'members'} = []; my $fileName = ( ref( $_[0] ) eq 'HASH' ) ? shift->{filename} : sh +ift; if ($fileName) { my $status = $self->read($fileName); return $status == AZ_OK ? $self : undef; } return $self; }
and that seems pretty reasonable, sets up kindof a basic structure with a few checks to see if an object is possible. Fair enough. But then I see a lot of like what HTML::Parser does:
sub new { my $class = shift; my $self = bless {}, $class; return $self->init(@_); } sub init { my $self = shift; $self->_alloc_pstate; my %arg = @_; my $api_version = delete $arg{api_version} || (@_ ? 3 : 2); if ($api_version >= 4) { require Carp; ...
so here we just get a very basic blessing of an empty hashref into the class, and then a sub "init" does some more heavy lifting of actually defining a unique sort of object. But I find myself writing "new" methods much like REST::Google here:
sub new { my $class = shift; my $args = $class->_get_args(@_); croak "attempting to perform request without setting a service + URL" unless ( defined $class->service ); my $uri = URI->new( $class->service ); $uri->query_form( $args ); unless ( defined $class->http_referer ) { carp "attempting to search without setting a valid htt +p referer header"; $class->http_referer( DEFAULT_REFERER ); } my $request = HTTP::Request->new( GET => $uri, [ 'Referer', $c +lass->http_referer ] ); my $ua = LWP::UserAgent->new(); $ua->env_proxy; my $response = $ua->request( $request ); croak sprintf qq/HTTP request failed: %s/, $response->status_l +ine unless $response->is_success; my $content = $response->content; my $json = JSON::Any->new(); my $self = $json->decode($content); return bless $self, $class; } sub new { my $class = shift; my $args = $class->_get_args(@_); croak "attempting to perform request without setting a service + URL" unless ( defined $class->service ); my $uri = URI->new( $class->service ); $uri->query_form( $args ); unless ( defined $class->http_referer ) { carp "attempting to search without setting a valid htt +p referer header"; $class->http_referer( DEFAULT_REFERER ); } my $request = HTTP::Request->new( GET => $uri, [ 'Referer', $c +lass->http_referer ] ); my $ua = LWP::UserAgent->new(); $ua->env_proxy; my $response = $ua->request( $request ); croak sprintf qq/HTTP request failed: %s/, $response->status_l +ine unless $response->is_success; my $content = $response->content; my $json = JSON::Any->new(); my $self = $json->decode($content); return bless $self, $class; }
I mean, they do all kinds of stuff in there, really kindof clearing the decks for action right off the bat.

So...which way is right? (if any of these are)?

Replies are listed 'Best First'.
Re: Philosophy of a "new" method
by CountZero (Bishop) on Feb 04, 2011 at 07:14 UTC
    TIMTOWDI at work!

    All of them are right, or wrong, or perhaps only some of them, sometimes, ...

    It all depends on your programming style and what you want to do with the object.

    Are all your objects perfect structural clones of each other with only the internal data changing? Then go for the "define the whole structure in the new method" format.

    Does the structure and content of the object depend on various external conditions which cannot be determined at creation time of the object? Then provide a new method which returns a bare bones blessed data-structure only.

    These are of course the two extremes: there are many intermediary forms possible, such as using an init-method to create structure and/or provide data for the object.

    Or --my favourite-- use something modern like Moose and don't sweat the details yourself.

    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

Re: Philosophy of a "new" method
by JavaFan (Canon) on Feb 04, 2011 at 07:39 UTC
    Should it be a barebones outline of the object?
    Even that's too much IMO.

    I prefer my new methods to do nothing beside creating a blessed ref and returning it.

    Separating object construction and object configuration makes MI much easier.

    Of course, if you believe in monsters under your bed, you probably also subscribe to the "MI is scary, don't do it!" philosophy. But this is Perl, not Java. Not only is MI possible in Perl, the *language* doesn't place more hurdles in your way than it already does with OO in general. It's just the classical way of "let's have a method that starts with nothing, return a fully configured object" that makes MI harder than it should be.

      Erm, what's MI?

        It's latin short-hand for the work of the devil :)

        I'd go hide under the bed, but there be monsters!


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        Multiple Inheritance.
Re: Philosophy of a "new" method
by wind (Priest) on Feb 04, 2011 at 07:36 UTC

    And there's even a fourth way, by using a Static Factory Method as seen more commonly in Java. The best method for each case is often determined by how likely the class is to be inheritted and have it's new method overridden.

    The majority of the projects I do don't rely on inheritance, so I tend to use Factories and only have a bare bones new method. But to each his own depending on the specific project.

    - Miller

Re: Philosophy of a "new" method
by tospo (Hermit) on Feb 04, 2011 at 10:06 UTC
    I'd second CountZeros suggestion to check out Moose, if only for getting a different perspective on OO programming styles.
    Apart from that, this really is a matter of style and also of the interface you want to your class. For example, you might have a class DataAnalyzer that does some calculations on data from a database. One interface to this could look like this:
    my $da = DataAnalyzer->new(); $da->connect_to_db->($dsn); my $result_set = $da->run_analysis( %parameters_for_analysis );
    But this probably connects to the same database every time, so it might make more sense to initiate the object with the database parameters straight away, i.e. set up the connection in the 'new' method so that the API becomes:
    my $da = DataAnalyzer->new( $dsn ); my $result_set = $da->run_analysis( %parameters_for_analysis );
    Now the 'new' method does a bit moe work (set up the actual connection) but it seems to make more sense from the user's perspective.
    Or, if you find that the new method should return results straight away because it is unlikely that this will ever be run twice with different parameters on the same database anyway. Now your new method will run the analysis and return some sort of result immediately (this may not be the best example but nevertheless...):
    my $result_set = DataAnalyzer->new( $dsn, \%parameters_for_analysis ) +;
    Whether or not you use an '_init' method is really just matter of style: it certainly helps to de-clutter the 'new' method itself and keep it short in cases where a lot of setting-up has to be done.
      Whether or not you use an '_init' method is really just matter of style
      I strongly disagree with an '_init' method just being matter of style. That's like saying "Whether or not a language has hashes is just a matter of style".

      Suppose you have classes with the following new methods:

      package FloorWax; sub new { my $class = shift; bless {FW_massage(@_)}, $class; } package DessertTopping; sub new { my $class = shift; bless {DT_massage(@_)}, $class; }
      And now you need to create something that's both a DessertTopping and a FloorWax, using two corner stones of object oriented programming: code reuse and encapsulation.

      Sure, the start is easy:

      package FloorToppingDessertWax; our @ISA = qw[FloorWax DessertTopping]; sub new { my $class = shift;
      But then what? Without breaking encapsulation, how do you set up the object so it's both a FloorWax, and a DessertTopping? If you call FloorWax->new you get a configured FloorWax object, without the DessertTopping attributes. And if you call DessertTopping->new, you will be missing the FloorWax attributes.

      If instead, you had:

      package FloorWax; sub new {bless {}, shift} sub init { my $self = shift; my %arg = FW_massage @_; @$self{keys %arg} = values %arg; $self } package DessertTopping; sub new {bless {}, shift} sub init { my $self = shift; my %arg = DT_massage @_; @$self{keys %arg} = values %arg; $self }
      Things would be a lot easier. You could write a derived class as:
      package FloorToppingDessertWax; our @ISA = qw[FloorWax DessertTopping]; sub new {bless {}, shift} sub init { my $self = shift; foreach my $class (@ISA) { my $init = "${class}::init"; $self->$init(@_) if $self->can($init); } $self; }

      So, I argue that having or not having an _init method isn't a matter of style. Having an init (not really an _init, as it should be called from the outside) makes your class more (re)usable.

        hmmmmmm, I don't think I agree with that. For a start, you are talking about a public method 'init' instead of the private '_init' that is usually used as a sort of extension to 'new' to do the actual heavy lifting. That in itself is a choice of style isn't it?
        I would also argue (again a matter of preference and style) that FloorWaxDessertTopping might be better off inheriting from a ThingsThatCanGoOnTopOfOtherThings base class if it's parents are so different.
        Or of course you could use Moose and define a role CanGoOnSomething or something along those lines...
Re: Philosophy of a "new" method
by pajout (Curate) on Feb 04, 2011 at 10:06 UTC
    Yes, TIMTOWDI :>)

    I think about such themes in this way:
    Q: What goal I have?
    A: To obtain new object instance in meaningful state.
    Q: How to do it?
    A: Start with simplest new() method and gradually add initial data structures. If it starts to be too chaotic, improve it, for instance using init() inside new(). If it, after some development, does not fit requirements well, develop other mechanism...

Re: Philosophy of a "new" method
by stonecolddevin (Parson) on Feb 04, 2011 at 22:18 UTC
      Awesome, thanks y'all, lots of interesting points all around.

      Very helpful!

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (2)
As of 2024-04-25 20:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found