- By
-
Vincenzo Zocca
- Creation
-
Sun Feb 16 20:29:26 CEST 2003
- Update
-
Thu Sep 4 06:42:32 CEST 2003
0 USAGE NOTE
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.
1 INTRODUCTION
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...
2 A PERL BEAN COLLECTION
PerlBean
represents a Perl module and contains information on the package. Its descriptions, attributes, methods etc...
A PerlBean Collection is a collection of PerlBean
s.
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.
2.1 Class diagram for shapes
+------------+
| Shape |
+ - - - -+ (abstract) +- - - - +
| |------------| |
|------------|
| | area() | |
/ \ +------------+ / \
+-+-+ +---+
| |
| |
+-----+------+ +-------+----+
| Square | | Circle |
|------------| |------------|
| width | | radius |
|------------| |------------|
| area() | | area() |
+-----+------+ +------------+
|
/ \
+---+
|
+-----+------+
| Rectangle |
|------------|
| width |
| height |
|------------|
| area() |
+------------+
2.2 Programming of the classes using PerlBean
The example.1.pl
program generates code for the Shape
packages.
2.2.1 PerlBean collection
First, a PerlBean::Collection
is created which is used throughout the program. A collection-wide license is specified at construction.
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
} );
2.2.2 PerlBean attribute factory
Then, a PerlBean::Attribute::Factory
is created which is used to create attributes (PerlBean::Attribute(*)
objects) throughout the program.
- (*)
-
PerlBean::Attribute
objects are an implementation ofPerlBean::Method::Factory
. That is,PerlBean::Attribute
objects on their turn createPerlBean::Method
objects.
use PerlBean::Attribute::Factory;
my $fact = PerlBean::Attribute::Factory->new();
2.2.3 Shape
PerlBean
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 );
2.2.4 area()
interface method
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 );
2.2.5 Circle
PerlBean
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
.
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 );
2.2.6 Circle
radius
attribute
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.
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 );
2.2.7 Circle
area()
method
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 );
2.2.8 Square
with a width
attribute and an area()
method
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 );
2.2.9 Rectangle
with a height
attribute and an area()
method
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 );
2.2.10 Code generation
The code is generated in directory 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);
2.2.11 Documentation check
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
2.2.12 Test the generated code
Program example.1.test.pl
tests the classes Circle
, Rectangle
and Square
.
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";
3 ATTRIBUTES
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.
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.
3.1 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
.
3.2 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
.
3.3 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
.
3.3.1 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.
3.3.2 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.
3.3.3 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.
3.3.4 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
.
3.3.5 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.
3.3.6 Example containing all attribute types
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.
4 CONSTRUCTORS
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.
5 A FEW WORDS ON INHERITANCE
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.
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.
6 STYLE
The default style is as defined in perlstyle
. It can however be changed at will.
The style is controlled though the PerlBean::Style
modules. The singleton instance of PerlBean::Style
is used to format the generated code.
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.
set_method_factory_name_to_method_base_filter()
allows to set the subroutine that converts an attribute name to the method base.
set_method_operation_filter()
allows to set the subroutine that formats the method operation.
set_str_pre_block_open_curl()
allows to set the string printed before the opening curly of a multi-line BLOCK.
set_str_between_function_and_parenthesis()
allows to set the string between function name and its opening parenthesis.
set_indent()
allows to set the string used for ONE indentation.
Check out the code and see for yourself.
7 SYMBOLS
The PerlBean::Symbol
class allows for symbols to be declared that are global to the package.
Exporting of the symbols is also handeled by PerlBean::Symbol
.
The tag description for exporting is handeled by PerlBean::Described::ExportTag
.
8 DEPENDENCIES
Code dependencies (e.g. import, require and use) can be specified through packages PerlBean::Dependency::Import
, PerlBean::Dependency::Require
and PerlBean::Dependency::Use
.
The dependency clases are listed at the top of the package.
9 AUTOLOADING
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.
10 GET INVOLVED
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