Глава 7. Roles

Содержание

What is a role?
Compile Time Composition
Multi-methods and composition
Calling all candidates
Expressing requirements
Runtime Application of Roles
Differences from compile time composition
The but operator
Parametric Roles
Roles and Types

A role is a standalone, named, and reusable unit of behavior. You can compose a role into a class at compile time or add it to an individual object at runtime.

That's an abstract definition best explained by an example. This program demonstrates a simple and pluggable IRC bot framework which understands a few simple commands.

    # XXX This is VERY preliminary code and needs filling out. But it
    # does provide opportunities to discuss runtime mixins, compile time
    # composition, requirements and a few other bits.

    my regex nick { \w+ }
    my regex join-line { ... <nick> ... }
    my regex message-line { $<sender>=[...] $<message>=[...] }

    class IRCBot {
        has $.bot-nick;
        method run($server) {
            ...
        }
    }

    role KarmaTracking {
        has %!karma-scores;

        multi method on-message($sender, $msg where /^karma <ws> <nick>/) {
            if %!karma-scores{$<nick>} -> $karma {
                return $<nick> ~ " has karma $karma";
            }
            else {
                return $<nick> ~ " has neutral karma";
            }
        }

        multi method on-message($sender, $msg where /<nick> '++'/) {
            %!karma-scores{$<nick>}++;
        }

        multi method on-message($sender, $msg where /<nick> '--'/) {
            %!karma-scores{$<nick>}--;
        }
    }

    role Oping {
        has @!whoz-op;

        multi method on-join($nick) {
            if $nick eq any(@!whoz-op) {
                return "/mode +o $nick";
            }
        }

        # I'm tempted to add another candidate here which checks any(@!whoz-op)
        multi method on-message($sender, $msg where /^trust <ws> <nick>/) {
            if $sender eq any(@!whoz-op) {
                push @!whoz-op, $<nick>;
                return "I now trust " ~ $<nick>;
            }
            else {
                return "But $sender, I don't trust you";
            }
        }
    }

    role AnswerToAll {
        method process($raw-in) {
            if $raw-in ~~ /<on-join>/ {
                self.*on-join($<nick>);
            }
            elsif $raw-in ~~ /<on-message>/ {
                self.*on-message($<sender>, $<message>)
            }
        }
    }

    role AnswerIfTalkedTo {
        method bot-nick() { ... }

        method process($raw-in) {
            if $raw-in ~~ /<on-join>/ {
                self.*on-join($<nick>);
            }
            elsif $raw-in ~~ /<on-message>/ -> $msg {
                my $my-nick = self.bot-nick();
                if $msg<msg> ~~ /^ $my-nick ':'/ {
                    self.*on-message($msg<sender>, $msg<message>)
                }
            }
        }
    }

    my %pluggables =
        karma => KarmaTracking,
        op    => Oping;

    role Plugins {
        multi method on-message($self is rw: $sender, $msg where /^youdo <ws> (\w+)/) {
            if %pluggables{$0} -> $plug-in {
                $self does $plug-in;
                return "Loaded $0";
            }
        }
    }

    class AdminBot    is IRCBot does KarmaTracking    does Oping       {}
    class KarmaKeeper is IRCBot does KarmaTracking    does AnswerToAll {}
    class NothingBot  is IRCBot does AnswerIfTalkedTo does Plugins     {}

You don't have to understand everything in this example yet. It's only important right now to notice that the classes KarmaKeeper and NothingBot share some behavior by inheriting from IRCBot and differentiate their behaviors by performing different roles.