Dynamically extending classes with new methods

One of the exciting characteristics of Groovy is Meta Object Protocol (MOP). In a nutshell, the term metaprogramming refers to writing code that can dynamically change its behavior at runtime. A Meta Object Protocol refers to the capabilities of a dynamic language that enable metaprogramming. In this recipe, we are going to look at one of the capabilities of the MOP, which is ExpandoMetaClass.

Groovy's ExpandoMetaClass lets you assign behavior and state to classes at runtime without editing the original source code; it is essentially a layer above the original class. In the next section of the recipe, we will show you how to achieve such a result.

How to do it...

Adding a new method to a Groovy (or Java) class is straightforward. We are going to perform the following given steps to add a getInEuros method to the BigDecimal Java class in order to convert US dollars to euros:

  1. In a new Groovy script, type the following code:
    import java.text.NumberFormat
    
    BigDecimal.metaClass.getInEuros = { ->
      def exchangeRate = 0.763461
      def nf = NumberFormat.getCurrencyInstance(Locale.US)
      nf.setCurrency(Currency.getInstance('EUR'))
      nf.format(delegate * exchangeRate)
    }
  2. Now, we are ready to test the conversion rate:
    assert 1500.00.inEuros == 'EUR1,145.19'

How it works...

Each Groovy object has an accompanying instance of a class named ExpandoMetaClass (reachable through the metaClass property) that holds a reference to the methods that can be called on an object, including:

  • The base methods that the type allows
  • More methods added for that type by Groovy (such as, find on collections)
  • Methods added at runtime, using metaClass

Every time a method on an object is invoked, a dynamic dispatcher mechanism is used to delegate to the companion ExpandoMetaClass.

The getInEuros closure, which we have added to the BigDecimal class, contains only the US dollar/euro conversion code. The only aspect worth mentioning is the delegate variable, which refers to the object on which we call the method.

It is important to note that a method added to a class using ExpandoMetaClass will effectively modify the class across all the threads of the application; therefore, it is not a local modification. If you need to modify only a single instance of a class, you can access the metaClass property on an instance, as in the following code snippet:

class Customer {
  Long id
  String name
  String lastName
}

def c = new Customer()
c.metaClass.fullName { "$name $lastName" }
c.name = 'John'
c.lastName = 'Ross'

assert c.fullName() == 'John Ross'

There's more...

ExpandoMetaClass can also be used to add:

  • Constructors to a class:
    // defines a new constructor
    Customer.metaClass.constructor << {
      String name -> new Customer(name: name)
    }
    def c = new Customer('John')
    assert 'John' == c.name

    In this example, we use the left shift operator to append a new constructor. The << operator can be used for chaining constructors or methods, like in this example:

    Customer.metaClass.constructor << { String name ->
      new Customer(name: name)
    } << { Long id, String fullName ->
      new Customer(
        id: id,
        name: fullName.split(',')[0],
        lastName: fullName.split(',')[1]
      )
    }
    
    def c0 = new Customer('Mike')
    c0.name = 'Mike'
    def c1 = new Customer(1000, 'Mike,Whitall')
    assert c1.name == 'Mike'
    assert c1.lastName == 'Whitall'

    The Customer class has been enhanced with two concatenated constructors: the first accepting a string and the second accepting a Long and a string. The second constructor uses the split function to assign the name and last name of the customer.

  • Static methods:
    Customer.metaClass.'static'.sayHello = {  ->
      "hello! I'm your customer"
    }
    assert  "hello! I'm your customer" == Customer.sayHello()
  • Properties:
    Customer.metaClass.gsm = null
    def c = new Customer()
    c.gsm = '123456'
    assert '123456' == c.gsm
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.116.137