Home > Programming > Getting the hang of Moose Roles

Getting the hang of Moose Roles

As I said in my previous post, roles are an important advancement in the world of OO. Until now, I’ve mainly been using them because Ovid told me to (not personally, but through his numerous posts on his use.perl journal). When someone like Ovid goes on about something like this as much as he has, I usually find it a good idea to pay attention.

So I’d been using roles for a few weeks (and Moose only a bit longer), but it wasn’t until I had to do a big re-factor of the combat system in my browser-based role-playing game – Kingdoms – that something “clicked” in my head (and not in a bad way). The reason for the re-factor was that I wanted to add Party vs. Party combat. Before that point, parties had only been able to fight against groups of creatures. The only variation was that sometimes combat occurred in the wilderness, sometimes in a dungeon.

My original implementation (which was really wrong in hindsight!) was to have a bunch of Catalyst controller methods that forwarded all over the place. There was a bit of switching between the small differences needed in dungeon vs. wilderness, but it was pretty much a single path of execution. Things are quite different in Party vs. Party combat. While the basic logic stays the same, some parts change, e.g. the way I decide whether a party flees from combat.

Another issue I had was that I wanted to allow for offline combat, meaning all the logic needed to be broken out of Catalyst (so a cron job could easily kick if off), so it seemed like the right move to re-factor all of it using Moose. As per Ovid’s blog posts, I decided to avoid inheritance and make everything role-based. It took me a while to figure out the best way to do it (I had to learn a fair bit about roles after all), but eventually I realised that I needed 5 different roles, one called “Battle” that contained most of the logic, one each for Party and Creature combat, and one each for Wilderness and Dungeon combat.

Next, I created 4 classes, one each for the different combinations of party/creature battle in wilderness/dungeon. These classes have very little (if any) code themselves, they just create something that can be instantiated with the right set of roles composed into them. E.g, the class that handles creature battles in the wilderness looks like:

package CreatureWildernessBattle;
use Moose;
with qw/
    Battle
    CreatureBattle
    InWilderness
/;

The interaction between the three roles each class has is quite complex. One role might need to use a method from another role. For instance, the main Battle role triggers fleeing, but exactly how fleeing is done depends on whether you’re in the wilderness or a dungeon, e.g. something like this:

package Battle;
use Moose::Role;
sub execute_round {
   my $self = shift;
   ...
   if ($flee) {
      $self->do_flee;
   }
   ...
}
package InWilderness;
use Moose::Role;
sub do_flee {
   # handle flee in the wilderness
   ...
}

Of course, the Battle role has no idea it’s going to be composed with the InWilderness role. Only the actual class (e.g. CreatureWildernessBattle) knows that. So how can Battle call a method like do_flee() when it doesn’t know that it’ll exist? That’s why Moose gives us the ‘requires’ keyword. You can simply say:

requires 'do_flee';

And a compile time check will be done to make sure that any class that uses the Battle role also defines the do_flee method in some way, whether that’s through inheritance, another role that it uses, or it’s own definition. This is really important when your roles get complex.

In the end, I was pretty glad I’d listened to Ovid (and the other people who’ve been talking up roles). If I’d gone down a traditional inheritance route, I’d have had to either use multiple inheritance or a complex and deep hierarchy. I’ve had experience maintaining code employing both those approaches, and it’s never fun.

So chalk one up for roles.

About these ads
Categories: Programming Tags: , ,
  1. May 4, 2009 at 12:58 am | #1

    When creating lots of subclasses only for different combinations of roles, it’s often useful simply create one class with the MooseX::Traits role applied and the right _trait_namespace configured.

    It’ll give you an alternative constructor, new_with_traits, which will create a new anonymous subclass with the specified roles applied, and instanciate it. The anon classes will be cached, so you won’t end up with more classes than you’d normally have, but you won’t have to keep around empty classes on your disk anymore just to combine various roles.

    • Mutant
      May 4, 2009 at 9:30 am | #2

      Ah, thanks for the tip… yeah, that’d probably be a lot cleaner.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: