Teach-In Extract - OO Perl

Dave Cross <dave@mag-sol.com>

Magnum Solutions Ltd

Object Oriented Perl

  • How to design objects

  • Design Patterns

  • Inside-Out Objects

  • Moose

What We Won't Cover

  • Writing objects in Perl

  • Assume you already know that

    • perldoc perlboot

How To Design Objects

  • Designing Objects is *hard* - OO Barbie

  • Very few hard and fast rules

  • A few heuristics

  • A bit of experience

  • A bit of guesswork

Prototyping

  • 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

Subclassing

  • A good class is easy to subclass

  • Two argument bless

  •   sub new {
        my $class = shift;
        my $self = {};
        return bless $self, $class;
      }

Constructor Tip

  • You'll often see code like this

  •   sub new {
        my $thing = shift;
        my $class = ref $thing || $thing;
        return bless {}, $class;
      }
  • Don't do that

Confusing Methods

  • 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?

Separate Constructors

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

Multiple Constructors

  • 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 Keyword

  • People 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;

Overriding Methods

  • 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

Overriding Methods Case Study

  • I needed to create a new graph type for GD::Graph

  • Waterfall graph

  • Based on bar chart

  • Subclassing GD::Graph::bars

Bar Chart

Waterfall Chart

Things to Override

  • Colour choosing

    • pick_data_clr

  • Legend drawing

    • ???

  • The author didn't envisage my use case

  • I ended up copying far too much code

Be Consistent

  • A good object design is consistent

  • Similar things act in similar ways

  • Good candidates for programming standards

  • Some examples

Accessor vs Mutator

  • 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

Mutator Return Values

  • What does a mutator return?

    • The old value

    • The new value

    • The object

    • Nothing

  • Pick one and stick to it

Don't Always Subclass

  • 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

Design Patterns in Perl

  • Perl does have some design patterns

  • Some of them come from Design Patterns

  • Some are more "Perlish"

Factory Pattern

  • 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

AudioFile::Info

  • 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

Using AudioFile::Info

  • 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

AudioFile::Info::new

  • 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

Singleton Pattern

  • 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

Singleton Class

  package MySingleton;
  my $single;
  sub new {
    my $class = shift;
    unless ($single) {
      $single = bless {}, $class;
    }
    return $single;
  }

More Design Patterns

Inside-Out Objects

  • 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

Accessing Attributes Directly

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

Adding Attributes Easily

  • 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

  • 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

Example

  package Person;
  use strict;
  my %name;
  my %email;
  sub new {
    my $self = bless {}, shift;
    $name{$self} = shift;
    $email{$self} = shift;
    return $self;
  }

Example (cont)

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

How It Works

  • 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

Solving The Problems

  • 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

One Improvement

  • 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

One New Problem

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

More

  • Need to handle inheritance

  • Automation of inside-out objects

    • Class::Std

    • Class::InsideOut

    • Object::InsideOut

  • See also Perl Best Practices

Moose

  • 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

Standard Moose Example

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

Understanding Moose

  • 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

Creating Attributes

  •   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')

Defining Methods

  •   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

Subclassing

  package Point3D;
  use Moose;
  
  extends 'Point';
  
  has 'z' => (isa => 'Int');
  
  after 'clear' => sub {
      my $self = shift;
      $self->{z} = 0;
  };

Understanding Subclasses

  • extends 'Point'

    • Similar to use base

    • Overwrites @ISA instead of appending

  •   has 'z' => (isa => 'Int')
    • Adds new attribute 'z'

    • No accessor function - private attribute

Extending Methods

  •   after 'clear' => sub {
          my $self = shift;
          $self->{z} = 0;
      };
  • New clear method for subclass

  • Called after method for superclass

  • Cleaner than $self->SUPER::clear()

Creating Objects

  • 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);

More Moose

  • Only scratching the surface

  • Many more options

  • Moose is well worth investigating

  • perldoc Moose::Cookbook::*

Coffee Break

  • Back in 15 minutes