Vincenzo Zocca
Sun Feb 16 20:29:26 CEST 2003
Thu Sep 4 06:42:32 CEST 2003
Although PerlBean produces decent code (decent enough for the author that is), PerlBean is still under development.
The code generation has impoved significantly since the first versions of PerlBean -which have been removed from CPAN- but still I expect more impovements. See the TODO list.
Most OO designs contain objects with a bean like(*) structure and -optionally- some kind of logic. Although the bean capabilities in an object are -or should be- trivial, programming them takes a non proportional amount of time. Also, documenting inheritance of attributes and methods is tedious at best.
The modules in the PerlBean hierarchy are to make the life of a Perl OO programmer easier. They allow to setup classes with attributes and logic methods. These classes can be put into a bean collection and the Perl code for the classes is generated. The basic class documentation and the documentation of the attribute and method inheritance is generated.
At the time of writing (Thu Sep 4 06:42:32 CEST 2003), this package has already proved itself very useful for my professional purposes. I can code more in less time, I produce higher quality code, I find it easier to keep a conceptual overview of my projects and I take better advantage of OO paradigm altogether.
For the code to mature I 1) use it to generate itself and 2) use it to develop other projects and feed my findings back into the code. Currently I am at the point where the TODO list grows bigger but the items in it get more and more details.
The term "bean" is borrowed from Java. In this context it is important to know that beans are objects with attributes (or properties) which are accessible through methods like "get", "set" etc...
PerlBean represents a Perl module and contains information on the package. Its descriptions, attributes, methods etc...
PerlBean
A PerlBean Collection is a collection of PerlBeans.
For this tutorial we'll develop a simple class hierarchy for geometrical shapes. The PerlBean Collection will consist of the abstract Shape, the implementations of classes Square and Circle and the Rectangle subclass of Square. This class hierarchy isn't exactly rocket science -it isn't even necessarily logical- but it serves the tutorial purpose.
Shape
Square
Circle
Rectangle
+------------+ | Shape | + - - - -+ (abstract) +- - - - + | |------------| | |------------| | | area() | | / \ +------------+ / \ +-+-+ +---+ | | | | +-----+------+ +-------+----+ | Square | | Circle | |------------| |------------| | width | | radius | |------------| |------------| | area() | | area() | +-----+------+ +------------+ | / \ +---+ | +-----+------+ | Rectangle | |------------| | width | | height | |------------| | area() | +------------+
The example.1.pl program generates code for the Shape packages.
example.1.pl
First, a PerlBean::Collection is created which is used throughout the program. A collection-wide license is specified at construction.
PerlBean::Collection
use PerlBean::Collection; my $coll = PerlBean::Collection->new( { license => <<EOF, This code is licensed under B<GNU GENERAL PUBLIC LICENSE>. Details on L<http://gnu.org>. EOF } );
Then, a PerlBean::Attribute::Factory is created which is used to create attributes (PerlBean::Attribute(*) objects) throughout the program.
PerlBean::Attribute::Factory
PerlBean::Attribute(*)
PerlBean::Attribute objects are an implementation of PerlBean::Method::Factory. That is, PerlBean::Attribute objects on their turn create PerlBean::Method objects.
PerlBean::Attribute
PerlBean::Method::Factory
PerlBean::Method
use PerlBean::Attribute::Factory; my $fact = PerlBean::Attribute::Factory->new();
The first Shape PerlBean is created and added to the collection. At creation the package name, a short description and the abstract are specified. Read the PerlBean documentation for the details.
use PerlBean; my $shape = PerlBean->new ( { package => 'Shape', short_description => 'geometrical shape package', abstract => 'geometrical shape package', autoloaded => 0, } ); $coll->add_perl_bean( $shape );
area()
The interface method area() is created and added to the Shape PerlBean. At creation the method name, the method description and the fact that the method is an interface is specified. Again, read the documentation for the details.
use PerlBean::Method; my $meth = PerlBean::Method->new( { method_name => 'area', interface => 1, description => <<EOF, Calculates the area of the C<Shape> object. EOF } ); $shape->add_method( $meth );
Now the Circle PerlBean is created and added to the collection. The creation and addition to the collection is almost identical to Shape. The base attribute is specified to tell that Circle is a subclass -or implementation- of Shape.
base
my $circle = PerlBean->new ( { package => 'Circle', base => [ qw( Shape ) ], short_description => 'circle shape', description => "circle shape\n", abstract => 'circle shape', autoloaded => 0, } ); $coll->add_perl_bean( $circle );
radius
A radius attribute is created and added to the Circle PerlBean. At creation the attribute name, a short description and a regular expression to which the attribute's values must match (^\d*(\.\d+)?$) are specified. The documentation of PerlBean::Attribute::Factory has all the details for this part of the process.
^\d*(\.\d+)?$
my $radius = $fact->create_attribute( { method_factory_name => 'radius', short_description => 'the shape\'s radius', allow_rx => [ qw( ^\d*(\.\d+)?$ ) ], } ); $circle->add_method_factory( $radius );
The implementation of method area() is created and added to the Circle PerlBean. At creation the method name and its body are specified. The description is copied from the interface when the code is generated.
# Make the Circle area() method add it to the Circle PerlBean use PerlBean::Method; my $area_circle = PerlBean::Method->new( { method_name => 'area', body => <<EOF, my \$self = shift; return( 2 * 3.1415926 * \$self->get_radius() ); EOF } ); $circle->add_method( $area_circle );
width
This is much like 2.2.5 - 2.2.7 but then for Square.
my $square = PerlBean->new ( { package => 'Square', base => [ qw( Shape ) ], short_description => 'square shape', abstract => 'square shape', autoloaded => 0, } ); $coll->add_perl_bean( $square ); my $width = $fact->create_attribute( { method_factory_name => 'width', short_description => 'the shape\'s width', allow_rx => [ qw( ^\d*(\.\d+)?$ ) ], } ); $square->add_method_factory( $width ); use PerlBean::Method; my $area_square = PerlBean::Method->new( { method_name => 'area', body => <<EOF, my \$self = shift; return( \$self->get_width() * \$self->get_width() ); EOF } ); $square->add_method( $area_square );
height
Rectangle is a subclass from Square. It inherits the width attribute, has an additional height attribute and a different implementation of area().
The Rectangle PerlBean which is a subclass of Square.
my $rectangle = PerlBean->new ( { package => 'Rectangle', base => [ qw( Square ) ], short_description => 'rectangle shape', abstract => 'rectangle shape', autoloaded => 0, } ); $coll->add_perl_bean( $rectangle );
The height attribute.
my $height = $fact->create_attribute( { method_factory_name => 'height', short_description => 'the shape\'s height', allow_rx => [ qw( ^\d*(\.\d+)?$ ) ], } ); $rectangle->add_method_factory( $height );
The area() method
use PerlBean::Method; my $area_rectangle = PerlBean::Method->new( { method_name => 'area', body => <<EOF, my \$self = shift; return( \$self->get_width() * \$self->get_height() ); EOF } ); $rectangle->add_method( $area_rectangle );
The code is generated in directory example.1
example.1
# The directory name my $dir = 'example.1'; # Remove the old directory system ("rm -rf $dir"); # Make the directory mkdir($dir); # Write the collection $coll->write($dir);
Check out the documentation of the PerlBeans. Try replacing Shape with Circle, Rectangle or Square.
pod2man example.1/Shape.pm | nroff -man|less pod2man example.1/Shape.pm | nroff -man|less -r pod2html example.1/Shape.pm > example.1/Shape.html
Program example.1.test.pl tests the classes Circle, Rectangle and Square.
example.1.test.pl
use strict; use lib qw( example.1 ); use Circle; my $circle = Circle->new( { radius => 1, } ); print 'Circle with radius ', $circle->get_radius(), ' has an area of ', $circle->area(), "\n"; use Square; my $square = Square->new( { width => 1, } ); print 'Square with width ', $square->get_width(), ' has an area of ', $square->area(), "\n"; use Rectangle; my $rectangle = Rectangle->new( { width => 1, height => 2, } ); print 'Rectangle with width ', $rectangle->get_width(), ' and with height ', $rectangle->get_height(), ' has an area of ', $rectangle->area(), "\n";
So far in the example from chapter 2 plain attributes were used (radius, width and height). The only advanced feature used is the allow_rx attribute when creating the attributes with the attribute factory.
allow_rx
But there is more. More attribute types and more features. The attribute factory knows how to select the correct attribute class and how to instantiate it.
PerlBean::Attribute::Single
So far we used the SINGLE attribute type which has set... and get... methods. The implementing class is called PerlBean::Attribute::Single.
SINGLE
set...
get...
PerlBean::Attribute::Boolean
The BOOLEAN attribute type has methods set... and is... and treats the value slightly different. The implementing class is called PerlBean::Attribute::Boolean.
BOOLEAN
is...
PerlBean::Attribute::Multi
The attribute type MULTI is intended to contain array and hash derivatives and there are several classes that implement those. The implementing interface class is called PerlBean::Attribute::Multi.
MULTI
PerlBean::Attribute::Multi::Ordered
The PerlBean::Attribute::Multi::Ordered class is obtained by specifying the attributes type => 'MULTI and ordered => 1 to the attribute factory. It implements a plain ordered list that can hold values in an ordered fashion.
type => 'MULTI
ordered => 1
PerlBean::Attribute::Multi::Unique
The PerlBean::Attribute::Multi::Unique class is obtained by specifying the attributes type => 'MULTI and unique => 1 to the factory. It implements a plain hash that can unique values (that is, any value is allowed to occur only once) in an unordered fashion.
unique => 1
PerlBean::Attribute::Multi::Unique::Ordered
The PerlBean::Attribute::Multi::Unique::Ordered is a hybrid of PerlBean::Attribute::Multi::Ordered and PerlBean::Attribute::Multi::Unique and is obtained by specifying the attributes type => 'MULTI, ordered => 1 and unique => 1. It implements an ordered list that can hold unique values.
PerlBean::Attribute::Multi::Unique::Associative
The PerlBean::Attribute::Multi::Unique::Associative class is obtained by specifying the attributes type => 'MULTI, unique => 1 and associative => 1 to the factory. It is a refinement of PerlBean::Attribute::Multi::Unique and associates unique keys to values. Well, this is actually even more a plain hash than PerlBean::Attribute::Multi::Unique.
associative => 1
PerlBean::Attribute::Multi::Unique::Associative::MethodKey
The PerlBean::Attribute::Multi::Unique::Associative::MethodKey class is obtained by specifying the attributes type => 'MULTI, unique => 1, associative => 1 and method_key => <method-key>to the factory. It is similar to PerlBean::Attribute::Multi::Unique::Associative but it takes the keys from the values by calling a method on the values. Very handy if you want to list objects by their keys.
method_key => <method-key>
Program example.2.pl contains a PerlBean collection with one PerlBean in it with all kinds of attributes. It doesn't do anything particularly useful other that serving the example purpose. Check out the program, the generated documentation and the generated code.
example.2.pl
The examples in the previous sections had PerlBean::Method objects. Well, there is also the PerlBean::Method::Constructor class that is an empty subclass of PerlBean::Method. Methods defined as PerlBean::Method::Constructor have the same properties as PerlBean::Method objects. They can be added to a PerlBean using the add_method() method. The only difference with PerlBean::Method objects is that the documentation is written in the METHODS section.
PerlBean::Method::Constructor
add_method()
METHODS
PerlBean takes inheritance into account when generating documentation and code. As Perl supports multiple inheritance, so does PerlBean.
Descriptions of methods and attributes are passed from superclass to subclass in a -hopefully- intuitive manner.
The string __SUPER_POD__ in descriptions is replaced with the description of the superclass. Most of the time you will not need this feature -which actually may need a few extra words in the documentation.
__SUPER_POD__
For interface methods a default method is implemented in the class where they are defined that merely throws an exception. This is to signal bad usage of the generated module.
The default style is as defined in perlstyle. It can however be changed at will.
perlstyle
The style is controlled though the PerlBean::Style modules. The singleton instance of PerlBean::Style is used to format the generated code.
PerlBean::Style
Example 3 calls the code from example 1 but changes the Style:
Method names in camel case
Block opening brace on new line
Space between function and brace
Tab indentation
The code to make the style change looks like:
use strict; use PerlBean::Style; my $style = PerlBean::Style->instance(); $style->set_method_factory_name_to_method_base_filter(\&mbase_flt); $style->set_method_operation_filter(\&op_ftl); $style->set_str_pre_block_open_curl("\n__IND_BLOCK__"); $style->set_str_between_function_and_parenthesis(' '); $style->set_indent("\t"); require 'example.1.pl'; sub mbase_flt { my $ret = ''; foreach my $attr_part ( split(/_+/, shift) ) { $ret .= ucfirst($attr_part); } return($ret); } sub op_ftl { return( ucfirst(shift) ); }
NOTE: The PerlBean::Method objects are NOT affected by PerlBean::Style.
The PerlBean::Style instance is obtained through the instance() method.
instance()
set_method_factory_name_to_method_base_filter() allows to set the subroutine that converts an attribute name to the method base.
set_method_factory_name_to_method_base_filter()
set_method_operation_filter() allows to set the subroutine that formats the method operation.
set_method_operation_filter()
set_str_pre_block_open_curl() allows to set the string printed before the opening curly of a multi-line BLOCK.
set_str_pre_block_open_curl()
set_str_between_function_and_parenthesis() allows to set the string between function name and its opening parenthesis.
set_str_between_function_and_parenthesis()
set_indent() allows to set the string used for ONE indentation.
set_indent()
Check out the code and see for yourself.
The PerlBean::Symbol class allows for symbols to be declared that are global to the package.
PerlBean::Symbol
Exporting of the symbols is also handeled by PerlBean::Symbol.
The tag description for exporting is handeled by PerlBean::Described::ExportTag.
PerlBean::Described::ExportTag
Code dependencies (e.g. import, require and use) can be specified through packages PerlBean::Dependency::Import, PerlBean::Dependency::Require and PerlBean::Dependency::Use.
PerlBean::Dependency::Import
PerlBean::Dependency::Require
PerlBean::Dependency::Use
The dependency clases are listed at the top of the package.
By default PerlBean generates autoloaded code. You can switch the autoloaded attribute off in PerlBean objects to generated plain Perl code. See also the examples.
autoloaded
Your comments are valuable!
As I am working at this project on my own there's the usual chance my vision is limited in several areas. That may show in certain bits of -generated- code. Particularly in this area, I'm interested in your feedback.
Bug reports are welcome too.
Vincenzo Zocca mailto:Vincenzo@Zocca.DO.NOT.SPAM.com
To install PerlBean, copy and paste the appropriate command in to your terminal.
cpanm
cpanm PerlBean
CPAN shell
perl -MCPAN -e shell install PerlBean
For more information on module installation, please visit the detailed CPAN module installation guide.