Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Re: RFC on how I'm doing when it comes to writing objects?

by jmlynesjr (Deacon)
on Feb 04, 2013 at 17:20 UTC ( [id://1016982]=note: print w/replies, xml ) Need Help??


in reply to RFC on how I'm doing when it comes to writing objects?

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; }

James

There's never enough time to do it right, but always enough time to do it over...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1016982]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (5)
As of 2024-03-19 08:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found