Upload
ivan-lopez
View
256
Download
0
Embed Size (px)
Citation preview
METAPROGRAMMING OPTIONS WITH GROOVYMETAPROGRAMMING OPTIONS WITH GROOVY
Iván López @ilopmarIván López @ilopmar@LondonGGUG@LondonGGUG
▷ 1st rule: You DO talk about the London-GGUG
▷ 2nd rule: If it's your first London-GGUG you HAVE to talk
▷3rd rule: You have to be awesome (by David Dawson)
London-GGUG rules
Groovy is dynamic
▷ “Delay” to runtime some decisions
▷ Add properties/behaviours in runtime
▷ Wide range of applicability
“Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data.
- Wikipedia
Runtime metaprogramming
▷ Groovy provides this through Meta-Object Protocol (MOP)
▷ Use MOP to:– Invoke methods dynamically– Synthesize classes and methods on
the fly
MetaClass▷ MetaClass registry for each class
▷ Collection of methods/properties
▷ We can always modify the metaclass
MOP Method Injection▷ Injecting methods at code-writing time
▷ We can “open” a class any time
▷ Different techniques:– MetaClass– Categories– Extensions– Mixins vs Traits
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
String chuckIpsum = "If you can see Chuck Norris, he can see you.\If you can not see Chuck Norris you may be only seconds away from death"
println StringUtils.truncate(chuckIpsum, 72)println StringUtils.truncate(chuckIpsum, 72, true)
// ExecutionIf you can see Chuck Norris, he can see you. If you can not see Chuck NoIf you can see Chuck Norris, he can see you. If you can not see Chuck No...
String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '')}
assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true)
Adding methods using MetaClass
class Utils {}
def utilsInstance = new Utils()
Utils.metaClass.version = "3.0"utilsInstance.metaClass.released = true
assert utilsInstance.version == "3.0"assert utilsInstance.released == true
Adding properties using MetaClass
Adding properties using MetaClass
class Utils {}
def utilsInstance = new Utils()
Utils.metaClass.version = "3.0"utilsInstance.metaClass.released = true
assert utilsInstance.version == "3.0"assert utilsInstance.released == true
class Utils {}
def utilsInstance = new Utils()
Utils.metaClass.version = "3.0"utilsInstance.metaClass.released = true
assert utilsInstance.version == "3.0"assert utilsInstance.released == true
Adding properties using MetaClass
class Utils {}
def utilsInstance = new Utils()
Utils.metaClass.version = "3.0"utilsInstance.metaClass.released = true
assert utilsInstance.version == "3.0"assert utilsInstance.released == true
Adding properties using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
// Integerassert '42' == 42.toString()
Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate)}
assert 42.toString() == 'The answer to life, the universe and everything'assert 100.toString() == '100'
// Booleanassert false.toBoolean() == false
Boolean.metaClass.toBoolean = { !delegate }assert false.toBoolean() == true
Overriding methods using MetaClass
Categories
▷ MetaClass changes are “persistent”
▷ Change metaclass in confined code
▷ MOP modified only in the closure
Categories exampleclass StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
use (StringUtils) { println "Lorem ipsum".truncate(5)}
try { println "Lorem ipsum".truncate(5)} catch (MissingMethodException mme) { println mme}
// ExecutionLorem
groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5]Possible solutions: concat(java.lang.String), take(int)
Categories exampleclass StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
use (StringUtils) { println "Lorem ipsum".truncate(5)}
try { println "Lorem ipsum".truncate(5)} catch (MissingMethodException mme) { println mme}
// ExecutionLorem
groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5]Possible solutions: concat(java.lang.String), take(int)
Categories exampleclass StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
use (StringUtils) { println "Lorem ipsum".truncate(5)}
try { println "Lorem ipsum".truncate(5)} catch (MissingMethodException mme) { println mme}
// ExecutionLorem
groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5]Possible solutions: concat(java.lang.String), take(int)
Categories exampleclass StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') }}
use (StringUtils) { println "Lorem ipsum".truncate(5)}
try { println "Lorem ipsum".truncate(5)} catch (MissingMethodException mme) { println mme}
// ExecutionLorem
groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5]Possible solutions: concat(java.lang.String), take(int)
Categories example (II)
import groovy.time.TimeCategory
use (TimeCategory) { println (20.hours + 10.days.from.now) // Fri Jul 17 20:00:00 CEST 2015}
println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)// Fri Jul 17 20:00:00 CEST 2015
Categories example (II)
import groovy.time.TimeCategory
use (TimeCategory) { println (20.hours + 10.days.from.now) // Fri Jul 17 20:00:00 CEST 2015}
println TimeCategory.getHours(20).plus(TimeCategory.getDays(10).from.now)// Fri Jul 17 20:00:00 CEST 2015
Extension modules
▷ JAR file that provides extra methods
▷ Meta-information file
▷ Put jar in classpath to enhance classes
// src/main/groovy/londonggug/StringUtilsExtension.groovypackage londonggugclass StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}
// Execute with:// gradle build// groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy
assert "Lorem..." == "Lorem ipsum". truncate(5, true)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = londonggug.StringUtilsExtension
Extension modules example
package londonggugimport spock.lang.Specificationclass StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) }}
Extension modules example// src/main/groovy/londonggug/StringUtilsExtension.groovypackage londonggugclass StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = londonggug.StringUtilsExtension
// Execute with:// gradle build// groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy
assert "Lorem..." == "Lorem ipsum". truncate(5, true)
package londonggugimport spock.lang.Specificationclass StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) }}
Extension modules example// src/main/groovy/londonggug/StringUtilsExtension.groovypackage londonggugclass StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}
// Execute with:// gradle build// groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy
assert "Lorem..." == "Lorem ipsum". truncate(5, true)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = londonggug.StringUtilsExtension
package londonggugimport spock.lang.Specificationclass StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) }}
Extension modules example// src/main/groovy/londonggug/StringUtilsExtension.groovypackage londonggugclass StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}
// Execute with:// gradle build// groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy
assert "Lorem..." == "Lorem ipsum". truncate(5, true)
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = londonggug.StringUtilsExtension
package londonggugimport spock.lang.Specificationclass StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) }}
// src/main/groovy/londonggug/StringUtilsExtension.groovypackage londonggugclass StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') }}
package londonggugimport spock.lang.Specificationclass StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) }}
// Execute with:// gradle build// groovy -cp build/libs/string-extensions-1.0.jar ExtensionExample1.groovy
assert "Lorem..." == "Lorem ipsum". truncate(5, true)
Extension modules example
# src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModulemoduleName = string-utils-modulemoduleVersion = 0.1extensionClasses = londonggug.StringUtilsExtension
Mixins
▷ “Bring in” or “mix in” implementations from multiple classes
▷ Calls first routed to mixed-in class
▷ Last mixin wins
▷ Not easily un-done
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins example
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SupermanPower { String fly() { "Flying..." }}
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SupermanPower { String fly() { "Flying..." }}
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins example
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SupermanPower { String fly() { "Flying..." }}
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins example
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SupermanPower { String fly() { "Flying..." }}
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins example
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins exampleclass SupermanPower { String fly() { "Flying..." }}
@Mixin([SpidermanPower])class Person {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert !(person instanceof SpidermanPower)
Person.mixin SupermanPowerassert person.fly() == "Flying..."assert !(person instanceof SupermanPower)
class SpidermanPower { String spiderSense() { "Using spider-sense..." }}
Mixins exampleclass SupermanPower { String fly() { "Flying..." }}
“When we started fixing mixin bugs we didn't know if they were a bug or a feature, so we removed mixins and add traits.
- Jochen Theodorou(Greach 2015 Opening Keynote)
Traits▷ Groovy 2.3+
▷ Similar to Java 8 default methods
▷ Supported in JDK 6, 7 and 8
▷ Stateful
▷ Composition over inheritance
▷ Documentation
class Person implements SpidermanPower {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower
Traits exampletrait SpidermanPower { String spiderSense() { "Using spider-sense..." }}
trait SupermanPower { String fly() { "Flying..." }}
class Person implements SpidermanPower {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower
Traits exampletrait SpidermanPower { String spiderSense() { "Using spider-sense..." }}
trait SupermanPower { String fly() { "Flying..." }}
class Person implements SpidermanPower {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower
Traits exampletrait SpidermanPower { String spiderSense() { "Using spider-sense..." }}
trait SupermanPower { String fly() { "Flying..." }}
class Person implements SpidermanPower {}
def person = new Person()assert person.spiderSense() == "Using spider-sense..."assert person instanceof SpidermanPower
def person2 = person.withTraits SupermanPowerassert person2.fly() == "Flying..."assert person2 instanceof SupermanPower
Traits exampletrait SpidermanPower { String spiderSense() { "Using spider-sense..." }}
trait SupermanPower { String fly() { "Flying..." }}
MOP Method Synthesis
▷ Dynamically figure out behaviour upon invocation
▷ It may not exist until it's called/executed
▷ “Intercept, Cache, Invoke” pattern
def p = new Person(name: 'Iván', age: 34)
assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')
assert p.hasProperty('name')assert !p.hasProperty('country')
Check for methods and properties
class Person { String name Integer age
String sayHi() { "Hi, my name is ${name} and I'm ${age}" }
String sayHiTo(String name) { "Hi ${name}, how are you?" }}
def p = new Person(name: 'Iván', age: 35)
assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')
assert p.hasProperty('age')assert !p.hasProperty('country')
Check for methods and properties
class Person { String name Integer age
String sayHi() { "Hi, my name is ${name} and I'm ${age}" }
String sayHiTo(String name) { "Hi ${name}, how are you?" }}
Check for methods and properties
class Person { String name Integer age
String sayHi() { "Hi, my name is ${name} and I'm ${age}" }
String sayHiTo(String name) { "Hi ${name}, how are you?" }}
def p = new Person(name: 'Iván', age: 35)
assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')
assert p.hasProperty('age')assert !p.hasProperty('country')
Check for methods and properties
class Person { String name Integer age
String sayHi() { "Hi, my name is ${name} and I'm ${age}" }
String sayHiTo(String name) { "Hi ${name}, how are you?" }}
def p = new Person(name: 'Iván', age: 35)
assert p.respondsTo('sayHi')assert p.respondsTo('sayHiTo', String)assert !p.respondsTo('goodbye')
assert p.hasProperty('age')assert !p.hasProperty('country')
MethodMissing example
▷ Requirements:– Send notifications to users by different
channels– +50 notifications– Not all notifications by all channels– Extensible and open to future
modifications
MethodMissing exampleabstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ...}
class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" }}
class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" }}
MethodMissing exampleabstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ...}
class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" }}
class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" }}
MethodMissing exampleabstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ...}
class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" }}
class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" }}
MethodMissing exampleabstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ...}
class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" }}
class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" }}
MethodMissing exampleclass NotificationService {
List channels = []
def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation
// Execute it! implementation(args) }}
MethodMissing exampleclass NotificationService {
List channels = []
def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation
// Execute it! implementation(args) }}
MethodMissing exampleclass NotificationService {
List channels = []
def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation
// Execute it! implementation(args) }}
notificationService.sendNewFollower(...)notificationService.sendNewMessage(...)
MethodMissing exampleclass NotificationService {
List channels = []
def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation
// Execute it! implementation(args) }}
MethodMissing exampleclass NotificationService {
List channels = []
def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}"
// Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation
// Execute it! implementation(args) }}
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")class EmailChannel extends Channel { void sendNewFollower(String username, String follower) {…} void sendNewMessage(String username, String msg) {…}}
class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) {…}}
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing example
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
MethodMissing exampledef notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()])
assert !notificationService.respondsTo('sendNewFollower', String, String)notificationService.sendNewFollower("John", "Peter")assert notificationService.respondsTo('sendNewFollower', String, String)
notificationService.sendNewFollower("Mary", "Steve")
notificationService.sendNewMessage("Iván", "Hello!")
// Execution...methodMissing called for sendNewFollower with args [John, Peter]Sending email notification to 'John' for new follower 'Peter'Sending mobile push notification to 'John' for new follower 'Peter'
Sending email notification to 'Mary' for new follower 'Steve'Sending mobile push notification to 'Mary' for new follower 'Steve'
...methodMissing called for sendNewMessage with args [Iván, Hello!]Sending email notification to 'Iván' for new message 'Hello!'
Let’s review some concepts
Metaprogramming out-of-the box
Easy and very powerful
Write better code
Add behaviour easily
Take advantage of this power
Because Groovy, it's groovy
Thanks!Any questions?
@ilopmar
https://github.com/lmivan
Iván López