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

Hi Molks!

I'm just starting to get to grips with the OO features of Perl and i found this basic example on the forums Re: Algorithm::Treap and i've been playing around with it, which has resulted in a few questions

First of all, is there a preferred style for "new" constructors? Specifically one which will be taking hash value/key pairs as attributes?

Secondly, is there a way to tell how many objects have been created/exist in a particular class?

And lastly, what is the best way to access individual object attributes? Always through a method or can you access them directly?

Here's the basic code i was messing around with. As Always, any help and comments greatly appreciated!

#! /gte/bin/perl -w use strict; use warnings; my $obj=Numbers->new('num1'=>7, 'num2'=>2); print "The object attribute num1 is ",$obj->dump('num1'),"\n"; print "The object attribute num2 is ",$obj->dump('num2'),"\n"; $obj->add_nums(); print "The add result is ",$obj->dump('result'),"\n"; $obj->subtract_nums(); print "The subtract result is ",$obj->dump('result'),"\n"; package Numbers; # This is the class sub new { my $class=shift; my %params=@_; my $self={}; $self->{$_}=$params{$_} foreach keys(%params); bless($self, $class); return $self; } sub add_nums { my $self=shift; $self->{result}=$self->{num1}+$self->{num2}; return $self->{result}; } sub subtract_nums { my $self=shift; $self->{result}=$self->{num1}-$self->{num2}; return $self->{result}; } sub change_nums { my $self=shift; my %params=@_; $self->{$_}=$params{$_} foreach keys(%params); return $self; } sub dump { my $self=shift; my $val=shift; return $self->{$val}; } 1; __END__

Replies are listed 'Best First'.
Re: OO best practice basic questions
by kennethk (Abbot) on Aug 27, 2014 at 14:42 UTC
    First of all, is there a preferred style for "new" constructors? Specifically one which will be taking hash value/key pairs as attributes?

    A summary of Perl Best Practices (TM) can be found on the Best Practices(TM) reference card. It does not make any pronouncements on the argument list for a constructor. The key-value syntax is a good choice, but you probably want to include a mod 2 test (die "Uneven list" if @_ % 2) to make sure the list is actually balanced. A single hash-ref is also a popular choice. See Moose::Manual for how the most standard issue object object system does it.

    Secondly, is there a way to tell how many objects have been created/exist in a particular class?
    You could add a counter to your constructor, and then provide an accessor to the counter, e.g.
    my $obj_count = 0; sub new { my $class=shift; my %params=@_; my $self={}; $self->{$_}=$params{$_} foreach keys(%params); bless($self, $class); $obj_count++; return $self; } sub get_count { return $obj_count; }
    with the possibility of also adding a decrement to the destructor.
    And lastly, what is the best way to access individual object attributes? Always through a method or can you access them directly?
    This one, best practices does weigh in on. Always through a accessor. That allows for a layer of abstraction between the object itself, makes you insensitive to implementation changes, and allows for side-effects and internal mutations of the class if the designer needs them.

    Side note: if this is not just a learning exercise, consider using a declarative object system, like Moose or one of its lighter-weight derivatives.


    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

      Thanks, i've added the uneven list test into my constructor.

      When using the obj_count, is there a way to make it a method of the object? I don't know if that makes sense but in order to call it i guess i have to use Numbers::get_count() for example. But then if i attach it to each object i'm guessing the object will only have the value at that time, so i'd have to be sure to only check the value in the last object created. Is that right?

      Another question i have is with the value "result"

      It is an internally created value. So do i initialise the value in the constructor? Or the first time it is used? For example if i call the method add_nums, the result is stored in result. Should that variable already exist?

      What we be an example of a destructor?

      Thanks!

        is there a way to make it a method of the object?
        Why not, just try
        $obj->ob_count;
        i guess i have to use Numbers::get_count()

        No, the class methods are usually called like

        my $count = 'Numbers'->get_count;

        It just returns a value of a lexical variable in the package, so it's not attached to an object, but to the class.

        Regarding the result: it depends. The direct initialization is usually used when it's cheap or when you know you'll use the value sooner or later. For the other cases, the "lazy" approach is more appropriate, i.e. create the result only when asked to do so.

        What we be an example of a destructor?
        In this case, you should probably do
        sub DESTROY { $obj_count-- }

        Note that you don't have to call $obj->DESTROY, you just undef $obj.

        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: OO best practice basic questions
