| name | perl-moose |
| description | Moose-Klassen — Attribute, Roles vs. Inheritance, BUILD/BUILDARGS, Type-Constraints, MooseX::Singleton, make_immutable, Method-Modifier. |
Perl/Moose – Architecture & Implementation Patterns
Core Principle
Use inheritance sparingly (stable "is-a" contracts), roles heavily (horizontal reuse). When in doubt: role, not subclass. Always end classes with make_immutable.
Pattern 1 – extends + Attribute Override
package Vehicle;
use Moose;
use namespace::autoclean;
has color => (is => 'ro', default => 'black');
__PACKAGE__->meta->make_immutable;
package Spaceship;
use Moose;
use namespace::autoclean;
extends 'Vehicle';
has '+color' => (default => 'silver');
__PACKAGE__->meta->make_immutable;
Rules:
- Multiple
extends calls REPLACE (don't add) — always extends 'A', 'B' in one call.
- Override inherited attributes with
has '+name' => (...) — don't redefine from scratch.
- Never override
isa type in a subclass (global side effects).
- Moose uses C3 linearization for diamond inheritance — deterministic MRO.
Pattern 2 – Role with requires
package Role::Printable;
use Moose::Role;
requires 'to_string';
sub print_self { print $_[0]->to_string, "\n" }
package Document;
use Moose;
use namespace::autoclean;
with 'Role::Printable';
sub to_string { "Document: " . $_[0]->title }
has title => (is => 'ro', required => 1);
__PACKAGE__->meta->make_immutable;
Rules:
requires fails at composition time, not runtime — loud and early.
- Always compose all roles in one
with call — separate with calls skip conflict detection.
with must come after all has declarations the role might need.
excludes => 'OtherRole' prevents composing two incompatible roles — use sparingly.
Pattern 3 – Role Conflict Resolution
package MyClass;
use Moose;
use namespace::autoclean;
with 'RoleA', 'RoleB';
sub foo { ... }
with 'RoleA' => { -alias => { foo => 'foo_a' }, -excludes => 'foo' },
'RoleB';
__PACKAGE__->meta->make_immutable;
Rules: Moose does NOT silently pick a winner — conflicts are fatal. Class-defined method always wins over roles.
Pattern 4 – Roles in Roles (Bundles)
package Role::Bundle;
use Moose::Role;
with 'Role::Printable', 'Role::Serializable';
Composing Role::Bundle into a class pulls in both sub-roles. Good for grouping related capabilities.
Pattern 5 – Dynamic Role Application
use Moose::Util 'apply_all_roles';
my $obj = MyClass->new;
apply_all_roles($obj, 'Role::Debug');
Use for plugin systems or per-instance decoration. Required role attributes must already be present.
Pattern 6 – Parameterized Roles (MooseX::Role::Parameterized)
package Role::Counter;
use MooseX::Role::Parameterized;
parameter name => (isa => 'Str', required => 1);
role {
my $p = shift;
my $n = $p->name;
has $n => (is => 'rw', isa => 'Int', default => 0);
method "inc_$n" => sub { my $self = shift; $self->$n($self->$n + 1) };
};
package Game::Weapon;
use Moose;
use namespace::autoclean;
with 'Role::Counter' => { name => 'power' };
__PACKAGE__->meta->make_immutable;
Use method (not sub) inside role { } block. Non-core — adds MooseX::Role::Parameterized dependency.
Pattern 7 – Attribute Options Cheatsheet
has name => (is => 'ro', required => 1);
has tags => (is => 'ro', isa => 'ArrayRef[Str]', default => sub { [] });
has content => (is => 'lazy');
sub _build_content { "generated: " . $_[0]->name }
has status => (
is => 'rw',
isa => 'Str',
trigger => sub {
my ($self, $new, $old) = @_;
die "bad status" unless $new =~ /\A(new|ok|done)\z/;
},
);
has _secret => (is => 'ro', init_arg => 'secret');
has size => (
is => 'ro',
isa => 'Int',
coerce => 1,
);
Rules:
default => [] → shared state bug. Always default => sub { [] }.
- Prefer
builder over default for complex initialization — builders are inheritable and overridable.
trigger receives ($self, $new_val, $old_val) — unlike Moo, old value IS passed.
coerce only on your own subtypes, never on built-in type names (global side effects).
is => 'lazy' requires a _build_name method or explicit builder.
Pattern 8 – Native Traits Delegation
package TaskList;
use Moose;
use namespace::autoclean;
has tasks => (
traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[Task]',
default => sub { [] },
handles => {
add_task => 'push',
next_task => 'shift',
all_tasks => 'elements',
task_count => 'count',
find_task => 'first',
},
);
__PACKAGE__->meta->make_immutable;
Available native traits: Array, Hash, Bool, String, Number, Counter, Code. Each provides a set of generated methods. See Moose::Meta::Attribute::Native::Trait::* on CPAN.
Pattern 9 – handles Delegation to Objects
package Website;
use Moose;
use namespace::autoclean;
has uri => (
is => 'ro',
isa => 'URI',
handles => [qw(host path)],
);
__PACKAGE__->meta->make_immutable;
Pattern 10 – Method Modifiers
before 'save' => sub {
my $self = shift;
die "read-only mode" if $self->readonly;
};
after 'save' => sub {
my ($self, @args) = @_;
$self->log("saved");
};
around 'calculate' => sub {
my ($orig, $self, $x) = @_;
return 0 if $x < 0;
return $self->$orig($x) * 2;
};
Execution order: before (LIFO), then around (LIFO, wrapping), then original, then after (FIFO).
Rules: before/after cannot alter return value; around can. Always capture and forward @_ correctly in around. Multiple modifiers from multiple roles stack in composition order.
Pattern 11 – augment / inner (Inverted Inheritance)
package Report;
use Moose;
use namespace::autoclean;
sub render {
my $self = shift;
"<html>" . inner() . "</html>";
}
__PACKAGE__->meta->make_immutable;
package PDFReport;
use Moose;
use namespace::autoclean;
extends 'Report';
augment 'render' => sub {
my $self = shift;
"<pdf>" . inner() . "</pdf>";
};
__PACKAGE__->meta->make_immutable;
Use when the parent defines the frame and children fill in the content. Rare — only when the parent controls the wrapper structure.
Pattern 12 – Constructor Lifecycle
around BUILDARGS => sub {
my ($orig, $class, @args) = @_;
return $class->$orig(id => $args[0]) if @args == 1 && !ref $args[0];
$class->$orig(@args);
};
sub BUILD {
my ($self, $args) = @_;
die "SSN required for US" if $self->country eq 'USA' && !$self->ssn;
}
Rules:
- Never define
sub new — use BUILDARGS/BUILD instead.
BUILDARGS: class method, runs before construction, returns hashref.
BUILD: object method, runs after construction. Moose calls all BUILD in the hierarchy automatically.
- Never call
SUPER::BUILD manually.
- For cleanup: use
DEMOLISH (child→parent order), never override DESTROY.
Pattern 13 – make_immutable + namespace::autoclean
package MyClass;
use Moose;
use namespace::autoclean;
has name => (is => 'ro', required => 1);
__PACKAGE__->meta->make_immutable;
Rules:
namespace::autoclean goes at the top (after use Moose), make_immutable at the bottom.
- After
make_immutable: no more dynamic add_attribute, add_role etc.
namespace::autoclean removes has, with, extends etc. from the symbol table — they won't accidentally become methods.
Pattern 14 – Type Constraints
use Moose::Util::TypeConstraints;
subtype 'PositiveInt',
as 'Int',
where { $_ > 0 },
message { "$_ is not a positive integer" };
coerce 'PositiveInt',
from 'Str',
via { int($_) };
use Types::Standard qw(Str Int ArrayRef InstanceOf);
has name => (is => 'ro', isa => Str);
has items => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
Prefer Type::Tiny / Types::Standard — portable between Moo and Moose, better error messages.
Decision Guide
| Situation | Use |
|---|
| Shared attributes/methods, stable "is-a" | extends |
| Optional/horizontal feature | Moose::Role + with |
| Enforce interface contract | requires |
| Same role, different config | MooseX::Role::Parameterized |
| Delegate method set to sub-object | handles (list/hash/role form) |
| Array/Hash/Counter operations | traits => ['Array'] + handles |
| Logging/validation/caching wrapper | before/around/after |
| Parent defines frame, child fills content | augment/inner |
| Normalize constructor args | around BUILDARGS |
| Post-construction validation/setup | BUILD |
| Catch constructor typos | MooseX::StrictConstructor |
| Named types with coercion | Type::Tiny / Moose::Util::TypeConstraints |
| Multiple roles define same method | Resolve in class or -alias/-excludes |
| Metaclass extensions | traits on attributes or class |
| Per-instance behavior change | Moose::Util::apply_all_roles |
Common Pitfalls
default => [] → shared state bug. Always default => sub { [] }.
extends 'A'; extends 'B' → replaces, does NOT add. Use extends 'A', 'B'.
- Separate
with 'RoleA'; with 'RoleB' → skips conflict detection. Use one with.
with before has → requires check may fail spuriously. Define has first.
coerce on built-in type names → global side effects across the whole program.
- Never define
sub new — breaks Moose constructor optimization.
- Never call
SUPER::BUILD manually — Moose handles the chain.
- Never override
DESTROY — use DEMOLISH.
- Forgetting
make_immutable → significant performance penalty on every new.
around without forwarding @_ correctly → subtle argument loss.
MooseX Extensions (Cheatsheet)
| Module | Purpose |
|---|
MooseX::StrictConstructor | Dies on unknown constructor args |
MooseX::Role::Parameterized | Parameterized roles |
MooseX::ClassAttribute | Class-level (shared) attributes |
MooseX::Types | Named type libraries |
MooseX::Singleton | Singleton pattern (->instance) |
MooseX::Getopt | Auto CLI options from attributes |
MooseX::Storage | Serialization/deserialization |