How to design objects
Design Patterns
Inside-Out Objects
Moose
Writing objects in Perl
Assume you already know that
perldoc perlboot
Designing Objects is *hard* - OO Barbie
Very few hard and fast rules
A few heuristics
A bit of experience
A bit of guesswork
Plan to throw one away; you will, anyhow - Fred Brooks
You won't get it right first time
Unless you're very lucky
Make it easy to refactor your code
Unit tests
Source code control
A good class is easy to subclass
Two argument bless
sub new { my $class = shift;
my $self = {};
return bless $self, $class; }
You'll often see code like this
sub new { my $thing = shift; my $class = ref $thing || $thing;
return bless {}, $class; }
Don't do that
The previous constructor can be called as a class method or an instance method
my $obj = MyClass->new;
my $obj2 = $obj->new;
Potentially confusing
What does $obj->new
do?
Clone?
Class method creates a new, empty, object
sub new { my $class = shift;
return bless {}, $class; }
Instance method creates a copy of an object
sub clone { my $self = shift;
# warning! simplified! buggy! return bless { %$self }, ref $self }
In Perl an constructor is just a subroutine
new
is not a keyword
You can have as many constructors as you want
They can be called whatever you want
But using new
for the standard constructor is recommended
new
is Not a KeywordPeople coming from other languages often write
my $obj = new MyObject;
This is potentially dangerous
See "perldoc perlobj" for gory details
Most of the time it will be fine
But avoid it
my $obj = MyObject->new;
Subclasses are created so that methods can be overridden
Or so that new methods can be added
Make it as easy as possible to subclass your classes
Create many methods
I needed to create a new graph type for GD::Graph
Waterfall graph
Based on bar chart
Subclassing GD::Graph::bars
Colour choosing
pick_data_clr
Legend drawing
???
The author didn't envisage my use case
I ended up copying far too much code
A good object design is consistent
Similar things act in similar ways
Good candidates for programming standards
Some examples
aka "getter vs setter"
Some people like one method foo
Works out what to do based on parameters
Some people like get_foo
and set_foo
Pick one and stick to it
What does a mutator return?
The old value
The new value
The object
Nothing
Pick one and stick to it
Not all relationships are "isa_a"
Sometimes "has_a" is more appropriate
If you're connecting to a database
Don't subclass DBI
Have a DBI attribute
Perl does have some design patterns
Some of them come from Design Patterns
Some are more "Perlish"
Call constructor on one class
Constructor works out what is the most appropriate class to use
Returns an object of the appropriate class
Often not the same class as the constructor was called on
There are many Perl modules for reading tags from MP3 files
MP3::ID3Lib
MP3::Info
MP3::Tag
And a couple for reading tags from Ogg Vorbis files
Ogg::Vorbis::Header
Ogg::Vorbis::Header::PurePerl
AudioFile::Info simplifies reading tag information from audio files
One syntax across numerous modules
$song = AudioFile::Info->new('a_song.mp3'); print $song->title, ' - ', $song->artist;
$song2 = AudioFile::Info->new('a_song.ogg'); print $song2->title, ' - ', $song2->artist;
print ref $song; # AudioFile::Info::MP3::Tag print ref $song2; # AudioFile::Info::Ogg::Vorbis::Header
Works out which kind of file it has been given
Works out the best installed module to handle that kind of file
Loads the appropriate module
Calls the constructor
Returns the new object
Highlander pattern
"There can be only one"
Only ever one instance of the class
If an instance has been created then use that
Else create new instance
package MySingleton;
my $single;
sub new { my $class = shift;
unless ($single) { $single = bless {}, $class; }
return $single; }
See Perl Design Patterns Wiki
Standard Perl objects are usually based on hashes
bless { name => 'Dave', email => 'dave@dave.org.uk' }, 'Person';
Two problems
People can access attributes directly
People can add attributes easily
$person->{name} = '';
Avoids any checks in the mutator method
sub set_name { my $self = shift; my $name = shift;
croak "Name can't be empty" unless $name;
$self->{name} = $name; }
No checks on adding entries to the hash
$person->{nick} = 'davorg';
Our class knows nothing about this attribute
No check on mistyped attributes
$person->{NAME} = 'dave';
Inside-out objects solve both of these problems
An object is no longer a hash containing attributes
Each attribute is a package variable
A hash
Key is unique identifier for object
Value is the attributes value for that object
package Person; use strict;
my %name; my %email;
sub new { my $self = bless {}, shift; $name{$self} = shift; $email{$self} = shift; return $self; }
sub get_name { my $self = shift; return $name{$self}; }
sub set_name my $self = shift; my $name = shift; croak "name cannot be empty" unless $name; $name{$self} = $name; }
The object is still a hash reference
Still blessed into the correct class
But it contains no data
All data is stored in the package variable hashes
Hence the name - Inside-Out
People can access attributes directly
Attributes are now stored in package variables
Only visible from within package
All access is through methods
People can add attributes easily
Attribute names are now the names of package variables
use strict
ensures that variable names can't be mistyped
The object is still a blessed hash
But we never put anything into it
So it may as well be a blessed scalar
sub new { my $self = bless \(my $dummy), shift; $name{$self} = shift; $email{$self} = shift; return $self; }
No anonymous scalars
When our objects go out of scope, the blessed scalar ceases to exist
But the values still exist in the attribute hashes
Need a DESTROY method
sub DESTROY { my $self = shift;
delete $name{$self}; delete $email{$self}; }
Need to handle inheritance
Automation of inside-out objects
Class::Std
Class::InsideOut
Object::InsideOut
See also Perl Best Practices
A complete modern object system for Perl 5
Based on experiments with Perl 6 object model
Built on top of Class::MOP
MOP - Meta Object Protocol
Set of abstractions for components of an object system
Classes, Objects, Methods, Attributes
An example might help
package Point; use Moose; has 'x' => (isa => 'Int', is => 'ro'); has 'y' => (isa => 'Int', is => 'rw'); sub clear { my $self = shift; $self->{x} = 0; $self->y(0); }
There's a lot going on here
use Moose
Loads Moose environment
Makes our class a subclass of Moose::Object
Turns on strict
and warnings
has 'x' => (isa => 'Int', is => 'ro')
Creates an attribute called 'x'
Contrainted to be an integer
Read-only accessor
has 'y' => (isa => 'Int', is => 'rw')
sub clear { my $self = shift; $self->{x} = 0; $self->y(0); }
Standard method syntax
Uses generated method to set y
Direct has access for x
package Point3D; use Moose; extends 'Point'; has 'z' => (isa => 'Int'); after 'clear' => sub { my $self = shift; $self->{z} = 0; };
extends 'Point'
Similar to use base
Overwrites @ISA instead of appending
has 'z' => (isa => 'Int')
Adds new attribute 'z'
No accessor function - private attribute
after 'clear' => sub { my $self = shift; $self->{z} = 0; };
New clear
method for subclass
Called after method for superclass
Cleaner than $self->SUPER::clear()
Moose classes are used just like any other Perl class
$point = Point->new(x => 1, y => 2);
$p3d = Point3D->new(x => 1, y => 2, x => 3);
Only scratching the surface
Many more options
Moose is well worth investigating
perldoc Moose::Cookbook::*
Back in 15 minutes