# Roles and delegation Inheritance answers "what is this object?" Roles and delegation answer a different question: "what can this object do?" When two unrelated classes need to share behaviour, forcing them under a common parent is the wrong tool. Composition — giving each class the behaviour it needs without pretending they are related — is the right one. This chapter covers the three composition patterns that pperl code uses in practice: 1. **Mixin-style sharing** via a tiny helper package. 2. **Role composition** via `Role::Tiny` or a Moose-family system in legacy code. 3. **Delegation** — forwarding methods to a contained object. The core [`class`](../../p5/core/perlfunc/class) feature does not yet ship a role primitive. That is a deliberate staging decision upstream: the `class` feature landed first, roles are a separate proposal. Until the core role feature ships, the patterns in this chapter are the working answers. ## The problem Imagine a `Radio` class and a `Computer` class. Both have an on/off switch. They are not related — a radio is not a computer, and neither specialises a hypothetical `Machine`. A `HasOnOffSwitch` parent class would be an artificial grouping whose only purpose is reuse of two methods. Three solutions, in increasing order of ambition: - Copy the `turn_on` / `turn_off` methods into each class. Tolerable for two classes, painful for ten. - Extract them into a helper package, use one of the sharing mechanisms below. - Use a role system with formal composition semantics. ## Pattern 1: manual mixin The smallest possible "share these methods" mechanism is a plain package that installs subroutines into the consuming class at `use` time: ```perl package HasOnOffSwitch; sub import { my $target = caller; no strict 'refs'; *{"${target}::turn_on"} = sub { $_[0]->{is_on} = 1 }; *{"${target}::turn_off"} = sub { $_[0]->{is_on} = 0 }; *{"${target}::is_on"} = sub { $_[0]->{is_on} }; } 1; ``` Used: ```perl package Radio; use HasOnOffSwitch; sub new { bless { is_on => 0 }, shift } ``` This works. It is also brittle: it reaches into the consuming class's hash directly (`$_[0]->{is_on}`), which is exactly the encapsulation violation that classical hash-backed OO is famous for. It breaks if the consumer is a [`class`](../../p5/core/perlfunc/class)-declared class, because fields are not hash slots. Use this pattern only for very small shared behaviour in a classical codebase. For anything larger, use a real role system. ## Pattern 2: `Role::Tiny` `Role::Tiny` is the lightweight role system that works with plain classical classes, with `Moo`, and with `Moose` (on the consuming side). It is the closest thing to a portable role primitive in the CPAN ecosystem. ```perl package HasOnOffSwitch; use Role::Tiny; sub turn_on { $_[0]->is_on(1) } sub turn_off { $_[0]->is_on(0) } requires 'is_on'; # the consumer must provide this accessor 1; ``` A consumer picks the role up with `with`: ```perl package Radio; use Role::Tiny::With; with 'HasOnOffSwitch'; sub new { bless { is_on => 0 }, shift } sub is_on { @_ > 1 ? $_[0]{is_on} = $_[1] : $_[0]{is_on} } ``` What `Role::Tiny` gives you over the manual mixin: - **Requirements.** `requires 'is_on'` fails at composition time if the consumer does not supply `is_on`. The manual mixin fails silently at the first method call. - **Conflict detection.** Consuming two roles that both define the same method is a composition-time error, not silent last- definition-wins. - **Method modifiers** — `before`, `after`, `around` — if you want them. `Role::Tiny` is **not** the same thing as Moose's role system. Its reach is intentionally narrow. If the codebase already uses Moose, use `Moose::Role`; if it uses Moo, use `Moo::Role`. ## Pattern 3: Moose-family roles Legacy codebases built on Moose or Moo use `Moose::Role` and `Moo::Role` respectively. The surface looks like the role system the core `class` feature will eventually gain: ```perl package HasOnOffSwitch; use Moose::Role; has is_on => (is => 'rw', isa => 'Bool', default => 0); sub turn_on { $_[0]->is_on(1) } sub turn_off { $_[0]->is_on(0) } 1; package Radio; use Moose; with 'HasOnOffSwitch'; 1; ``` In pperl, Moose still works. New code should not reach for it — the runtime cost and dependency weight are both large, and the core [`class`](../../p5/core/perlfunc/class) feature now covers the declarative side of what Moose was built for. When the core role feature lands, migrating from `Moose::Role` to the core role will be the natural next step. ## Pattern 4: delegation Delegation says "I don't do this myself, but I hold something that does." Instead of inheriting behaviour, contain an object that provides it, and forward methods to it. The manual form: ```perl use feature 'class'; class Player { field $radio :param; method turn_on { $radio->turn_on } method turn_off { $radio->turn_off } method is_on { $radio->is_on } } ``` Each forwarded method is three lines of mechanical plumbing. For two or three methods that is fine; for a wide surface, reach for `Class::Method::Delegate` or a Moose `handles` accessor: ```perl package Player; use Moose; has radio => ( is => 'ro', handles => ['turn_on', 'turn_off', 'is_on'], ); ``` `handles` generates the forwarders at class-build time. ### When to delegate vs inherit - **Delegate** when the contained object is a *collaborator* — something the owner *has* rather than *is*. A `Player` has a `Radio`; a `Player` is not a kind of `Radio`. - **Inherit** when the child genuinely *is* a specialised parent. A `Circle` is a kind of `Shape`; a `File::MP3` is a kind of `File`. If the answer to "is-a or has-a?" is not immediate, the relation is probably has-a. Defaulting to composition is almost always the less wrong call — you can refactor composition into inheritance with less pain than the reverse. ## Delegation with lazy construction A common pattern: the collaborator is expensive to build, so build it on first access. With the modern [`class`](../../p5/core/perlfunc/class) feature: ```perl use feature 'class'; class Logger { field $path :param; field $fh; method fh { unless ($fh) { open $fh, '>>', $path or die "open $path: $!"; } return $fh; } method log ($msg) { print {$self->fh} $msg, "\n"; } } ``` `fh` is a reader that also handles the one-shot construction. Callers see a plain method; the lazy cache is private. ## What not to do - **Multiple inheritance to share two methods.** You pay the full MRO cost for a tiny win, and the next person to inherit from your class inherits the MRO pain too. Use a role or a mixin. - **`AUTOLOAD` as a delegator.** Convenient in the five-minute demo, impossible to introspect, and it eats typos. Write explicit forwarders or use `handles`. - **Roles that carry state.** A role that defines both methods and attributes is really a mini-class. The cleanest role defines behaviour that the consuming class's state supports; the role requires the accessors it needs and trusts the consumer to provide them. The manual-mixin example in pattern 1 got this wrong by reaching into `$_[0]->{is_on}` directly. - **Deep delegation chains.** If `$player->radio->volume->level` appears repeatedly, add a `volume_level` accessor on `Player`. Train-wreck method chains are a composition smell. ## Further reading - [Modern classes](classes) — the base layer that composition sits on top of. - [Inheritance and method resolution](inheritance) — when the answer really *is* inheritance. - [Migration](migrating) — how role-based legacy code translates into the core `class` model (spoiler: mostly unchanged; roles migrate last). - [`class`](../../p5/core/perlfunc/class) — reference for the modern class syntax. - [`isa`](../../p5/core/perlfunc/isa) — class-membership test; pair with `DOES` for role membership.