Class::Prototype, for fast OOP development
Social links
View Ashley Pond V's profile on LinkedIn
Miscellaneous

Other pages

Class::Prototype Perldoc

This module makes setting up an object oriented package (your own module) simple and fast. I use it for when I’m writing test code, code I’m not sure I’ll keep, or just trying to get something done in a hurry.

I wrote Class::Prototype before I was acquainted with some of the Class namespace. I’m actually glad because I might not have written it if I’d seen some of the similar modules like Class::Base from Andy Wardley, and Class::Accessor from Michael G Schwern. Class::Prototype is for generally different purposes, is more simple to just drop in and use, and I like it better for those cases.

I have not released it to CPAN because I’m not sure I will have time to ever maintain it and it could be deeper (more auto-methods available per data type) and it could stand a code review.

Here is the POD for the module. You can view the source of Class::Prototype too.

NAME

Class::Prototype

VERSION

0.03

ABSTRACT

Class::Prototype is an object oriented prototyping base class. It is designed to throw together a module or family of modules quickly by giving self-‍installed methods defined via the child class’s attributes() method. The types of methods which can be self-‍installed cover 80-‍90% of what a typical OOP module does. Ie: making a hash based object, installing attributes, and then getting, setting, resetting, stacking, shifting those attributes.

Class::Prototype is not really intended for building production code. The use of the code it generates is terse (set/get methods are one in the same) and DWIM (do what I mean — context determines behavior). As such it will for some users “not do what I expected” and “not work the way I prefer.” Class::Prototype code is also generally slower than what you might code up by hand b/c it’s installing its own methods as it goes. Class::Prototype is for putting up a scaffolding of prototype code that will work immediately and will be phased completely out as development progresses. Class::Prototype is for jumping in and writing code that works.

SYNOPSIS

Define your new class—must contain attributes()

 #------------------------------------------------------------
 package Fish; 
 use base 'Class::Prototype';
 #------------------------------------------------------------
 # set some lexical class data
 my @family = qw( Loricariidae Balistidae Gobiidae 
                  Syngnathidae Megachasmidae );
 my %sizes;
 @sizes{@family} = qw( medium small small 
                       tiny huge );
 #------------------------------------------------------------
 # set up the only method required to start coding,
 # attributes(), which  must be defined in this general format
 sub attributes {
      {#                   DEFAULT           ACCESS
        _favorite     => [ undef,        'writeonce' ],
        _current_fish => [ undef,            'write' ],
        _family       => [ \@family,      'readonly' ],
        _size         => [ \%sizes,       'readonly' ],
      }
 }
 #------------------------------------------------------------
 1; # save it in the @INC path as Fish.pm and we're ready!

Now use it in a script

 use Fish;         # <<-- your new class/module
 #--------------------------------------------- 
 my $fish_obj = Fish->new();
 
 # set your favorite fish
 $fish_obj->favorite("Kuhli Loach");

 print "Fish Chart:\n";
 for my $fish ( sort $fish_obj->family ) {
     printf "%15s --> %s\n", 
     $fish, $fish_obj->size($fish);

 # keep track of last fish seen here
     $fish_obj->current_fish($fish);
 }

 print "The last fish family I saw was ", 
     $fish_obj->current_fish, ".\n";

 # try to reset the "writeonce" favorite
 $fish_obj->favorite("7 Gill Shark");

 print "My favorite fish is still the ", 
     $fish_obj->favorite, ".\n";

And you should get:

 Fish Chart:
      Balistidae --> small
        Gobiidae --> small
    Loricariidae --> medium
   Megachasmidae --> huge
    Syngnathidae --> tiny
 The last fish family I saw was Syngnathidae.
 You cannot reset favorite() (to 7 Gill Shark),
        skipping! at fish line 22
 My favorite fish is still the Kuhli Loach.

You give, you get

attributes() is just a wrapper for a hash reference which describes your object’s automated behavior. You can use scalars, arrays and hashes in your object.

In your hash ref, you have keys (attributes) which point to 2 element array ref of initialization information. Like so:

  attribute  => [ 'default value', 'access' ],

Access can be set to one of the following:

  • readonly (default/initialization value is only value)
  • writeonce (default, new(key=>‘value’), or first !undef write)
  • write (open to change as often as desired)

Default values can be scalars (undef is acceptable default for a scalar, especially a “writeonce” scalar), array refs, hash refs. So, another sample attributes() method with examples:

 sub attributes {
    return {          # DEFAULT                    ACCESS
      scalar_attr  => [ 'default value',          'write' ],
      scalar_attr2 => [ undef,                'writeonce' ],
      array_attr   => [ [],                       'write' ],
      array_attr2  => [ [ 1 .. 99 ],           'readonly' ],
      hash_attr    => [ { perl => 'rocks',
                          ruby => 'rolls',
                          java => 'hmm...' },  'readonly' ],
      hash_attr2   => [ {},                       'write' ]
      };
 }

What happens now? Methods are installed

