I am working on learning to write objects myself. The following is a draft document I am writing to document what I am finding in the documentation and in posts and emails. It's related to wxPerl, but maybe it will be of use to you as well. Also,take a look at my wxPerl LCD Clock post to see the direction I am going at the moment.
Update: Added link to clock post.
A Bridge to a Real wxPerl Application(Draft)
--------------------------------------------
Compiled and/or written by:
James M. Lynes Jr.
Last Modified: February 4, 2013
Introduction
------------
In my process of learning Perl and wxPerl over the past two years, I h
+ave noticed that there
is quite a gap between the way things are done in the "hello world" ty
+pe of example and
the way things are done in a real application. In many cases, the exam
+ples available to learn
from are nicely packaged into large applications since there are so ma
+ny modules to be
presented. Unfortunately this packaging adds complexity over and above
+ that needed by the
module itself. In addition these examples are written by very experien
+ced developers
using all of the "obscure" Perl syntax. The available documentation is
+ 2200+ pages
of the wxWidgets C++ usage and many of the examples are sparsely comme
+nted. Fortunately,
over time, the C++ usage gets easier to read and the documentation bec
+omes easier
to use. It's a steep hill for a new learner to climb.
In order to assist in my learning, I ported many of the "wxBook" C++ e
+xamples to wxPerl. I
tried to use the most stripped down program structure that I could tha
+t would illustrate
only the function being demonstrated, focusing on readability over eff
+iciency.
Now I am looking to start combining these elements into a "real" appli
+cation. And even though
I tend to lean towards readability, it's time for some of the "obscure
+" Perl syntax to
begin to be blended in. I'm sure my coding style will evolve as more o
+f these idioms
are included in my applications. I intend to document my findings in t
+his document.
Maybe it will speed up your learning curve too.
Basic Application Structure
---------------------------
The following is a basic structure for a wxPerl application. Some prog
+rammers
like to reverse the order of the packages shown.
package main; # Program initialization
use strict;
use warnings;
my $app = App->new(); # Create the applicatio
+n object
$app->MainLoop; # Start event processing
package App; # Application initialization
use strict;
use warnings;
use base 'Wx::App'; # Inherit from Wx::App ob
+ject
sub OnInit { # Called from $app automatic
+ally
my $frame = Frame->new(); # Create the top le
+vel window
$frame->Show(1); # Display the top level
+window
}
package Frame; # Create the top level win
+dow
use strict;
use warnings;
use Wx qw(:everything); # Lazy way to pull in
+ references
use base qw(Wx::Frame); # Inherit from Wx::Fr
+ame object
use Wx::Event(EVT_A EVT_B EVT_3 etc); # List required
+ events
# use other modules as needed by your application - Core, CPAN,
+or custom
sub new {
my ($self) = @_;
# Create top level window- (SUPER refers to Wx::Frame)
$self = $self->SUPER::new(undef, wxID_ANY, "Window Title", w
+xDefaultPosition, wxDefaultSize);
# Attach required events(listed above) to an event handler -
+ simple format - see below
EVT_A($self, \&onAhandler); # $self is the wi
+ndow/subwindow
EVT_B($self, \&onBhandler); # that is to rece
+ive this event
EVT_C($self, \&onChandler);
# Create subwindows, controls, sizers, etc. as needed by the
+ application
return $self; # Return the top level
+window object
}
1; # True, so App continues processin
+g
sub onAhandler { # One handler per event
+above
my ($self, $event) = @_;
# Process the event here
}
sub onBhandler { # One handler per event
+above
my ($self, $event) = @_;
# Process the event here
}
sub onChandler { # One handler per event
+above
my ($self, $event) = @_;
# Process the event here
}
# Other subroutines as needed by the application
Alternate Event Handler - can be overridden in a derived class(from a
+Mark Dootson email)
----------------------------------------------------------------------
+-------------------
EVT_A($self, sub{shift->_evt_on_Ahandler(@_);})
_evt_on_Ahandler {
my($self, $event) = @_;
$self->SUPER::_evt_on_Ahandler($event);
# Extra code here for a derived class
}
Application Flow
----------------
The flow of an application is as follows:
1. main creates the application object($app) by calling App->new
+()
2. The application object($app) automatically calls App::OnInit(
+)
3. OnInit calls Frame->new() to create the top level window
4. Frame->new() creates the top level window, subwindows, contro
+ls,
sizers, event handlers, etc. as needed by the application
5. Frame returns the top level window object, $self, to OnInit
6. OnInit displays the top level window($self) by calling $frame
+->Show(1)
7. OnInit returns to main
8. main starts the MainLoop(which processes events) by calling $
+app->MainLoop
(it's all event processing from here on out, steps 1-8 aren't
+ called again)
Object Creation - Basic to Actual Practice
------------------------------------------
C Structure Emulation(from Perltoot)
------------------------------------
By far the most common mechanism used in Perl to represent a Pa
+scal
record, a C struct, or a C++ class is an anonymous hash. That'
+s
because a hash has an arbitrary number of data fields, each
conveniently accessed by an arbitrary name of your own devising
+.
If you were just doing a simple struct-like emulation, you woul
+d likely
go about it something like this:
$rec = {
name => "Jason",
age => 23,
peers => [ "Norbert", "Rhys", "Phineas"],
};
If you felt like it, you could add a bit of visual distinction
+by up-
casing the hash keys:
$rec = {
NAME => "Jason",
AGE => 23,
PEERS => [ "Norbert", "Rhys", "Phineas"],
};
And so you could get at "$rec->{NAME}" to find "Jason", or
"@{$rec->{PEERS} }" to get at "Norbert", "Rhys", and "Phineas".
Basic Class Structure/Creation(from Perltoot) - This version does not
+support Inheritance
----------------------------------------------------------------------
+------------------
Still, someone has to know what's in the object. And that some
+one is
the class. It implements methods that the programmer uses to a
+ccess
the object. Here's how to implement the Person class using the
standard hash-ref-as-an-object idiom. We'll make a class metho
+d called
new() to act as the constructor, and three object methods calle
+d
name(), age(), and peers() to get at per-object data hidden awa
+y in our
anonymous hash.
package Person;
use strict;
##################################################
## the object constructor (simplistic version) ##
##################################################
sub new {
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless($self); # See below
return $self;
}
##############################################
## methods to access per-object data ##
## (hand coded Accessors) ##
## With args, they set the value. Without ##
## args, they retrieve the value. ##
##############################################
sub name {
my $self = shift;
if (@_) { $self->{NAME} = shift }
return $self->{NAME};
}
sub age {
my $self = shift;
if (@_) { $self->{AGE} = shift }
return $self->{AGE};
}
sub peers {
my $self = shift;
if (@_) { @{ $self->{PEERS} } = @_ }
return @{ $self->{PEERS} };
}
1; # True, so the require or use succeeds
Basic Class with support for inheritance(from Perltoot)
-------------------------------------------------------
Even though at this point you may not even know what it means,
+someday
you're going to worry about inheritance. (You can safely ignor
+e this
for now and worry about it later if you'd like.) To ensure tha
+t this
all works out smoothly, you must use the double-argument form o
+f
bless(). The second argument is the class into which the refer
+ent will
be blessed. By not assuming our own class as the default secon
+d
argument and instead using the class passed into us, we make ou
+r
constructor inheritable.
sub new {
my $class = shift;
my $self = {};
$self->{NAME} = undef;
$self->{AGE} = undef;
$self->{PEERS} = [];
bless ($self, $class);
return $self;
}
That's about all there is for constructors. These methods brin
+g
objects to life, returning neat little opaque bundles to the us
+er to be
used in subsequent method calls.
Automagically generate accessors/mutators for your class(from Class::A
+ccessor)
----------------------------------------------------------------------
+--------
Most of the time, writing accessors is an exercise in cutting a
+nd pasting.
You usually wind up with a series of methods like this:
sub name {
my $self = shift;
if(@_) {
$self->{name} = $_[0];
}
return $self->{name};
}
sub salary {
my $self = shift;
if(@_) {
$self->{salary} = $_[0];
}
return $self->{salary};
}
# etc...
One for each piece of data in your object. While some will be
+unique,
doing value checks and special storage tricks, most will simply
+ be exercises
in repetition. Not only is it Bad Style to have a bunch of rep
+etitious code,
but it's also simply not lazy, which is the real tragedy.
If you make your module a subclass of Class::Accessor and decla
+re your
accessor fields with mk_accessors() then you'll find yourself w
+ith a set of
automatically generated accessors which can even be customized!
The basic set up is very simple:
package Foo;
use base qw(Class::Accessor);
Foo->mk_accessors( qw(far bar car) );
Done! Foo now has simple far(), bar() and car() accessors defi
+ned.
Class::Accessor also provides a basic constructor, "new". It g
+enerates a hash-based object
and can be called as either a class method or an object method.
my $obj = Foo->new;
my $obj = $other_obj->new;
my $obj = Foo->new(\%fields);
my $obj = $other_obj->new(\%fields);
It takes an optional %fields hash which is used to initialize t
+he object.
The fields of the hash correspond to the names of your accessor
+s, so...
package Foo;
use base qw(Class::Accessor);
Foo->mk_accessors('foo');
my $obj = Foo->new({ foo => 42 });
print $obj->foo; # 42
However %fields can contain anything, new() will shove them all
+ into your object.
Accessor Usage with wxPerl(from a Mark Dootson email)
-----------------------------------------------------
It is generally accepted that accessing the members of an objec
+t directly
by their hash keys is bad practice. So,
$object->{something} ; # is bad
$object->something() ; # is good
Apart from all the OO theory that says $object->something is th
+e correct thing to have,
in practical terms the major disadvantage of the direct hash ac
+cess is that errors
in your code are more difficult to debug / catch.
For example, if I misspell the accessor name
$object->{somting} ;
$object->somting() ;
Then for the hash based access I get no error and the type of w
+arning will be dependent on context.
For the method based accessor ( $object->somting ) I always get
+ 'no method named somting' error.
The plain Perl method for creating the method would be along th
+e lines of
sub something {
my $self = shift;
$self->{_something} = shift if(@_);
$self->{_something};
}
The module 'Class::Accessor' just gives a few shorthand ways of
+ doing this and
I wanted to demonstrate that you don't have to do the longhand
+code above.
Class::Accessor also claims a speed increase for the accessors
+it creates.
*** However, we have a problem incorporating this into Wx. ***
Class::Accessor relies on an inheriting class calling its 'new'
+ method. But we
also need to call the 'new' method of Wx::Window so we inherit
+from that.
There are various ways of working around this, but there isn't
+one that would be
regarded as a 'standard' as far as I am aware. So rather than s
+ubject you
to some possibly confusing code, I just put the data into a sep
+arate class.
For example:
###############################################################
+##########################
package Meter::Data;
###############################################################
+##########################
use strict;
use warnings;
use Class::Accessor::Fast;
use base qw( Class::Accessor::Fast );
# Create the Accessors
__PACKAGE__->mk_accessors( qw( ActiveBar PassiveBar ValueColour
+ BorderColour
LimitColour TagsColour ScaledVal RealVal Max Min DirOrizFlag Sh
+owCurrent
ShowLimits Font Tags ) );
# Create the Object
sub new { shift->SUPER::new( @_ ); }
1;
Class::Accessor Usage Example in wxPerl
---------------------------------------
See the comments below relating to the BEGIN block. Only needed
+ due to the
structure of this example - multiple packages within the same s
+ource file.
#! /home/xxxx/CitrusPerl/perl/bin/perl
# Test of Class::Accessor::Fast
package main;
use strict;
use warnings;
my $app = App->new();
$app->MainLoop;
package App;
use strict;
use warnings;
use base 'Wx::App';
sub OnInit {
my $frame = Frame->new();
$frame->Show(1);
}
package Frame;
use strict;
use warnings;
use Wx qw(:everything);
use base qw(Wx::Frame);
use Data::Dumper;
sub new {
my($self) = @_;
$self = $self->SUPER::new(undef, -1, "Class::Accessor Test"
+,
wxDefaultPosition, wxDefaultSize)
+;
my %defaults = (
vara => 10,
varb => 20,
varc => 30,
vard => 40,
vare => 50,
);
my $obj = Data->new(\%defaults); # Object create
+d and initialized correctly
print Dumper($obj);
$obj->vara(100); # Use of generated acce
+ssors
$obj->varb(200);
$obj->varc(300);
$obj->vard(400);
$obj->vare(500);
print Dumper($obj);
return $self;
}
1;
BEGIN { # Begin block required since
+ Data is
package Data; # not in a separate module
+. Therefore
use strict; # mk_accessors not run befor
+e being used
use warnings; # and accessor subs would
+not be defined.
use Class::Accessor::Fast; # Normally use Data w
+ould fix this issue
use base qw(Class::Accessor::Fast);
__PACKAGE__->mk_accessors(qw(vara varb varc vard vare));
sub new {shift->SUPER::new(@_);}
1;
}