Are Abilities Roles?

In my last post I started out modeling D&D characters with Moose to examine how roles could be applied

I ended with very little code written before I had to make a decision on using my first role or to use more traditional object extension.

Now, Looking at 'abilities' they all have the same set of common attributes, (I will just use a few)

  • name

  • initial value

  • current value

So to make this a class we would just do this


package Ability;

use Moose;

has 'name' => (
      is  => 'ro',
      isa => 'Str',
      init_arg => undef,
  );

has 'initial' => (
      is  => 'ro',
      isa => 'Int',
  );

has 'current' => (
      is  => 'rw',
      isa => 'Int',
  );


All nice and dandy, Moose does all the basic tedious bits (accessesors , validation construction etc), Moose even lets you undo the sticky bits by adding in the 'init_arg=>undef' I can disable the constructor's ability to set the 'name' so now I know some one can not get away with this

my $me = Ability->new({name=>'xxx'}

In the Java world we would call this an abstract class. An 'Ability' on its own, especially one who's name* we cannot set or change, would not be much use to anyone. Does it make it sense at this point to make it a 'Role'?

I could just use this as a base-class for all my differing 'Ability' classes. An "Ability" is a good example of a single inheritance as you will never have composite 'Ability' made up of two like 'Strength-Wisdom' or 'Constitution-Charisma', at least when I am the DM. So I think I am safe with 'Ability' as a base in a single level of inheritance Class like this;



package Strength;
use Moose;
extends "Ability";

has '+name' => (
      default => 'Strength',
);


Now I can account for that first little sticky bit on the 'name' attribute, all I need to to is tell Moose I am doing a little overriding by adding the '+' sign in front of the attribute's name followed by what I want to override in this case make its 'default' value 'Strength'

Hmm, still haven't got to roles yet!

If the concept of a Role as something that may provide

  • behaviour
  • attributes
  • requirements

One does see some of these in the 'Ability' class, but another key point when looking at roles is code reuse. In the game there will only be 6 classes that use the 'Ability' as a base so not allot of reuse, I think is might even be more code to set it up as a role?

Oh well onto the next piece of the puzzle.


*I know could just take advantage of Moose meta data parts and never bother with a name attribute but I will leave that for another post.

9 Comments

What is the purpose of making Ability a class in this design? If you never plan to instantiate it, then it should be role.

Also, with your current design I could write Strength->new( name => 'Wisdom' ). That makes no sense. The name should not be settable by any means.

To answer your first question if Ability should be a role. No it shouldn't. Just having an Ability class is fine. Another point, you said a ability where you can't change the name is of no use. Why? In whatever game you play, where does on a sudden ever changes a name of an ability? A "Fireball" should always be a "Fireball".

Instead, just think of Roles as "better" Interfaces. I don't program Java, but at the Moment i'm programming in C# so for the background i use the terms how it is used in C#.

So in an interface you just define a definition of attributes, methods, events and so on which your class should implement. You can assign multiple interfaces to a class. Well and i hope you knew which benefits you get from it. You can write multiple implements of the same thing. In C# i also can now write "IAbility ability" for example and i now can assign any class to it that implements the IAbility Interface. At some point you also can use Roles for this. But in Perl you probably doesn't gain to much from it because you can assign anything to a variable. But you still get the benefit that you don't forget to implement an propertie, Method and so on. If you expand the role later, you get errors for every class that don't implement the role completly. And you get the benefit to easy asking if an object implements an Role. With "$obj->does("IAbility")" you just can ask your object if it is an Ability for example.

But i said a Role is a form of a "better" interface. Well what is better is that a role can also have an implementation.

You can also write a default implementation that you put in your role. Now a class that uses your role not only have the definition what it should implement, it also gets an implementation and you just can overwrite some Methods that you need to adjust. Or add something to an propertie/method and so on.

And to help you where you can use it. Think of it as a better way of inheritance. Actually i think "inheritance" is "broken by design" and just the new "goto" statement and should be avoided at all costs, and with roles you can do it.

Did you ever wrote an class, and you wrote an method, and later you saw. "Hmm this method is also useful for other classes". The typicall wrong way how a lot of people react to that problem is to create a super class, and they put that method in the super class. And now they create a new class and inherit from that super class. And some time later you end up in the inheritance hell, you don't knew where a method came from. Where it is defined. You get a lot of methods that a class don't need. If you change a method in a super class a lot of sub-class can break. Voila, modern spaghetti-code provided by inheritance.

And the answer is. Just use roles. If you think that something you have written can be used by other classes. Instead of creating a super class, create an Role that combines your methods. And use this role in your class. Instead of inherit from something you just inject the pieces to assemble your class.

For example, the typical "Animal" Tree what a lot of people use for an "good" example of inheritance is basically a good example why you shouldn't use inheritance.

Let's say you have this sort of inheritance "Animal -> Bird -> Dove". Now you say that every bird can Fly, so you put your "Fly" Code in the Bird class. Now your Dove can fly. And you propably create a lot of others classes that used this. You put your "Crow" under "Bird", you put your "Eagle" under Bird. Everything works fine. Until you want to implement a "Penguin". You put your Penguin under Bird, because, well it is bird, right? But, now, a penguin can't fly. What do you do now? Create a new super Class? Like "FlyingBirds" and "NotFlyingBirds"? What is if you want to implement "Flying Fishes" where do you put your Flying code then?

And that is the typicall way how you will end with inheritance. You want reusable code, but you are locked to classed. And the real problem is that you have put "behaviours" in a definition like a "bird". Instead of implying that every Bird can Fly you should create an behaviour (Role) "Fly". And now everything that should be able to Fly you inject your role "Fly" to it. So your "Dove" have an "with 'Fly'" definition. But your Penguin just don't have it. because he can't fly. If you want Flying fishes. Inject the "Fly" role in your flying fishes. If you have an Helicopter, X-Wing Fighter or the Death Star that can fly. Just inject the "Fly" role in that classes.

Instead of getting reusability through inheritance you use roles instead. Instead of asking if an object has some methods, you create an role that defines your methods and you accept every object that implements that interface/role.

That's the way how you should use Roles or think of roles. It combines an interface with reusable piece of code. Think of Roles as an alternative to the shit that is named "inheritance" and just gives you a piece of unmaintainable code in the long-run.

In really like parameterizing roles. I used to do it with MooseX::ParameterizedRole but recently I switched over to Package::Variant.


package DD::Role::Ability;
use strictures 1;
use Package::Variant
  importing => ['Moo::Role'],
  subs => [ qw(has around before after with) ];

sub make_variant {
  my ($class, $target_package, %arguments) = @_;
  my $name = $arguments{name};
  has $name => (is => 'lazy');
  install "_build_${name}" => sub {
    return $arguments{class}->new;
  };
}

package DD::Character;
use strictures 1;
use Moose;
use DD::Role::Ability;
with
  Ability(name => 'strength'),
  Ability(name => 'stamina');

#!/usr/bin/env perl
use strictures 1;
use DD::Character;
use feature qw/ say /;
my $c1 = DD::Character->new(
    strength => 5,
    stamina  => 90,
);
say $c1->strength;
say $c1->stamina;

This is perhaps the best argument for roles vs superclasses I've ever read.

Doh, I missed that init_arg bit. Nonetheless, my point about whether you'd instantiate the base class stands. If the answer is no, there's no point in having it be a class at all.

Wow, really good comment about roles vs inheritance!

Leave a comment

About byterock

user-pic Long time Perl guy, a few CPAN mods allot of work on DBD::Oracle and a few YAPC presentations