Assuming the attribute name is “my_attr,” regardless of the default data type or access, the method my_attr() is installed for your object and behaves in the following ways.

  • default is ‘some string’ access is ‘readonly’
  • my_attr() has only one use. It is called to return the scalar “some string.” If given an argument, it will carp() about it and not do anything with it.
  • default is ‘some string’ access is ‘writeonce’
  • my_attr() can set its value one time only. NB: this b initialization. Therefore given the default of ‘some string’ it will behave for the user like “readonly.” See the next one for the way you want to use this.
  • default is undef access is ‘writeonce’
  • my_attr() is not set by the initialization (undef) so the user can set it one time. After that, it’s only get. It can be set in new() as well.
     my $obj = Subclass->new( my_attr => 'my only value' );
        # or the equivalent
     my $obj = Subclass->new();
     $obj->my_attr('my only value');
  • default is ‘some string’ access is ‘write’
  • my_attr() is get/set. Calling it without an arg, like $obj->my_attr(), gets: “some string.” Calling it with an arg, like $obj->my_attr(‘new string’), updates the contents so the next call without an argument will get: “new string.”
  • default is [‘array’,‘ref’] access is ‘readonly’
  • my_attr() is get only. In scalar context it returns the array ref, in list context it dereferences it for you and returns the list. Carps if given arguments.
  • default is [‘array’,‘ref’] access is ‘writeonce’
  • my_attr() can be passed an array or an array ref to store for future gets once. After it is set, which can be in the call to new(), it behaves as the ‘readonly’ version above.
  • default is [‘array’,‘ref’] access is ‘write’
  • my_attr() is get/set, or in this case, return all or shift/push.
     my @array   = $obj->my_attr(); # list context, gets the list
     my $element = $obj->my_attr(); # scalar, shifts off an element
    so this would harmless iterate on the array’s data
      for my $element ( $obj->my_attr ) {
          print $element, "\n";
      }
    while this would empty the array one shift at a time:
     while ( my $element = $obj->my_attr() ) {
        print $element, "\n";
     }
    And adding elements to the list is easy:
     $obj->my_attr(@elements_to_append);
  • default is a hash ref, any access type
  • keys_my_attr() is installed to get the list of keys to use like so:
     for my $key ( sort $obj->keys_my_attr ) {
        print "$key  -->> ", $obj->my_attr($key), "\n";
     }
    There is no each_my_attr() style function.
  • default is { hash => ‘ref’ } access is ‘readonly’
  • The attributes() hash ref value is the only one possible. keys_my_attr() gets keys, my_attr($key) gets values.
  • default is { hash => ‘ref’ } access is ‘writeonce’
  • I think you get the idea.
  • default is { hash => ‘ref’ } access is ‘write’
  • my_attr( $key => $value ) to set

METHODS

Correction, no methods

Class::Prototype is a parent class. You cannot create objects in it and you should never directly use its methods.

This means you will never do this:

  use Class::Prototype;

You’ll always use a subclass of it. Because if you try to do something like

 my $obj = Class::Prototype->new();

You will get a croak along the lines of “Class::Prototype is a parent class only. You cannot create Class::Prototypes in it!”

Subclass methods you start with

  • my $obj = SubClass->new()
  • Obviously, we need a new() method and one is ready for you. It controls the installation of the object from the attributes() method you need to have in your subclass.
    If you are worried about being limited by being unable to write your own new(), there is a way to get in on the object initialization. If you have a method called new_hook() in your subclass,
  • $obj->verbosity() or $obj->verbosity($verbosity)
  • Set/get. A verbosity system of 0-‍5 is set up and used through the method verbosity(). 0 means no extra info, 5 would mean everything. It can be reset on the fly if given a 0-‍5 value. Implement your own:
     sub method1 {
         my ( $self, @args ) = @_;
    
         warn "method1() got ", join(', ', @args), ".\n"
             if $self->verbosity >= 2;
    
     #... do the rest of what you came to do
     }
  • $obj->show_attributes()
  • Returns an unsorted list of keys/attributes in your object but only those keys that start with an underscore and a letter. This is the Class::Prototype way of installing them. Eg, $self->{_touchiness}. If you add your own attributes through hardcoded methods and you don’t use this convention, show_attributes() will not return them. So neither $self->{__private_x_2} nor $self->{look_ma} would be found by show_attributes().

Initializing the object specially with new_hook()

new_hook() is run on the object immediately after it is bless’d and before any methods are installed or default values are set. This means you can define anything into your object you like before the automatic installation runs.

Also note that if you have an attribute named xyz in your attributes() but you also create an xyz() method in your subclass, the automatic installation of xyz(), or its related methods, like keys_xyz() and delete_xyz() for hash refs attributes, does not occur. Class::Prototype::new() contains this:

 # auto-install method unless the package already defines it
        $ego->_install_method($method, $access, $type, $value)
            unless $ego->can($method);

which does what you’d expect — skips the method installation if you already defined a method by the same name.

This is so that you can drop in methods as you go which will replace the automated ones. You can also take them back out to restore the automatic behavior as long as the attribute remains in your attributes() method.

Setting and getting all in one

Any attribute which is defined as “write” is set/get and any attribute defined as “writeonce” is set-‍once/get.

Subclass POD writer

IT SHOULD WRITE ITS OWN POD WITH A COMPANION SCRIPT, to show the methods the child will have. And either put it to STDOUT or append it to the .pm in question.

THANKS

Anyone who owns “Object Oriented Perl,” Damian Conway, 1884777791, already knows this module is heavily based on techniques therein. Anyone who hasn’t dropped Mr. Conway a line just to say, “Thank you for your Perl work and advocacy,” should.

COPYRIGHT

©2002-‍2003 Ashley Pond V, ashley@cpan.org, all rights reserved; modify and redistribute under the same terms as Perl.

Search these pages via Google
Text, original code, fonts, and graphics ©1990-2008 Ashley Pond V.