6.3 Roles

     

A role is a reusable unit of class code. Much like a module exports subroutines into your program or another module, a role exports methods and attributes into a class. If your first thought on reading this is "Isn't that just inheritance?", then welcome to a whole new world. Inheritance is one way to reuse code, but many relationships other than isa are possible. Various languages pick an alternative and provide syntax for it: Ruby has mixins , Java has interfaces , and some versions of Smalltalk have traits . Perl roles go a bit beyond all of them.

You define a role using the role keyword:

 role Hitchhiker { . . . } 

You pull a role into a class using the does keyword:

 class Arthur does Hitchhiker { . . . } 

Roles cannot instantiate objects directly. To create an object that makes use of a role, you make a new object from a class that uses that role:

 $person = Arthur.new(. . .); 

6.3.1 Composition

Like classes, roles can define both attributes and methods:

 role Hitchhiker {   has $.towel;   method thumb_ride ($self: $ship) { . . . }    . . .  } 

Unlike classes, when you pull a role's methods and attributes into a class they aren't maintained in an inheritance hierarchy to be searched later. Instead, they are composed into the class almost as if they had been defined in that class. All methods defined in the role are accessible in the composed class, even if they wouldn't be inherited. All attributes defined in the role are accessible in the composed class by their direct $. name , not just by their accessor method. [2]

[2] Though you'll probably want to use the accessor methods anyway, based on the principles of encapsulation and ease of refactoring.

One advantage of composition is that classes can reuse behavior, even if they have no connection that would justify an inheritance relation. Suppose you want to define two classes: Arthur and Ford . Arthur inherits from Human and has all the behaviors and qualities of a human creature. Ford , on the other hand, has the behaviors and qualities of a creature from Betelgeuse:

 class Arthur is Human does Hitchhiker { . . . } class Ford is Betelgeusian does Hitchhiker { . . . } 

Inheritance makes sense in this case ”the inherited classes are core , defining characteristics of the resulting class. But the Hitchhiker role isn't a defining part of Ford and Arthur ”they weren't always hitchhikers. The role just adds some useful behavior to the class.

6.3.1.1 Mixins

In some situations you may want to add a role at run time instead of at compile time. Perhaps you want to choose different roles based on how the object is used, or perhaps the role's behavior shouldn't be available until part-way through the life of an object. The same does keyword adds roles at run time, but operates on an object instead of a class. In this example, Arthur starts as an ordinary human, and only adds the Hitchhiker role later in life (after the Vogons destroy his home planet):

 class Arthur is Human { . . . } $person = Arthur.new; $person.live_quietly until $vogon_ship.arrive; $person does Hitchhiker; $person.thumb_ride($vogon_ship); 

6.3.1.2 Interfaces

An interface is a reusable unit that defines what methods a class should support, but doesn't provide any implementations for those methods. In Perl 6, interfaces are just roles that define only method stubs and no attributes. This example defines an interface for products shipped by the Sirius Cybernetics corporation:

 role Sirius::Cybernetics {     method share { . . . }     method enjoy { . . . } } 

No matter whether the product is an elevator, a nutrimatic machine, or an automatic door, it must support the share and enjoy methods. Now, since these products are so very different, none will implement the methods in quite the same way, but you're guaranteed the products will be able to "Share and Enjoy" in one way or another ( generally in an irritating way).

6.3.2 Conflicts

Since a class pulls in roles by composition instead of inheritance, a conflict results when two roles both have a method with the same name. So, the Hitchhiker and Writer roles both define a lunch_break method ( lunch breaks being vitally important in both the publishing and footslogging industries):

 role Hitchhiker {     method lunch_break {         .suck($.towel);          .drink($old_janx);     }      . . .  } role Writer {     method lunch_break {         my $restaurant = Jolly::Nice::Restaurant.new;         .dine($restaurant);     }      . . .  } 

If the Ford class does the Writer role as well as the Hitchhiker role, which kind of lunch break should he take? Since roles are composed without hierarchy or priority, both methods are equally valid choices. Rather than randomly selecting an implementation for you, Perl 6 simply requires you to choose one. There are several ways to do this. One is to define a method of the same name in the class itself. This method might simply call the method from one of the roles:

 class Ford does Hitchhiker does Writer {     method lunch_break { .Writer::lunch_break(@_); } } 

Or the method might select between the possible implementations based on one of the arguments or some condition in the object. This example checks the string value returned by the .location method to find out which lunch break is appropriate:

 class Ford does Hitchhiker does Writer {     method lunch_break ($self: $arg) {         given (.location) {             when "Megadodo Office" { $self.Writer::lunch_break($arg); }             when "Ship Cargo Hold" { $self.Hitchhiker::lunch_break($arg); }         }     } } 

You can also get a finer-grained control over method conflict resolution using delegation syntax (explained in the next section). This example renames Hitchhiker 's lunch_break method to snack in the composed class:

 class Ford does Hitchhiker handles :snacklunch_break does Writer { . . . } 



Perl 6 and Parrot Essentials
Perl 6 and Parrot Essentials, Second Edition
ISBN: 059600737X
EAN: 2147483647
Year: 2003
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net