Saturday, July 19, 2008

Class extention with roles and method modifiers

While working on blok I introduced a new feature to Joose that has proven to be a very powerful programming technique: Method modifiers used in roles.

Let me start with a quick introduction to those two concepts:

Method modifiers are declarative approach to JavaScript method wrapping techniques. They allow you to do stuff before or after a method is executed or to override a method and only call the method in the super class "on demand" using this.SUPER(). In the Joose cookbook a before method modifier is used to adjust a bank account balance using an overdraft account if neccessary:
Class("CheckingAccount", {
isa
: m.Account,
has
: {
overdraftAccount
: {
isa
: m.Account,
is: rw
}
},
before
: {
withdraw
: function (amount) {
var overdraftAmount = amount - this.getBalance()

if(this.overdraftAccount && overdraftAmount > 0) {
this.overdraftAccount.withdraw(overdraftAmount);
this.deposit(overdraftAmount);
}
}
}
})
Roles are a mixture between Java-Interfaces and and Ruby Mixins. They were originally called Traits but we like Role better :) They require that a class that implements them provides a set of methods but in contrast to traditional interfaces they may also export methods into the classes that implement them. Those methods may only use other methods provided or required by the role. That way a role can guarantee that the functionality it provides actually works in the destination class. A class that does a role may of course always choose to override methods provided by the role. This way roles give you all the power of multiple inheritance without the problems of mi.

Now to the real topic of this post: Method modifiers in roles.
Using method modifiers in roles creates a very natural and declarative way of solving a problem that is traditionally tackled with the decorator pattern:

(Dramatized) Java:
Window win = new ScrollingWindowDecorator(
new TabbedWindow
Decorator(
new ResizableWindow
Decorator(
new DraggableWindow
Decorator(
new Window("Window Name")))))
The decorator allows the dynamic composition of functionality by giving each decorator the chance to modify the behavior of the object when a method is called. Besides looking like Lisp because of all the )))) the decorator has one primary problem (or features depending on the view point): When window calls a method on itself, none of the methods of any of the decorator will be called.

Method modifying roles to the rescue. Consider this example: We want to model people who vary in their behavior when they say hello to somebody. Some people introduce themselves, some people are shy, some people stutter, all people say hello. The person class in Joose is easy:
Class("Person", {
methods: {
sayHello: function () {
print("Hello!")
}
}
})
The sayHello method prints "Hello!" when you call it. Now we use roles and method modifiers to attach extra functionality to the sayHello method. Lets start with the shy person:
Role("ShyPerson", {
before: {
sayHello: function () {
print("May I talk to you?")
}
}
})
Everytime before the ShyPerson says hello, she aks whether she may talk to you. Introductions follow the same principle:
Role("Introduction", {
after: {
sayHello: function () {
print("I am a Joose user.")
}
}
})
In our example people who stutter say "Stutter" before they say hello and then say everything twice. This example shows the implementation using the around and override method modifiers:
Role("Stuttering", {
around: {
// say it twice
sayHello: function (orig) {
orig()
orig()
}
},
override: {
// say Stutter and and say what was defined before
sayHello: function () {
print("Stutter")
this.SUPER()
}
}
})
Now, we'll define classes for two different persons with different behaviors:
Class("Eve", {
isa: Person,
does: [
Introduction,
ShyPerson
]
})

Class("Adam", {
isa: Person,
does: [
Introduction,
ShyPerson,
Stuttering
]
})
The does-keyword in the class declaration tells Joose that this class implements roles that follow in the declaration. So now Eve introduces herself and is shy. Adam has the same attributes but also stutters. Should Eve ever start to stutter, we can, of course, always later apply the Stuttering role to eve's instances: Stuttering.meta.apply(eve)

When you use the classes it looks like this (print is defined as somehow printing its arguments and attaching a new line):
var eve = new Eve();
eve.sayHello() // prints May I talk to you?\nHello!\nI am a Joose user.\n

var adam = new Adam();
adam.sayHello() // prints Stutter\nMay I talk to you?\nHello!\nI am a Joose user.\nMay I talk to you?\nHello!\nI am a Joose user.\n
Conclusion:
The above example shows how roles can be used to nicely attach extra functionality to classes using method modifiers. This pattern is especially useful if you need to augment a certain set of functionality without totally replacing the original methods which is a common phenomenon when you deal with GUI elements.
Post a Comment