Title Line - Raleigh Perl Mongers

advertisement
Modules and Object Classes
Some Background and Case Study Examples
Bill Cowan
Metagenix, Inc.
www.metagenix.com
919 / 490-0034
bcowan@metagenix.com
For any comments on my approach to Perl O-O style,
please object politely…
Table of Contents
Table of Contents .......................................................................................... 2
Background: References .............................................................................. 3
Scalar References ...................................................................................... 3
Array References ....................................................................................... 3
Anonymous Array References: ................................................................... 3
Hash References ....................................................................................... 5
Anonymous Hash References: ................................................................... 5
Background: Perl Modules ............................................................................ 6
Example of Simple Module ......................................................................... 6
Calling Program ......................................................................................... 7
Comments about Sample Module .................................................................. 9
Example of Module with POD ...................................................................... 10
Perl and Classes/Objects ............................................................................. 13
The Basics … ........................................................................................... 13
Example of Simple Class from perltoot ........................................................ 14
How to Represent an Object ........................................................................ 17
Categories for Methods................................................................................ 18
Expanded Example with Inherited Utility Class ............................................ 19
Sample Methods from Utility Class .............................................................. 23
AUTOLOADed Data Assessors ................................................................... 27
Candidates to Consider for Utility Class ....................................................... 29
Productivity Thoughts to Code Classes ....................................................... 30
Background: References
Scalar References
DB<1> $Scalar = 'abcd'
DB<2> $ScalarRef = \$Scalar
DB<3> print $ScalarRef
SCALAR(0xdef3bc)
DB<6> print $Scalar, '
abcd
', ${$ScalarRef}
abcd
Array References
DB<8> @Array = qw( A B C D);
DB<9> $ArrayRef = \@Array;
DB<10> print $ArrayRef, ' ', join('|', @{$ArrayRef})
ARRAY(0x9b6940) A|B|C|D
DB<11> print $ArrayRef, '
ARRAY(0x9b6940)
', join('|', @{$ArrayRef})
A|B|C|D
DB<13> print $ArrayRef->[1], '
B
', $ArrayRef->[3]
D
Anonymous Array References:
DB<16> $ArrayRef = ['a', 'b', 'c', 'd']
DB<18> print @{$ArrayRef}, '
abcd
', $ArrayRef->[1]
b
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 3 of 30
919 / 490-0090
Doc Reference:
perlref
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 4 of 30
919 / 490-0090
Hash References
DB<19> %Hash = (A => '1', B => '2')
DB<20> $HashRef = \%Hash
DB<24> print $HashRef->{A}, '
1
AB
DB<25> print $HashRef->{A}, '
1
', keys( %{$HashRef} )
AB
DB<26> print $HashRef->{A}, '
1
', keys(%$HashRef)
', keys( %$HashRef )
AB
Anonymous Hash References:
DB<27> $HashRef = {A => '1', B => '2'}
DB<29> print $HashRef, ' ', join('|', %{$HashRef})
HASH(0xdeff38)
A|1|B|2
DB<30> print $HashRef->{A}, '
1
', keys( %{$HashRef} )
AB
Note different ways to do de-referencing on the reference variables:
%{ $HashRef },
$HashRef->{'A'}
Caution: Until you are comfortable with references and de-referencing
process, do not start writing classes and objects!
Doc Reference:
perlref
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 5 of 30
919 / 490-0090
Background: Perl Modules
Example of Simple Module
# -- MODULE:
HandyDandyLib.pm
package HandyDandyLib;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK
%EXPORT_TAGS $VERSION);
use Exporter();
@ISA
= qw(Exporter);
@EXPORT
= qw(Trim Ltrim);
@EXPORT_OK = qw(TrimHashValues);
functions to export.
#-- optional
%EXPORT_TAGS = (VARS => [ @EXPORT_OK ]);
$VERSION = '';
sub Trim {
#-- Parameters:
my( $StringParm
#-- string to process
) = @_;
$StringParm =~ s/^\s+//;
$StringParm =~ s/\s+$//;
return( $StringParm );
} #-- end of Trim function
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 6 of 30
919 / 490-0090
Example Module Continued.
sub TrimHashValues {
#-- Parameters:
my( $HashRef,
#-- in/out: hash ref to trim.
)=@_;
....
return(1);
} #-- end of TrimHashValues function.
sub GetPgmRevNbr {
#-- Function parameters:
my ($RevString,
#-- Input revision string.
) = @_;
....
return($1);
}
1;
#-- End of GetPgmRevNbr.
#-- end of module
Calling Program
# Default imported entry points.
use HandyDandyLib;
# &Trim was in @EXPORT
$String = &Trim($Input) . subtr($Name, 1, 5);
-----------------# Explicit request to import specific entry point.
use HandyDandyLib qw(TrimHashValues);
# &TrimHashValues was in @EXPORT_OK.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 7 of 30
919 / 490-0090
%NewHash = &TrimHashValue(\%InHash);
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 8 of 30
919 / 490-0090
Comments about Sample Module
Points about Module:
 "Package HandyDandyLib" to create separate namespace.
 "use vars …" as way to declare package standard package globals.
 "use Exporter;" to handle export entry points into caller's namespace.
 @EXPORT, @EXPORT_OK to control which entry points to export by
