程式扎記: [ Groovy Doc ] Category and Mixin transformations

標籤

2014年9月28日 星期日

[ Groovy Doc ] Category and Mixin transformations

Source From Here
If you've been using Groovy for a while, you're certainly familiar with the concept of Categories. It's a mechanism to extend existing types (even final classes from the JDK or third-party libraries), to add new methods to them. This is also a technique which can be used when writing Domain-Specific Languages. Let's consider the example below:
  1. final class Distance {  
  2.     def number  
  3.     String toString() { "${number}m" }  
  4. }  
  5.   
  6. class NumberCategory {  
  7.     static Distance getMeters(Number self) {  
  8.         new Distance(number: self)  
  9.     }  
  10. }  
  11.   
  12. use(NumberCategory) {  
  13.     def dist = 300.meters  
  14.   
  15.     assert dist instanceof Distance  
  16.     assert dist.toString() == "300m"  
  17. }  
We have a simplistic and fictive Distance class which may have been provided by a third-party, who had the bad idea of making the class final so that nobody could ever extend it in any way. But thanks to a Groovy Category, we are able to decorate the Distance type with additional methods. Here, we're going to add a getMeters() method to numbers, by actually decorating the Number type. By adding a getter to a number, you're able to reference it using the nice property syntax of Groovy. So instead of writing 300.getMeters(), you're able to write 300.meters.

The downside of this category system and notation is that to add instance methods to other types, you have to create static methods, and furthermore, there's a first argument which represents the instance of the type we're working on. The other arguments are the normal arguments the method will take as parameters. So it may be a bit less intuitive than a normal method definition we would have added to Distance, should we have had access to its source code for enhancing it. Here comes the @Categoryannotation, which transforms a class with instance methods into a Groovy category:
  1. @Category(Number)  
  2. class NumberCategory {  
  3.     Distance getMeters() {  
  4.         new Distance(number: this)  
  5.     }  
  6. }  
No need for declaring the methods static, and the this you use here is actually the number on which the category will apply, it's not the real this of the category instance should we create one. Then to use the category, you can continue to use the use(Category) {} construct. What you'll notice however is that these kind of categories only apply to one single type at a time, unlike classical categories which can be applied to any number of types.

Now, pair @Category extensions to the @Mixin transformation, and you can mix in various behavior in a class, with an approach similar to multiple inheritance:
  1. @Category(Vehicle) class FlyingAbility {  
  2.     def fly() { "I'm the ${name} and I fly!" }  
  3. }  
  4.   
  5. @Category(Vehicle) class DivingAbility {  
  6.     def dive() { "I'm the ${name} and I dive!" }  
  7. }  
  8.   
  9. interface Vehicle {  
  10.     String getName()  
  11. }  
  12.   
  13. @Mixin(DivingAbility)  
  14. class Submarine implements Vehicle {  
  15.     String getName() { "Yellow Submarine" }  
  16. }  
  17.   
  18. @Mixin(FlyingAbility)  
  19. class Plane implements Vehicle {  
  20.     String getName() { "Concorde" }  
  21. }  
  22.   
  23. @Mixin([DivingAbility, FlyingAbility])  
  24. class JamesBondVehicle implements Vehicle {  
  25.     String getName() { "James Bond's vehicle" }  
  26. }  
  27.   
  28. assert new Plane().fly() ==  
  29.        "I'm the Concorde and I fly!"  
  30. assert new Submarine().dive() ==  
  31.        "I'm the Yellow Submarine and I dive!"  
  32.   
  33. assert new JamesBondVehicle().fly() ==  
  34.        "I'm the James Bond's vehicle and I fly!"  
  35. assert new JamesBondVehicle().dive() ==  
  36.        "I'm the James Bond's vehicle and I dive!"  
You don't inherit from various interfaces and inject the same behavior in each subclass, instead you mixin the categories into your class. Here, our marvelous James Bond vehicle gets the flying and diving capabilities through mixins.

An important point to make here is that unlike @Delegate which can inject interfaces into the class in which the delegate is declared, @Mixin just does runtime mixing — as we shall see in the metaprogramming enhancements further down in this article.

Supplement
Delegate Transformation
Java doesn't provide any built-in delegation mechanism, and so far Groovy didn't either. But with the @Delegate transformation, a class field or property can be annotated and become an object to which method calls are delegated...

Dynamic object orientation - Using power features
This section presents three power features that Groovy supports at the language level: GPath, the Spread operator, and the use keyword...


沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!