Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid

Re: Handling cascading defaults

by tye (Sage)
on Aug 20, 2000 at 00:14 UTC ( #28669=note: print w/replies, xml ) Need Help??

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")

Replies are listed 'Best First'.
RE (tilly) 2: Handling cascading defaults (abstraction is great!)
by tilly (Archbishop) on Aug 20, 2000 at 08:07 UTC
    (Posted based on private conversation with tye.)

    To me the key concept is abstraction layers. Perl offers a lot of ways to create an abstraction layer. As long as you have and think in terms of abstraction layers you will find cascading defaults trivial to implement.

    I have found this to be true even if the abstraction layer is something as simple as storing stuff in a hash. (Start it at the default, then overlay changes in hash slices.) The second pass at an OO answer in RE (tilly) 4: Handling cascading defaults shows this kind of abstraction allowing the over-riding of defaults set during your constructor in a method call. (OK, that was in an OO example, but the principle of how I cascaded that time was not OO specific.)

    Another form of abstraction is provided by using references to subs rather than subs directly. OO is yet another. All of these can solve cascading defaults quite smoothly, for the same basic reason - your code goes through a level of indirection which anything you want can be overridden in.

    Now what does OO provide above and beyond abstraction? Well it provides an entire mental framework for how to organize your thoughts about problems. For some people this fits well. For others it doesn't. I am a person who is both competent at thinking that way but doesn't particularly enjoy it or find it a natural fit. (YMMV of course!) I don't want to track down the link, but at IWETHEY we had a conversation about this once. I thought that OO reminded me very much of an area of math called category theory, both of which I disliked. Ironically someone else there knew both, agreed they were similar, and liked both of them. :-)

    I won't give a detailed critique of my feelings. Most of it wouldn't make sense to most people here because I compare it to my experiences with graduate level math, which is probably not a particularly common experience base in these parts. :-)

    However if you look at what I write you will find that for me abstraction layers are a theme that comes up quite often as a motivating principle. You also see it in some of my favorite links, such as Linus on Portability (linked on my home node).

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2021-04-22 02:36 GMT
Find Nodes?
    Voting Booth?

    No recent polls found