Object-Oriented Perl¶
From bless to Moose: Building Reusable Abstractions¶
Version: 1.1 Year: 2026
Copyright Notice¶
Copyright (c) 2025-2026 Ryan Thomas Robson / Robworks Software LLC. Licensed under CC BY-NC-ND 4.0. You may share this material for non-commercial purposes with attribution, but you may not distribute modified versions.
Perl's object system is unlike most languages. There is no class keyword, no built-in new method, and no special syntax for declaring attributes. Instead, Perl OOP is built on three concepts you already know: packages (namespaces), references (data), and subroutines (behavior). The bless function connects a reference to a package, and that connection is the entire foundation of Perl objects.
This minimal design gives you full control - and full responsibility. You can build a class in five lines or layer on a sophisticated metaobject protocol with Moose. This guide covers both ends of that spectrum: the manual mechanics that every Perl programmer needs to understand, and the modern tools that make OOP productive.
bless and Constructors¶
An object in Perl is a reference that has been associated with a package using bless. That package becomes the object's class, and its subroutines become the object's methods.
The bless Function¶
bless takes a reference and a class name (package name) and returns the reference, now tagged with that class:
package Dog;
sub new {
my ($class, %args) = @_;
my $self = {
name => $args{name} // 'Unknown',
breed => $args{breed} // 'Mixed',
};
bless $self, $class;
return $self;
}
Here is what happens step by step:
$classreceives the string"Dog"(the invocant - explained below)%argsabsorbs the remaining arguments as key-value pairs$selfis an anonymous hash reference holding the object's databless $self, $classtags that hash reference as aDogobject- The blessed reference is returned to the caller
Calling the Constructor¶
The ref function returns the class name of a blessed reference, or the reference type (HASH, ARRAY, etc.) for unblessed references.
Why a hash reference?
You can bless any reference - array refs, scalar refs, even code refs. Hash references are the convention because they give you named fields ($self->{name}) that are readable and extensible. Array-based objects use less memory but sacrifice clarity.
Methods and the Invocant¶
A method is a subroutine that expects an object (or class name) as its first argument. Perl passes this automatically when you use arrow notation.
Instance Methods¶
When you call $obj->method(), Perl translates it to Package::method($obj). The object is passed as the first argument, conventionally called $self:
package Dog;
sub speak {
my ($self) = @_;
print "$self->{name} says: Woof!\n";
}
sub describe {
my ($self) = @_;
printf "%s is a %s\n", $self->{name}, $self->{breed};
}
Class Methods¶
Class methods receive the class name as the first argument instead of an object. The constructor new is the most common class method:
package Logger;
sub new {
my ($class, %args) = @_;
return bless { level => $args{level} // 'info' }, $class;
}
# Another class method
sub default {
my ($class) = @_;
return $class->new(level => 'warn');
}
shift vs Explicit Unpacking¶
Two common styles for extracting the invocant:
# Style 1: shift (common in short methods)
sub name {
my $self = shift;
return $self->{name};
}
# Style 2: list unpacking (common when there are other arguments)
sub set_name {
my ($self, $new_name) = @_;
$self->{name} = $new_name;
}
Both are idiomatic. Use shift for simple accessors and list unpacking when you have additional parameters.
Accessor Methods¶
Direct hash access ($obj->{name}) exposes your object's internals. Accessor methods provide a controlled interface:
Read-Only Accessors¶
sub name {
my ($self) = @_;
return $self->{name};
}
sub breed {
my ($self) = @_;
return $self->{breed};
}
Read-Write Accessors¶
A combined getter/setter uses the argument count to decide behavior:
print $dog->name; # getter: returns "Rex"
$dog->name("Buddy"); # setter: changes name to "Buddy"
print $dog->name; # getter: returns "Buddy"
Generating Accessors¶
Writing the same accessor pattern for every field is tedious. You can generate them:
package Animal;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
# Generate accessors for each field
for my $field (qw(name species weight)) {
no strict 'refs';
*{"Animal::$field"} = sub {
my $self = shift;
$self->{$field} = shift if @_;
return $self->{$field};
};
}
This loop installs a subroutine into the package's symbol table for each field name. The closure captures $field, so each generated method accesses the correct hash key.
no strict 'refs'
The no strict 'refs' directive is required when manipulating the symbol table with string references. Limit its scope to the smallest block possible. In production, prefer Moose or Moo (covered below) which handle accessor generation safely.
Inheritance¶
Inheritance in Perl is controlled by the @ISA array. When you call a method on an object, Perl first looks in the object's class. If the method is not found there, Perl searches through the classes listed in @ISA, in order.
The @ISA Array¶
package Animal;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
sub speak {
my ($self) = @_;
print $self->{name} . " makes a sound\n";
}
sub describe {
my ($self) = @_;
printf "%s is a %s\n", $self->{name}, $self->{species};
}
package Dog;
our @ISA = ('Animal');
sub speak {
my ($self) = @_;
print $self->{name} . " says: Woof!\n";
}
package Cat;
our @ISA = ('Animal');
sub speak {
my ($self) = @_;
print $self->{name} . " says: Meow!\n";
}
Dog and Cat inherit new and describe from Animal, but override speak with their own implementations.
use parent (Preferred)¶
Manually setting @ISA works, but the use parent pragma is the modern way:
package Dog;
use parent 'Animal';
sub speak {
my ($self) = @_;
print $self->{name} . " says: Woof!\n";
}
use parent sets @ISA at compile time and loads the parent module if it has not been loaded yet. An older pragma, use base, does the same thing but has quirks around failed module loading. Prefer use parent.
Class Hierarchy Diagram¶
classDiagram
Animal <|-- Dog
Animal <|-- Cat
Animal : +name
Animal : +species
Animal : +new()
Animal : +speak()
Animal : +describe()
Dog : +speak()
Dog : +fetch()
Cat : +speak()
Cat : +purr()
SUPER:: and Method Resolution¶
When a subclass overrides a parent method but still needs the parent's behavior, use SUPER:::
package Dog;
use parent 'Animal';
sub speak {
my ($self) = @_;
$self->SUPER::speak(); # call Animal::speak
print "(tail wagging)\n"; # add Dog-specific behavior
}
SUPER::speak() calls the speak method from the current package's parent class. This is resolved at compile time based on the package where SUPER:: appears, not the class of the object.
SUPER:: is package-relative
SUPER:: looks up the parent based on the package where the call is written, not the runtime class of $self. In deep inheritance chains, this distinction matters. For more flexible dispatch, see the next::method approach from the mro module.
Method Resolution Order (MRO)¶
When a class has multiple parents (multiple inheritance), Perl needs a strategy to decide which parent to search first. The default is depth-first left-to-right (DFS), but Perl 5.10 introduced the C3 linearization algorithm:
The mro module controls method resolution order. C3 linearization prevents the "diamond problem" where a class could inherit the same method through two different paths, with ambiguous results.
sequenceDiagram
participant main
participant Dog
participant Animal
main->>Dog: $dog->speak()
Note over Dog: Look in Dog package
Dog->>Dog: Found Dog::speak
Dog->>Animal: SUPER::speak()
Note over Animal: Look in Animal package
Animal->>Animal: Found Animal::speak
Animal-->>Dog: Returns
Dog-->>main: Returns
isa and can¶
Two introspection methods tell you about an object's capabilities:
isa checks whether an object belongs to a class (or inherits from it):
my $dog = Dog->new(name => 'Rex');
print $dog->isa('Dog'); # 1 (true)
print $dog->isa('Animal'); # 1 (true - Dog inherits from Animal)
print $dog->isa('Cat'); # "" (false)
can checks whether an object has a particular method available:
print $dog->can('speak'); # returns a code reference (true)
print $dog->can('fly'); # undef (false)
# can() returns the code ref, so you can call it
if (my $method = $dog->can('speak')) {
$dog->$method();
}
Both isa and can are provided by the UNIVERSAL class, which is the implicit base class of every Perl object.
DESTROY and Destructors¶
When an object goes out of scope or its last reference is removed, Perl calls the DESTROY method on it (if one exists). This is the destructor:
package TempFile;
sub new {
my ($class, %args) = @_;
my $self = bless {
path => $args{path},
}, $class;
open $self->{fh}, '>', $self->{path}
or die "Cannot create $self->{path}: $!\n";
return $self;
}
sub DESTROY {
my ($self) = @_;
close $self->{fh} if $self->{fh};
unlink $self->{path} if -e $self->{path};
print "Cleaned up $self->{path}\n";
}
{
my $tmp = TempFile->new(path => '/tmp/work.dat');
# ... use $tmp ...
}
# $tmp goes out of scope here - DESTROY is called automatically
DESTROY Rules¶
- Perl calls
DESTROYautomatically - never call it manually DESTROYis called on each object exactly once- The order of destruction for global objects at program exit is undefined
dieinsideDESTROYis silently caught (it sets$@but does not propagate)- If a subclass needs cleanup, call
$self->SUPER::DESTROY()in its destructor
Circular references prevent DESTROY
If object A references object B and object B references object A, neither will be destroyed until the program exits. Use Scalar::Util::weaken to break cycles, or use Moose's weak_ref attribute option.
Moose: Modern OOP¶
Moose is a complete metaobject protocol for Perl. It provides declarative syntax for attributes, type constraints, inheritance, roles, and method modifiers. Moose eliminates the boilerplate of manual OOP while adding features that bless-based code cannot easily replicate.
Your First Moose Class¶
package Person;
use Moose;
has 'name' => (
is => 'ro',
isa => 'Str',
required => 1,
);
has 'age' => (
is => 'rw',
isa => 'Int',
default => 0,
);
has 'email' => (
is => 'rw',
isa => 'Str',
predicate => 'has_email',
);
no Moose;
__PACKAGE__->meta->make_immutable;
That is equivalent to roughly 40 lines of manual OOP code: a constructor with validation, three accessor methods, a predicate method, and immutability optimization.
The has Declaration¶
has declares an attribute with options:
| Option | Purpose | Example |
|---|---|---|
is |
Access mode | 'ro' (read-only), 'rw' (read-write) |
isa |
Type constraint | 'Str', 'Int', 'ArrayRef[Str]' |
required |
Must be provided to constructor | 1 |
default |
Default value (scalar or code ref) | 0, sub { [] } |
builder |
Method name that returns default | '_build_name' |
lazy |
Build default on first access | 1 |
predicate |
Generate has_attribute method | 'has_email' |
clearer |
Generate clear_attribute method | 'clear_email' |
trigger |
Callback on attribute write | sub { ... } |
weak_ref |
Weaken the reference (break cycles) | 1 |
Type Constraints¶
Moose provides a hierarchy of type constraints:
has 'count' => (is => 'rw', isa => 'Int');
has 'name' => (is => 'ro', isa => 'Str');
has 'scores' => (is => 'ro', isa => 'ArrayRef[Int]');
has 'metadata' => (is => 'rw', isa => 'HashRef[Str]');
has 'parent' => (is => 'ro', isa => 'Maybe[Person]');
Common types: Any, Bool, Int, Num, Str, ArrayRef, HashRef, CodeRef, RegexpRef, Object, ClassName. Parameterized types like ArrayRef[Int] check every element. Maybe[Type] allows undef as well as the specified type.
my $p = Person->new(name => 'Alice', age => 'thirty');
# Dies: Attribute (age) does not pass the type constraint
# because: Validation failed for 'Int' with value "thirty"
Defaults and Builders¶
For simple defaults, use default. For complex defaults, use builder:
has 'created_at' => (
is => 'ro',
isa => 'Int',
default => sub { time() },
);
has 'config' => (
is => 'ro',
isa => 'HashRef',
lazy => 1,
builder => '_build_config',
);
sub _build_config {
my ($self) = @_;
return { timeout => 30, retries => 3 };
}
Mutable defaults
Always use a code reference for mutable defaults: default => sub { [] }. Writing default => [] would share the same array reference across all instances - modifying one object's array would modify them all.
Moose Inheritance¶
Moose uses extends instead of use parent:
package Animal;
use Moose;
has 'name' => (is => 'ro', isa => 'Str', required => 1);
has 'sound' => (is => 'ro', isa => 'Str', default => '...');
sub speak {
my ($self) = @_;
printf "%s says: %s\n", $self->name, $self->sound;
}
no Moose;
__PACKAGE__->meta->make_immutable;
package Dog;
use Moose;
extends 'Animal';
has '+sound' => (default => 'Woof!');
sub fetch {
my ($self, $item) = @_;
print $self->name . " fetches the $item\n";
}
no Moose;
__PACKAGE__->meta->make_immutable;
The has '+sound' syntax modifies an inherited attribute - here overriding the default value. This is cleaner than redefining the entire attribute.
Method Modifiers¶
Moose provides method modifiers - before, after, and around - that wrap existing methods without overriding them:
package Dog;
use Moose;
extends 'Animal';
before 'speak' => sub {
my ($self) = @_;
print "(tail wagging) ";
};
after 'speak' => sub {
my ($self) = @_;
print "(sits down)\n";
};
my $dog = Dog->new(name => 'Rex', sound => 'Woof!');
$dog->speak;
# Output: (tail wagging) Rex says: Woof!
# (sits down)
around gives you full control - you receive the original method as a code reference:
around 'speak' => sub {
my ($orig, $self, @args) = @_;
print "[DEBUG] speak called\n";
$self->$orig(@args);
print "[DEBUG] speak finished\n";
};
Roles¶
Roles are Perl's answer to mixins and interfaces. A role is a collection of methods and attributes that can be composed into any class. Unlike inheritance, roles do not create an "is-a" relationship - they add capabilities.
package Printable;
use Moose::Role;
requires 'to_string';
sub print_self {
my ($self) = @_;
print $self->to_string . "\n";
}
package Serializable;
use Moose::Role;
requires 'to_hash';
sub to_json {
my ($self) = @_;
require JSON::PP;
return JSON::PP::encode_json($self->to_hash);
}
Consuming Roles with with¶
package Person;
use Moose;
with 'Printable', 'Serializable';
has 'name' => (is => 'ro', isa => 'Str', required => 1);
has 'age' => (is => 'ro', isa => 'Int', required => 1);
sub to_string {
my ($self) = @_;
return sprintf "%s (age %d)", $self->name, $self->age;
}
sub to_hash {
my ($self) = @_;
return { name => $self->name, age => $self->age };
}
no Moose;
__PACKAGE__->meta->make_immutable;
The with keyword composes one or more roles into the class. If a role has a requires declaration, the consuming class must provide that method - Moose enforces this at compile time.
my $p = Person->new(name => 'Alice', age => 30);
$p->print_self; # Alice (age 30)
print $p->to_json, "\n"; # {"name":"Alice","age":30}
print $p->does('Printable'); # 1
Roles vs Inheritance
Use inheritance when objects share a genuine "is-a" relationship (a Dog is an Animal). Use roles when objects share behavior without a taxonomic relationship (a Person and a Document can both be Printable, but neither is a subclass of the other).
Moo: Lightweight Moose¶
Moo provides almost the same interface as Moose but without the metaobject protocol. It starts faster, uses less memory, and is compatible with Moose (you can upgrade to Moose later without rewriting your classes).
package Server;
use Moo;
has 'host' => (
is => 'ro',
required => 1,
);
has 'port' => (
is => 'ro',
default => sub { 8080 },
);
has 'connections' => (
is => 'rw',
default => sub { 0 },
);
sub start {
my ($self) = @_;
printf "Server listening on %s:%d\n", $self->host, $self->port;
}
1;
Moo vs Moose: Key Differences¶
| Feature | Moose | Moo |
|---|---|---|
| Startup time | ~500ms | ~50ms |
isa type constraints |
Built-in type system | Accepts code refs or Type::Tiny |
| Meta-object protocol | Full introspection | None (until Moose is loaded) |
| Method modifiers | before, after, around |
Same |
| Roles | Moose::Role |
Moo::Role (compatible) |
make_immutable |
Required for performance | Automatic |
| Moose compatibility | N/A | Inflates to Moose on demand |
Type Checking in Moo¶
Moo uses code references or Type::Tiny for type checking:
use Types::Standard qw(Str Int ArrayRef);
has 'name' => (
is => 'ro',
isa => Str,
);
has 'ports' => (
is => 'ro',
isa => ArrayRef[Int],
);
Type::Tiny constraints work identically in Moo and Moose, making migration painless.
When to choose Moo vs Moose
Start with Moo. It handles 90% of OOP needs with a fraction of the overhead. Switch to Moose only if you need its metaobject protocol (runtime introspection, complex type coercions, or MooseX:: extensions). The API is compatible enough that switching is straightforward.
When to Use OOP vs Procedural¶
Not every Perl program needs objects. Choosing between OOP and procedural style depends on what you are building.
Use OOP When¶
- You have entities with state - objects that hold data and have behavior tied to that data
- Multiple instances of the same type need independent state (database connections, user sessions, HTTP requests)
- Your code has a natural hierarchy (Animals, Vehicles, UI widgets)
- You need polymorphism - different objects responding to the same method in different ways
- The codebase is large and needs clear boundaries between components
Use Procedural When¶
- You are writing a utility script or one-liner
- The program is a pipeline - read input, transform, write output
- Functions operate on passed-in data without maintaining state
- The module is a collection of functions (like
File::PathorList::Util) - Simplicity matters more than extensibility
Hybrid Approach¶
Most real Perl programs mix both styles:
# Procedural: utility function
sub normalize_phone {
my ($number) = @_;
$number =~ s/[^\d]//g;
return $number;
}
# OOP: stateful object
package Contact;
use Moo;
has 'name' => (is => 'ro', required => 1);
has 'phone' => (is => 'rw');
sub set_phone {
my ($self, $raw) = @_;
$self->phone(normalize_phone($raw));
}
Functions that do not need state live outside classes. Classes manage the entities that need state and behavior.
Exercises¶
Beginner: Bank Account Class¶
Intermediate: Task Queue with Roles (Moose)¶
Putting It All Together¶
Perl's OOP model is layered. At the bottom: bless, @ISA, and subroutines give you complete control over how objects are built and dispatched. At the top: Moose and Moo provide declarative syntax that handles the boilerplate. Here is what you covered:
blessassociates a reference with a package, turning data into an object- Methods are subroutines that receive the invocant (
$selfor$class) as their first argument - Accessors provide controlled access to object data instead of exposing hash internals
- Inheritance via
@ISAanduse parentlets classes share and override behavior SUPER::delegates to parent methods while adding subclass-specific logic- Method resolution order determines which parent wins in multiple inheritance (use C3)
isaandcanintrospect objects at runtimeDESTROYhandles cleanup when objects are garbage collected- Moose provides
has,extends,with, type constraints, and method modifiers - Moo delivers the same API with less overhead - start here for most projects
- Roles compose behavior across unrelated classes without inheritance
- OOP vs procedural is a design choice - use objects for stateful entities, functions for transformations
The manual approach teaches you what Perl objects really are. Moose and Moo let you stop thinking about the mechanics and focus on your domain.
Further Reading¶
- perlobj - Perl's official object-oriented programming reference
- perlootut - official OOP tutorial for Perl
- Moose::Manual - comprehensive Moose documentation
- Moo documentation - lightweight Moose alternative
- Moose::Manual::Roles - role composition in Moose
- Type::Tiny - type constraints for Moo and Moose
- Intermediate Perl - Chapters 8-15 cover references, objects, and testing
- Moose is Perl - community resource for Moose patterns and best practices
Previous: Modules and CPAN | Next: Error Handling and Debugging | Back to Index