http://www.perlmonks.org?node_id=28669


in reply to Handling cascading defaults

Well, I disagree strongly that you should avoid OO just because the code is small. Just yesterday I wrote an OO module that was under 20 lines.

At this point, since you've not shown the need for inheritance implementation reuse or even interface reuse, I'd stay away from the complexity of objects. My personal rule is not to pull in OO technology in Perl until the line count of the program exceeds about 1000 lines, and using all the OO features of abstraction, inheritance, and data hiding all becomes useful.

Well, I find that implementation reuse and inheritance are way over emphasized in OO and almost never happen (outside of standard libraries that get shipped with the language). Although I see a small percentage of objects that do interface reuse (via inheritance), the majority don't do any of those things.

In Perl the most common big advantages I see from OO are reduction of namespace collisions and simplified compartmentalization of configuration settings (my other favorite is cleaning up via destructors, but not all objects involve such). Here we want cascading defaults (ie. compartmentalization of configuration settings), which is done very nicely and naturely in Perl OO (I've never noticed it being done nicely in Perl in the absense of OO). I find that using Perl OO almost gives you cascading defaults without even trying.

Have a package global that contains the default configuration parameters. Have a constructor that copies the configuration parameters from the package or object that was used to call it. Provide methods for changing configuration parameters (but only on constructed objects, not on the package global of sane defaults). (Heck, if writing methods seems like too much work, you can let the module user directly set the parameters using $obj->{param_name} -- though I think you'd be wrong both in thinking that writing methods is too much work and in encouraging such unverified manipulation of your object's members.)

If you want script-level defaults, then you can also make this easy by making it easy to export/import a default error object for the script (see below).

Also, a package that starts out at 20 lines could eventually grow to over 1000. Retrofitting OO at that point would be a huge pain. I wouldn't necessarilly start all projects as OO modules. But once you get to the point that you see a need to use the code from multiple scripts and that therefore it makes sense to turn it into a module, then I think it also usually (in Perl) makes sense to put in OO.

package Err; use base qw( Exporter ); use Carp qw( croak ); my( %defaults, @options ); BEGIN { %defaults= ( Title => 'An error has occurred', Message => 'If error persists, seek assistance', ); @options= keys(%defaults); } sub new { my $this= shift; # Either a package name or an object. croak 'Usage: $handle= ',__PACKAGE__,"->new();\n", ' or: $handle= $ErrHandle->new()',"\n" if @ARGV; my $class= ref($this) || $this; # Copy defaults from calling object or use standard defaults: my $obj= ref($this) ? {%$this} : {%defaults}; return bless $obj, $class; } sub import { # Export a unique $ErrHandle to each caller: my $uniq= $_[0]->new(); *ErrHandle= \$uniq; goto &Exporter::import; } sub Configure { my( $self, $opts )= @_; croak 'Usage: $ErrHandle->Configure( ', '{ Title=>"new title", Message=>"new msg" } )', "\n" unless 2 == @ARGV && ref($opts) && UNIVERSAL::isa($opts,"HASH"); my @err= grep { ! exists $defaults{$_} } keys %$opts; croak __PACKAGE__,"::Configure: Unknown option(s) ", "(@err) not (@options)\n" if @err; @{$self}{keys %$opts}= values %$opts; return $self; }

Now add a method that actually reports the error (you just had "do something" in your example). And you use it like this:

use Err qw( $ErrHandle ); # Set script-specific defaults: $ErrHandle->Configure( {Message=>"Contact technical support"} ); # Use script defaults: $ErrHandle->Report(...); # Set and use other configurations: my $accountingErrHandle= $ErrHandle->new()->Configure( {Message=>"Please notify Bob"} ); $accountingErrHandle->Report(...);

Note that you can now add a new configuration parameter by just adding one line specifying the name and default value for it. At 40 lines, it is more complex than what we started out with. But a lot of that has to do with the request for script-level defaults and some nice things I added like croak()ing when a method is called incorrectly.

In summary, I think you can write very small but reasonable OO modules when you have something that starts out very simple but that you want to use several places. I think that in the long run you'll be glad you did start out with OO.

Update: Fixed unmatched quotes in my still-thoroughly-untested sample code. Actually, I was forced to test the exporting of a unique scalar stuff before posting since I'd never actually done that before (actually).

        - tye (but my friends call me "Tye")