程式扎記: [ Groovy Doc ] ExpandoMetaClass - Main, Borrowing Methods and Adding constructors (1)

標籤

2015年1月8日 星期四

[ Groovy Doc ] ExpandoMetaClass - Main, Borrowing Methods and Adding constructors (1)


Source From Here
Using ExpandoMetaClass to add behaviour
Groovy includes a special MetaClass called an ExpandoMetaClass that allows you to dynamically add methods, constructors, properties and static methods using a neat closure syntax.

How does it work? Every java.lang.Class is supplied with a special "metaClass" property that will give you a reference to an ExpandoMetaClass instance. For example to obtain the ExpandoMetaClass of the java.lang.String class you use:
  1. String.metaClass.swapCase = {->  
  2.       def sb = new StringBuffer()  
  3.       delegate.each {  
  4.            sb << (Character.isUpperCase(it as char) ? Character.toLowerCase(it as char) :   
  5.                    Character.toUpperCase(it as char))  
  6.       }  
  7.       sb.toString()  
  8. }  
This adds a method called swapCase to the String class. By default ExpandoMetaClass doesn't do inheritance. To enable this you must callExpandoMetaClass.enableGlobally() before your app starts such as in the main method or servlet bootstrap.

Further Reading:
ExpandoMetaClass - Borrowing Methods — Borrowing methods from other classes
ExpandoMetaClass - Constructors — Adding or overriding constructors
ExpandoMetaClass Domain-Specific Language
ExpandoMetaClass - Dynamic Method Names — Dynamically creating method names
ExpandoMetaClass - GroovyObject Methods — Overriding invokeMethod, getProperty and setProperty
ExpandoMetaClass - Interfaces — Adding methods on interfaces
ExpandoMetaClass - Methods — Adding or overriding instance methods
ExpandoMetaClass - Overriding static invokeMethod — Overriding invokeMethod for static methods
ExpandoMetaClass - Properties — Adding or overriding properties
ExpandoMetaClass - Runtime Discovery — Overriding invokeMethod for static methods
ExpandoMetaClass - Static Methods — Adding or overriding static methods

Borrowing Methods from other classes
With ExpandoMetaClass, you can also use Groovy's method pointer syntax to borrow methods from other classes. For example:
  1. package test  
  2.   
  3. class Person {  
  4.     String name  
  5. }  
  6. class MortgageLender {  
  7.     def action = "Buy house"  
  8.       
  9.     MortgageLender(){}  
  10.     MortgageLender(def act){this.action=act}  
  11.       
  12.     def borrowMoney() {  
  13.         action  
  14.     }  
  15. }  
  16.   
  17. def lender1 = new MortgageLender()  
  18. def lender2 = new MortgageLender("Buy car")  
  19.   
  20. Person.metaClass.buyHouse = lender1.&borrowMoney  
  21.   
  22. def p = new Person()  
  23.   
  24. assert "Buy house" == p.buyHouse()  
  25. Person.metaClass.buyHouse = lender2.&borrowMoney // Dynamically switch   
  26. assert "Buy car" == p.buyHouse()  
Adding constructors
Adding constructors is a little different to adding a method with ExpandoMetaClass. Essentially you use a special "constructor" property and either use the << or= operator to assign a closure. The arguments to the closure are of course the constructor arguments.
  1. package test  
  2.   
  3. import groovy.transform.ToString  
  4.   
  5. @ToString  
  6. class Book {  
  7.     String title  
  8.       
  9.     public Book(String title){this.title=title.toUpperCase()}  
  10.       
  11.     @Override  
  12.     public String toStrong(){return String.format("Book:${title}")}  
  13. }  
  14.   
  15. def b = new Book("The Stand")  
  16. printf("${b}\n")  
  17. // Book.metaClass.constructor << { String title -> new Book(title:title) } --> Exception:  
  18. // Cannot add new constructor for arguments [[class java.lang.String]]. It already exists!  
  19. Book.metaClass.constructor << { String title, String ver -> new Book(String.format("%s(%s)", title, ver)) }  
  20. b = new Book("The Stand""v1.0")  
  21. printf("${b}\n")  
Execution result:
test.Book(THE STAND)
test.Book(THE STAND(V1.0))

Be careful when adding constructors however, as it is very easy to get into stack overflow troubles. For example below code will overrides the default constructor:
  1. import groovy.transform.ToString  
  2.   
  3. class Book {  
  4.     String title  
  5. }  
  6. Book.metaClass.constructor = {  new Book() }  
  7.   
  8. // Exception in thread "main" java.lang.StackOverflowError  
  9. def b = new Book("The Stand")  
The above would produce a StackOverflowError as it rescursively keeps calling the same constructor through Groovy's MetaClass system. You can get around this by writing helper code to instantiate an instance outside of Groovy. For example this uses Spring's BeanUtils class and does not cause a StackOverflow:
  1. class Book {  
  2.     String title  
  3. }  
  4. Book.metaClass.constructor = {  BeanUtils.instantiateClass(Book) }  
  5.   
  6.   
  7. def b = new Book("The Stand")  

Supplement
Annotation Type ToString
Class annotation used to assist in the creation of toString() methods in classes. The @ToString annotation instructs the compiler to execute an AST transformation which adds the necessary toString() method.

沒有留言:

張貼留言

網誌存檔

關於我自己

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