by Anonymous Monk on Aug 27, 2014 at 14:30 UTC
    First of all, is there a preferred style for "new" constructors? Specifically one which will be taking hash value/key pairs as attributes?

    In Perl, TIMTOWTDI, There Is More Than One Way To Do It :-)

    However, if your constructor has more than ~3 arguments, a hash is probably better - especially if some arguments are optional.

    Secondly, is there a way to tell how many objects have been created/exist in a particular class?

    Again TIMTOWTDI, a quick implementation might have a global variable in the package as a counter, which gets incremented in new and decremented in DESTROY. If there is more than one way to create an object and/or there are subclasses, one has to watch out that those are covered.

    And lastly, what is the best way to access individual object attributes? Always through a method or can you access them directly?

    Again, it depends. If your class is just a blessed hash, there is no practical way from stopping anyone to dig into the hash other than telling people not to in the documentation. However, from outside the class, it's normally preferred to abstract it and go through accessors, because that hides the internal storage details and allows for things like validation. From inside the class, how you access fields depends on whether the accessors do more than just get/set a value, such as validation.

    Your code looks ok. I'd just recommend adding individual accessors for the fields, the change_nums and dump methods kind of break encapsulation. Also, for real code, you'd probably want to put each class in its own file; at the very least you should put the package in its own block ({ package Foo; ... }).

    The Perl documentation contains more info on OO, a good starting point is probably perlootut.

      "However, from outside the class, it's normally preferred to abstract it and go through accessors"

      Even from inside the class, you should access fields/attributes via the accessors. Otherwise you'll make life harder for yourself if/when you need to subclass your class.

      "a good starting point is probably perlootut"

      w00t! I just noticed that as of Perl 5.20, this document links to one of my modules. :-)

      Thanks very much for your reply!

      Couple of questions though:

      What do you mean by individual accessors? Do you mean actually have a separate method for each field like "dump_num1"?

      And i'm not totally familiar with modules/packages. How do i put the package in a separate file? For example, if i have the package in "Numbers.pm" do i then need to go through the process of using Makefile.pl etc? Or do i need to modify @INC at all?

      Thanks again for your help!

        What do you mean by individual accessors? Do you mean actually have a separate method for each field like "dump_num1"?

        Yes, although I wouldn't name it that :-) Here's an example of a common, simple implementation:

        sub num1 { my $self = shift; $self->{num1} = shift if @_; return $self->{num1}; }

        That'll let you get the current value with $oo->num1 and set a value with $oo->num1(123);

        And i'm not totally familiar with modules/packages.

        Your file Numbers.pm would start with package Numbers; use warnings; use strict; and end with 1; (the file needs to return a true value), and then the normal way to include it would be via use Numbers; - that's it. @INC includes the current working directory ., so initially you wouldn't need to change @INC at all if all your files are in the same directory. Later on, once you get into actually building a real library, you can deal with @INC - see for example lib or the -I switch.

        If you 'use <module>' in your code, perl will search in the @INC array of directories (observed with perl -V) for that <module>.pm and import it. Also, the command perldoc -q 'How do I add a directory ' should give more ways to add directories at runtime. For procedural based modules, not object oriented, there is more information worth reading in the documentation for Exporter. 'perldoc Exporter'.

        You do not need to do the Makefile to make everything work on your machine, but if you want to start sharing it, like on CPAN, you would want to use one of the mod builder tools like module-starter and write a bunch of tests to make the module complete, documented, and robust like.
Re: OO best practice basic questions
by Anonymous Monk on Aug 27, 2014 at 16:09 UTC

    Writing your own objects in Perl is an excellent learning exercise and is perfectly fine for small projects (OO enthusiasts might disagree, but I think there's nothing wrong with whipping up one or two classes this way when best practices are followed). If you're going to be writing a lot of OO code, nowadays there are a lot of modules that can help you. The best-known one is Moose, and chromatic's "Modern Perl" has a chapter on it ("regular" Perl objects like the one you're writing are described later on in that chapter, "Blessed References"). Some people prefer Moose's smaller siblings, such as Mouse, Moo or even Mo (although that might be a little too small ;-) ).

      Usually skip Mouse - it's a good OO kit in its own right, but it doesn't play well with Moose. (You can't for example consume a Mouse role in a Moose class, or vice versa.)

      Rather than Mo, consider Class::Tiny. It has fewer features, but is less crazy. Sadly, it disobeys the naming rule of starting with "Moose" and then stripping away letters. There are ongoing efforts to get it to play nice with Moo and Moose, and they seem to be getting somewhere.

      ... or even M (although that is a little too small).
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: OO best practice basic questions
by locked_user sundialsvc4 (Abbot) on Aug 27, 2014 at 15:17 UTC

    Perl is rather refreshing in the fact that it has tremendous power without shoving your nose into “and this is the one-and-only ‘right’ way to do it.”   (The essential way that Perl implements all of this stuff ... the bless verb ... always leaves me saying, “that’s it?”)

    If possible, look at a bunch of existing object-oriented Perl code.   The CPAN library is a fine place to do that, since you can look at the source-code to any package right there on the web-site.   Just choose packages of recent vintage.   Take a very close look at Moose and its many brethren, and do this very soon.

    If you find yourself dealing with a legacy application that is written in OO-Perl, as is often the case, make sure that you continue to do things the same way such things had previously been done, if possible.   Because the language does not impose a single right-way to do almost anything, I think that it is generally most important to stay consistent, and clear.   (“Golf” is a game, not a best-practice ...)

    Comments, for example.   They’re still free.   “Tell me what you’re thinking right now.”   Explain to me the clarity of your thoughts.   Even if you are writing for yourself, to yourself, you will find that you do not remember what you wrote when you read it again.   But, I guess, I digress . . .