default or by request.

"sub Trim…" to define each function. Some functions can be exported and
other can remain local to this module.
 "1; #-- end of module" to signal end of this module.
Points about Caller's Code
 Note difference in "use" statement and how that relates to which function
names are listed in @EXPORT and @EXPORT_OK.
 You know that a function is defined in a module (e.g. a .pm file), because
you visually verified that function existed. But Perl tells you "undefined"
function. Check the @EXPORT and @EXPORT_OK arrays.
 Also remember that undefined functions are runtime error (not found at
compile time). You have forgotten the "use" command.
 If Perl can not locate the .pm file for the module, then do "perl -V" to
check your list of directories being searched for .pm files.
 Another good trick in the debugger is to do "x \%INC" to display module
name and what directory it came from off list of directories.
Reference:
perlmod, perl, perlsub, perlmodlib
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 9 of 30
919 / 490-0090
Example of Module with POD
See Handout Available with Generated HTML Documentation
#---------------------------------------------------#
MODULE:
HandyDandyLib.pm
#---------------------------------------------------#---- START of Plain Old Documentation (POD) -------=head2 NAME
B<HandyDandyLib> - A function library defined in the
HandyDandyLib.pm module.
=head2 SYNOPSIS
use HandyDandyLib;
B<Functions:>
Trim -- Trim leading and trailing whitespace from scalar.
TrimHashValues -- Trim leading/trailing whitespace from a
hash.
GetPgmRevNbr -- Return the program revision number.
B<Prerequisites:> Perl 5.002
=head2 DESCRIPTION
B<HandyDandyLib> provides a library of simple, common utility
functions. These functions are application-independent.
=head2 SOURCE CONTROL INFORMATION
Source file:
$Archive: /Perl/Misc/HandyDandyLib.pm $
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 10 of 30
919 / 490-0090
Last changed by:
$Author: Billc $
Date:
$Date: 5/13/99 12:02a $
Version:
$Revision: 4 $
=head2 DIAGNOSTICS and KNOWN BUGS
None.
=head2 SEE ALSO
None.
=head2 AUTHOR
Bill Cowan.
=cut
#-------------- END of Plain Old Documentation (POD) ---------package HandyDandyLib;
use strict;
<<< SNIP >>>
#-------------------------------------------------------------=head1
=head2 C<TrimHashValues> -- Trim leading/trailing whitespace
from a hash.
$Result = &TrimHashValues(\%HashToTrim);
The C<TrimHashValues> function removes leading and trailing
spaces from all values in a hash. Returns true (one) on
success, and false (zero) on error.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 11 of 30
919 / 490-0090
Parameters:
\%HashToTrim - A reference to the hash to trim.
=cut
sub TrimHashValues { . . . .
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 12 of 30
919 / 490-0090
Perl and Classes/Objects
(Material Heavily Stolen from Perl's Documentation Set)
The Basics …
1. An object is simply a reference that happens to know which class it
belongs to.
2. A class is simply a Perl package that happens to provide methods to deal
with object references.
3. A method is simply a subroutine that expects an object reference as the
first argument. For class methods, the package name (e.g. class name) is
expected as the first argument.
Method invocations for Employee class:
 Class method using Employee class name, e.g. constructor:
$EmpObj = Employee->new($Name, $SSN, $Salary);
 Object method using object reference $EmpObj:
$NewSalary = $EmpObj->SalaryIncrease(0.10);
 Opinion: There is "indirect syntax" for method calls that I decided to
forget several months ago, e.g. "$db = new Win32::ODBC $DSN;".
My preferred or standard is "$db = Win32::ODBC->new($DSN;".
Reference:
perlobj and perltoot have excellent discussions.
Suggestions:
 If you understand references and modules, you can probably spend your
time with perltoot and not purchase a book for Perl O-O.
 My observation is that books usually provide one chapter on how to write
classes. I had to pull different things from different books.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 13 of 30
919 / 490-0090
Example of Simple Class from perltoot
What caller's code may look like:
use Person;
# class name.
$him = Person->new();
# constructor as class method
$him->name("Jason");
# data assessor to set value
$age = $him->age();
# data assessor to get value
$him->peers( "Norbert", "Rhys", "Phineas" );
What the module would look like to implement this class:
package Person;
use strict;
sub new {
my $Class = shift; # Get class name.
my $Obj
= {};
# Set object as hash reference.
$Obj->{NAME}
= undef;
$Obj->{AGE}
= undef;
$Obj->{PEERS}
= [];
bless $Obj, $Class; # Bless hash ref into class.
return $Obj;
}
sub name {
my $Obj = shift;
# Get object reference.
if (@_) { $Obj->{NAME} = shift }
return $Obj->{NAME};
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 14 of 30
919 / 490-0090
}
sub age {
my $Obj = shift;
if (@_) { $Obj->{AGE} = shift }
return $Obj->{AGE};
}
sub peers {
my $Obj = shift;
if (@_) { @{ $Obj->{PEERS} } = @_ }
return @{ $Obj->{PEERS} };
}
1;
# so the require or use succeeds
A faster, but less readable implementation of data assessor method is:
sub age {
my $Obj = shift;
return $Obj->{age} = $_[0] if (@_ > 0);
return $Obj->{age};
#-- set.
#-- get attribute value.
}
Still faster, but even less readable implementation is:
sub age {
my $Obj = shift;
return (@_ > 0
? $Obj->{age}
# parm to set value.
: $Obj->{age}
# no parm; get value.
);
}
Suggestions:
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 15 of 30
919 / 490-0090
 I prefer $Obj as standard convention, instead of $self as is often used.
 Use $Obj->{age} for public members, $Obj->{ _age} for private
members by using the underscore convention.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 16 of 30
919 / 490-0090
How to Represent an Object
 Hashes are often used to implement the object, i.e. object reference is a
blessed hash reference.
 Hashes are easy to use for storing public and private members of the
object (also called instance variables). This has worked well in all of my
development.
 Nothing requires objects to be implemented as hash references. An object
can be any sort of reference so long as its reference has been suitably
blessed.
 That means scalar, array, and code references are also fair game.
 A scalar would work if the object has only one datum to hold.
 An array would work for most cases, but makes inheritance a bit dodgy
because you have to invent new indices for the derived classes.
 A code reference may be too exotic; I have not tried that.
Suggestion:
Good discussion of this in perltoot.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 17 of 30
919 / 490-0090
Categories for Methods
 Constructors such as $EmpObj = Employee->new($Name);
 Often new() method creates object and initializes member values.
 Other times the constructor just creates an empty object.
 And uses 2nd method to initialize/allocate info for the object.
 Depends on your application…
 Data assessors to get/set public members of the object such as:
 $Salary = $EmpObj->Salary(); # to get value
 $NewSalary = $EmpObj->Salary($Salary * 1.1); # 10% increase
 Sometime data assessor methods are implemented as:
 $Salary = $EmpObj->get('Salary'); # to get value
 $NewSalary = $EmpObj->set('Salary', $Salary * 1.1); # to set
 Opinion: I prefer the first style as being more compact. But
main thing is to choose one consistent style to use.
 Workhorse methods (probably object methods) to perform most of the
functions needed by the application. Probably where you do most of the
new coding.
 Support methods such as ErrorMsg(), Debug(), PrintObj() that you tend to
find in many classes. This is where you can inherit common support or
utility methods from a common base class. That is, utility library class.
 Destructor such as $EmpObj->DESTROY(); to remove an employee. Note
DESTROY method is automatically called by Perl.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 18 of 30
919 / 490-0090
Expanded Example with Inherited Utility Class
#----------------------------------------------------#
MODULE:
Employee.pm
#
#----------------------------------------------------..... <<Similar to Module POD>> .....
#----------- END of Plain Old Documentation (POD) ---package Employee;
use strict;
#-- Note no use of Exporter.pm module.
use UtilMethods;
# Utility methods to inherit.
#-- Must be package globals (not file-scoped 'my').
use vars qw(@ISA $VERSION);
@ISA
= qw(UtilMethods);
#-- Classes to inherit.
#----------------------------------------------------#
Class Variables
# Some as package global variables for access by
#INHERITED class methods:
use vars qw($DEBUG @_LastErrorListG);
$DEBUG = 0;
#-- Control debug display.
@_LastErrorListG = ();
#-- Save last set of msg.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 19 of 30
919 / 490-0090
#----------------------------------------------------#
Declare 'my' Variables with File Scope Here
#-- Legal list of user-visible attributes
#-- and default values to build part
#-- of "instance variables" for each object.
my %ExternalAttributesG = (
Attribute1
=> 'value',
Attribute2
=> 'value',
);
#-- Internal attributes (not changed by user)
#-- and default values to build
#-- part of the "instance varibles" for each object.
my %InternalAttributesG = (_Attribute3 => 'value');
=head1 METHODS
=head2 C<new> -- Constructor Method
my $Obj = Employee->new(
Attribute1
=> 'aaaaa',
#-- named parameters.
Attribute2
=> 'zzzzz',
#-- named parameters.
);
if (not defined $Obj) {
#-- Class method to call if "new" fails.
print STDERR Employee->ErrorMsg(), "\n";
}
Edit here to describe method.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 20 of 30
919 / 490-0090
C<Attribute1> -- Explanation of this named parameter.
=cut
sub new {
my $Class = shift;
#-- class or method call?
$Class = (ref($Class) or $Class or 'Employee');
#-- Create hash-based object w/attr & defaults.
#-- More attributes added by_AllocateObject.
my $Obj = $Class>_AllocateObject(
\%ExternalAttributesG,
\%InternalAttributesG);
bless $Obj, $Class;
$Obj->_PushErrorMsg(undef); Clear old messages.
#-- Validate all named parameters. Then replace
#-- any object's attr values with NAMED parms.
if (not $Obj->_ReplaceAttributes(\@_, 1)) {
return undef;
}
#-- Update other attributes for this object.
return $Obj;
} #-- end of 'new' method.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 21 of 30
919 / 490-0090
=head2 C<age> -- Data accessor method
$CurrentValue = $Obj->age();
#-- get attribute.
$UpdatedValue = $Obj->age($NewValue);
#-- set.
For either get or set operation, the value of the
attribute is returned.
=cut
sub age {
my $Obj = shift;
#-- If another parm present, write attr value.
#-- Either way, attr value returned.
return $Obj->{age} = $_[0] if (@_ > 0); #-- write.
return $Obj->{age};
#-- read attribute.
} #-- end of get/set age method.
1; #-- end of module Employee.pm; modules must return
true.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 22 of 30
919 / 490-0090
Sample Methods from Utility Class
=head2 C<ErrorMsg> -- Retrieve list of error messages
previously stored.
@Messages = $Obj->ErrorMsg();
#-- list context.
$LastMessage = $Obj->ErrorMsg(); # scalar context.
@Messages = $Class->ErrorMsg();
#-- Class methods
$LastMessage = $Class->ErrorMsg();
....
=cut
sub ErrorMsg {
my $Obj = shift;
#-- Parameters:
my( $ConcatenateMsgs
#-- Optional boolean.
) = @_;
if (ref($Obj)) {
#-- Being used as an object method.
$ArrayRef = $Obj->{__ErrorMsgList};
} else {
#-- No object reference; class method.
my $Class = $Obj;
#-- Really class!
#-- Get last set of errors for this class.
no strict 'refs';
$ArrayRef = \@{"${Class}::_LastErrorListG"} ;
}
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 23 of 30
919 / 490-0090
.....
} #-- end of ErrorMsg method.
=head2 C<_ReplaceAttributes> -- Use named parameters
to update object variables for object.
$OK = $Obj->_ReplaceAttributes( \@_ );
Validate any named parameter is legal for this class.
If all parameters are valid, replace the value for the
corresponding object variable (attribute). Return
boolean to show valid or invalid parameters. Method
will do error return for any invalid parameters found.
=cut
sub _ReplaceAttributes {
my $Obj = shift;
....
return($OK);
} #-- end of _ReplaceAttributes method.
=head2 C<SelfDisplay> -- Display contents of object.
$Obj->SelfDisplay($Title);
$Obj->SelfDisplay(); #-- suppress title line.
$Obj->SelfDisplay($Title, 1); #-- compact display.
....
=cut
sub SelfDisplay {
my $Obj = shift;
....
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 24 of 30
919 / 490-0090
return;
} #-- end of SelfDisplay method.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 25 of 30
919 / 490-0090
=head2 C<Debug> -- Read or write the "debug" setting.
$OldValue = $Obj->Debug();
$NewValue = $Obj->Debug($Level);
$OldValue = $Class->Debug();
$NewValue = $Class->Debug($Level);
=cut
sub Debug {
my $Obj
= shift;
....
} #-- end of Debug method.
This method will be automatically called when object goes out of scope or is
undef-ed.
sub DESTROY {
my $Obj = shift;
$Obj->DebugMsg("Destroying " . ref($Obj) . " class
for object $Obj...");
undef $Obj;
return;
} #-- end of DESTROY method
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 26 of 30
919 / 490-0090
AUTOLOADed Data Assessors
=head2 C<Get/Set Attribute>-- Data accessor methods.
$CurrentValue = $Obj->Attribute();
$UpdatedValue = $Obj->Attribute($NewValue);
=cut
sub AUTOLOAD {
my $Obj
= shift;
my $Class = ref($Obj)
or confess "DIE: \$Obj '$Obj' not object\n\t";
#-- $AUTOLOAD has "Package::Method" to invoke,
#-- Package may also include '::' separators also.
my $MethodName = $AUTOLOAD;
return if $MethodName =~ m/::DESTROY$/;
$MethodName =~ s/^.+:://;
Remove package name.
#-- Autoload for get/set attribute method.
if (exists $Obj->{__Permitted}->{$MethodName} ) {
#-- Autoload get/set for object attribute.
#-- If another parm, then write attribute.
return $Obj->{$MethodName} = $_[0]
if (@_ > 0);
#-- write attribute.
return $Obj->{$MethodName}; # read attribute.
} else {
#-- Use confess to "die" since this is
#--"undefined function" also.
confess "DIE: '$MethodName' method undefined";
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 27 of 30
919 / 490-0090
}
} #-- end of AUTOLOAD method.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 28 of 30
919 / 490-0090
Candidates to Consider for Utility Class
1. Error message methods -- Class or object methods. For caller and internal.
And handle list of error messages.
2. Validate access for public members of object to get/set values. Also
default values for public and internal members of cless.
3. Diagnostic methods such as setting flags to enable/disable debugging
code. Also to print out contents of object on requests (See Data::Dumper.)
4. Display of debug data conditionally based on debug settings.
5. Support easy use of named parameters with default values.
6. Standard initialization of common values to each object, if needed.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 29 of 30
919 / 490-0090
Productivity Thoughts to Code Classes
1. Use a utility class as a base class in order to inherit common methods
such as error handling or debugging.
2. Evaluate CPAN tools to quickly develop classes -- especially data
assessors -- such as Class::Struct in perltoot. Class::Struct is useful if you
want to use for object as a data structure. See Class::xxxxx on CPAN.
3. Develop template or skeleton files for classes and/or methods to save
keystrokes and encourage consistency.
 Also evaluate editor macros and templates.
 I am currently playing with Perl template module as tool to generate and
fill-in values for methods.
4. Add __END__ to end of .pm file for the module. You can then place test
cases there and later enable by removing __END__.
Metagenix, Inc.
www.metagenix.com
Leading Way in Data Migration
Page 30 of 30
919 / 490-0090
Download