+ All Categories
Home > Software > Rethinking API Design with Traits

Rethinking API Design with Traits

Date post: 14-Jun-2015
Category:
Upload: spring-io
View: 576 times
Download: 3 times
Share this document with a friend
Description:
Groovy 2.3 introduces the concept of traits in the language. Traits look like interfaces, but allow the developer to add both implementation and state into it. It introduces multiple inheritance in the language while avoiding the diamond problem. Traits will let you rethink the way you design APIs in Groovy, by favoriting composition of behaviors.
Popular Tags:
60
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Rethinking API design with traits By Cédric Champeau
Transcript
Page 1: Rethinking API Design with Traits

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.

Rethinking API design with traits

By Cédric Champeau

Page 2: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

About me

Pivotal employee

Core Groovy committerCompilation configuration

Static type checking & Static compilation

Traits, new template engine, ...

Groovy in Action 2 co-author

Misc OSS contribs (Gradle plugins, deck2pdf, jlangdetect, ...)

2

Page 3: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Social (or why to use #groovylang)

3

Page 4: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Groovy is Open-Source

• Licensed under APL2• 100+ contributors• 10000+ commits• 1.7+ million downloads in 2013• On GitHub since summer 2011• Dependency of 25000+ OSS projects

4

Page 5: Rethinking API Design with Traits

Life without traits(or why it's primarily a static issue)

5

Page 6: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Life without traits, pre Java 8

• Contracts are defined through interfaces• Interfaces are mandatory in a static type system• Base behavior defined through abstract classes• To implement multiple contracts, you need multiple abstract

classes• … or you can use duck typing in Groovy

6

Page 7: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Life without traits, pre Java 8

• Even in Groovy...• Problems with inheritance

• Hard to determine whether a class explicitly or implicitly implements a contract

7

Page 8: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

@Mixin

• Deprecated in Groovy 2.3• Combines static and dynamic features to achieve runtime mixins

8

class Cat { void meow() { println 'Meow!' }}@Mixin(Cat)class YoutubeDog { void bark() { println 'Woof!' }}def dog = new YoutubeDog()dog.meow()dog.bark()

Page 9: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

@Mixin

• Mixed in class is not an instance of the mixin• Problems with inheritance• Memory• Difference between the mixed instance and the mixin instance• Buggy (really)

9

Page 10: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Delegation

• Delegation vs inheritance• Delegation keeps concerns separate

• Cleaner responsibility model

• Not always suitable

• Foo “is a Bar” → inheritance

• Foo “acts as a Bar” → composition

• Optionally requires interfaces

10

Page 11: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

@Delegate : composition model

11

/* An API-limited version of a Stack */class StackOnly<T> { Stack<T> delegate = new Stack<>()

void push(T e) { delegate.push(e) } T pop() { delegate.pop() }}

def t = new StackOnly<String>()t.push 'a't.push 'b'

assert t.pop() == 'b'

Page 12: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

@Delegate : composition model

12

/* An API-limited version of a Stack */class StackOnly<T> { @Delegate(interfaces=false, includes=['push','pop']) Stack<T> delegate = new Stack<>()}

def t = new StackOnly<String>()t.push 'a't.push 'b'

assert t.pop() == 'b'assert t.get(0) == 'a' // will fail because get is not delegated

Page 13: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

@Delegate : composition model

• Excellent fit when the delegating object requires a subset of the features of the delegate

• Delegation + interfaces used as the poor man's multiple inheritance solution

13

Page 14: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Groovy specific : Extension methods

• URL#getText(String encoding)• Allows adding behavior to existing classes• Supported by @CompileStatic• Add your own thanks to extension modules

– See http://docs.groovy-lang.org/latest/html/documentation/#_extension_modules

• Not visible from Java

14

Page 15: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Java 8 default methods

• Some kind of traits• An interface

• With default implementation

15

public interface Greeter { default void greet() { System.out.println("Hello!"); }}

// Usable from Groovyclass DefaultGreeter implements Greeter {}

Page 16: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Java 8 default methods

• Stateless• No state = No diamond issue

• Virtual

16

class VirtualGreeter implements Greeter { @Override void greet() { println "Virtual Hello!" }}

● Backward compatible● Groovy does not support writing default methods

Page 17: Rethinking API Design with Traits

Traits in Groovy

17

Page 18: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits

• Similar to Java 8 default methods• Supported in JDK 6, 7 and 8• Stateful• Composable• Not really virtual...

18

Page 19: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the basics

• Declared through the “trait” new keyword• Requires Groovy 2.3+

19

trait Flying { void fly() { println "I'm flying!" }}trait Quacking { void quack() { println 'Quack!' }}class Duck implements Flying, Quacking {}

Page 20: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the basics

• Override like any other interface• Compatible with @CompileStatic

20

@CompileStaticclass Duck implements Flying, Quacking { @Override void fly() { println "Duck flying!" quack() }}

Page 21: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the basics

• Stateful

21

trait Geolocalized { double longitude double latitude String placeName}class City implements Geolocalized {}

def city = new City(longitude: 32.803468, latitude: -96.769879, placeName: 'Dallas')

Page 22: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the basics

• Multiple stateful traits allowed!• Major difference with inheritance

22

trait Inhabited { int population}class City implements Geolocalized, Inhabited {}def city = new City( longitude: 32.803468, latitude: -96.769879, placeName: 'Dallas', population: 1_280_000)println city.population

Page 23: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : first insight...

• Traits can be designed as small bricks of behavior• Classes can be composed with traits• Increases reusability of components

23

Page 24: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : beyond basics

• A trait may inherit from another trait

24

trait Named { String name}trait Geolocalized extends Named { long latitude long longitude}class City implements Geolocalized {}

Page 25: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : beyond basics

• A trait may inherit from multiple traits

25

trait WithId { Long id}trait Place implements Geolocalized, WithId {}

class City implements Place {}

Page 26: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : beyond basics

• A trait can define abstract methods

26

trait Polite { abstract String getName() void thank() { println "Thank you, my name is $name" }}class Person implements Polite { String name}

def p = new Person(name: 'Rob')p.thank()

Page 27: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : beyond basics

• Why implements?• From Java

• a trait is only visible as an interface

• default methods of traits are not default methods of interface

• From Groovy

• A trait can be seen as an interface on steroids

• A trait without default implementation is nothing but an interface

27

Page 28: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : conflicts

• What if two traits define the same method ?

28

trait Worker { void run() { println "I'm a worker!" }}trait Runner { void run() { println "I'm a runner" }}class WhoAmI implements Worker, Runner {}

Page 29: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : conflicts

• Conflict resolution rule: last trait wins

29

class WhoAmI implements Worker, Runner {}

def o = new WhoAmI()o.run() // prints "runner"

class WhoAmI implements Runner, Worker {}

def o = new WhoAmI()o.run() // prints "worker"

Page 30: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : explicit conflict resolution

• You can choose which method to call

30

trait Lucky { String adj() { 'lucky'} String name() { 'Alice' }}trait Unlucky { String adj() { 'unlucky'} String name() { 'Bob' }}

class LuckyBob implements Lucky, Unlucky { String adj() { Lucky.super.adj() // we select the "adj" implementation from Lucky } String name() { Unlucky.super.name() // and the "name" implementation from Unlucky } String toString() { "${adj()} ${name()}"}}

def l = new LuckyBob()assert l.toString() == 'lucky Bob'

Page 31: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : dynamic too!

• Groovy is a dynamic language, so traits should be too!

31

trait ExpandoTrait { private final Map<String,?> values = [:] def propertyMissing(String name) { values.get(name) } void setProperty(String name, value) { println "Setting dynamic property $name" values.put(name, value) }}class Person implements ExpandoTrait { String name}def p = new Person(name: 'Cedric')assert p.name == 'Cedric'p.age = 35assert p.age == 35

Page 32: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : dynamic too!

• Traits can call methods which are defined in the implementing class

32

class A { void foo() { println 'A' }}

class B {}

trait CallsFooIfAvailable { void bar() { if (metaClass.getMetaMethod('foo')) { foo() } }}class SubA extends A implements CallsFooIfAvailable {}class SubB extends B implements CallsFooIfAvailable {}

[new SubA(), new SubB()]*.bar()

Page 33: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : what's this?

• “this” represents the trait'ed class

33

trait WhoIsThis { def self() { this }}

class Me implements WhoIsThis {}

def me = new Me()assert me.is(me.self())

Page 34: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : runtime coercion

• Groovy way of implementing interfaces

34

interface MyListener { void onEvent(String name)}

def explicit = { println it } as MyListenerMyListener implicit = { println it }

explicit.onEvent 'Event 1'implicit.onEvent 'Event 2'

Page 35: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : runtime coercion

• Same can be done with traits

35

trait Greeter { abstract String getName() void greet() { println "Hello $name" }}

def greeter = { 'Dan' } as GreeterGreeter greeter2 = { 'Dan' }

greeter.greet()greeter2.greet()

Page 36: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : runtime coercion

• Support for multiple traits at runtime

36

class PoorObject { void doSomething() { println "I can't do much" }}trait SuperPower { void superPower() { println 'But I have superpowers!' }}trait Named { String name}def o = new PoorObject()def named = o as Namednamed.name = 'Bob' // now can set a namenamed.doSomething() // and still call doSomethingdef powered = o as SuperPowerpowered.superPower() // now it has superpowerspowered.doSomething() // it can do somethingdef superHero = o.withTraits(SuperPower, Named) // multiple traits at once!superHero.name = 'SuperBob' // then you can set a namesuperHero.doSomething() // still call doSomethingsuperHero.superPower() // but you have super powers too!

Page 37: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : runtime coercion gotchas

• Coercion occurs at runtime, at call site• Returned object implements all interfaces and traits

• But doesn't extend the original class!

37

class Person { String name }trait Flying { void fly() { println "$name flying!" }}

def p = new Person(name: 'Thor')def f = p as Flyingassert !p.is(f) // not the same instanceassert f instanceof Flying // it's a Flying objectassert !(f instanceof Person) // but it's not a Person anymore!println f.name // yet, name still existsf.fly()

Page 38: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : who's fastest?

• Courtesy of Erik Pragt (@epragt)

38

trait NoStackTrace { Throwable fillInStackTrace() { return this }}

class ExpensiveException extends RuntimeException { }class CheapException extends RuntimeException implements NoStackTrace {}

def e1 = new CheapException()def e2 = new ExpensiveException()def e3 = new ExpensiveException() as NoStackTrace

Page 39: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : who's fastest?

• Courtesy of Erik Pragt (@epragt)

39

user system cpu real

Cheap Exception

42 0 42 43

Expensive Exception

6722 3 6725 6734

Make expensive exception cheap

14982 7 14989 15010

See https://gist.github.com/bodiam/48a06dc9c74a90dfba8c

Page 40: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : not virtual

• Methods from traits are “copied” where applied

40

trait HasColor { String getColor() { 'red' }}

class Colored implements HasColor {}

def c = new Colored()assert c.color == 'red'

class Colored2 implements HasColor { String getColor() { 'blue' }}

def c2 = new Colored2()assert c2.color == 'blue'

Takes precedence

Page 41: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : not virtual

• Methods from traits are “copied” where applied

41

class Colored3 { String getColor() { 'yellow' }}class Colored4 extends Colored3 implements HasColor {}

def c4 = new Colored4()assert c4.color == 'red'

Takes precedence

● Traits can be used to share overrides!

Page 42: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the meaning of “super”

• super has a special meaning in traits

42

Lucky.super.foo()

Qualified super● But super can also be used for chaining traits

interface MessageHandler { void onMessage(String tag, Map<?,?> payload)}

trait DefaultMessageHandler implements MessageHandler { void onMessage(String tag, Map<?, ?> payload) { println "Received message [$tag]" }}

Page 43: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the meaning of “super”

class MyHandler implements DefaultMessageHandler {}

def h = new MyHandler()h.onMessage("event",[:])

Page 44: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : the meaning of “super”

// A message handler which logs the message and// passes the message to the next handler in the chaintrait ObserverHandler implements MessageHandler { void onMessage(String tag, Map<?, ?> payload) { println "I observed a message: $tag" if (payload) { println "Payload: $payload" } // pass to next handler in the chain super.onMessage(tag, payload) }}

class MyNewHandler implements DefaultMessageHandler, ObserverHandler {}def h2 = new MyNewHandler()h2.onMessage('event 2', [pass:true])

Page 45: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : chaining with runtime coercion

class BaseHandler implements MessageHandler { @Override void onMessage(final String tag, final Map<?, ?> payload) { println "Received [$tag] from base handler" }}

trait UpperCaseHandler implements MessageHandler { void onMessage(String tag, Map<?, ?> payload) { super.onMessage(tag.toUpperCase(), payload) }}trait DuplicatingHandler implements MessageHandler { void onMessage(String tag, Map<?, ?> payload) { super.onMessage(tag, payload) super.onMessage(tag, payload) }}

def h = new BaseHandler()h = h.withTraits(UpperCaseHandler, DuplicatingHandler, ObserverHandler)h.onMessage('runtime', [:])

Page 46: Rethinking API Design with Traits

Gotchas

46

Page 47: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : inheritance of state gotchas

• Backing fields are per trait

47

trait IntCouple { int x = 1 int y = 2 int sum() { x+y }}

class BaseElem implements IntCouple { int f() { sum() }}def base = new BaseElem()assert base.f() == 3

Page 48: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : inheritance of state gotchas

• Backing fields are per trait

48

class Elem implements IntCouple { int x = 3 int y = 4 int f() { sum() }}def elem = new Elem()println elem.f()

What do you think?

Page 49: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : inheritance of state gotchas

• Backing fields are per trait

49

trait IntCouple { int x = 1 int y = 2 int sum() { getX()+getY() }}

class Elem implements IntCouple { int x = 3 int y = 4 int f() { sum() }}

def elem = new Elem()assert elem.f() == 7

Page 50: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : inheritance of state gotchas

• Avoids the diamond problem

50

class BaseElem implements IntCouple {}BaseElem.declaredFields.each { println it.name}

// outputsgotchas_IntCouple__xgotchas_IntCouple__y

Fields are “namespaced”

Page 51: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : AST transformations

51

Page 52: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : AST transformations

52

• Unless stated differently, consider AST xforms incompatible• Both for technical and logical reasons

• Is the AST xform applied on the trait or should it be applied when the trait is applied?

Page 53: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : AST transformations

53

• This works

@CompileStatictrait StaticallyCompiledTrait { int x int y int sum() { x + y }}

Page 54: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : AST transformations

54

• This works too

trait Delegating { @Delegate Map values = [:]}

class Dynamic implements Delegating {}new Dynamic().put('a','b')

Page 55: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : AST transformations

55

• But this doesn't

trait Named { String name}

@ToString// ToString misses the "name" propertyclass Foo implements Named {}

Page 56: Rethinking API Design with Traits

API design?

56

Page 57: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : rethinking API design conclusion

57

• Traits let you think about components• Traits can be seen as generalized delegation• Methods can accept traits as arguments• Traits are stackable• Traits are visible as interfaces from Java

Page 58: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Traits : API independent extension with DSL

58

• DSL can be used to augment an API• No need to subclass the base API

trait ListOfStrings implements List<String> { int totalLength() { sum { it.length() } }}

trait FilteringList implements List<String> { ListOfStrings filter() { findAll { it.contains('G') || it.contains('J') } as ListOfStrings }}

def list = ['Groovy', 'rocks', 'the', 'JVM']println list.withTraits(FilteringList).filter().totalLength()

Page 59: Rethinking API Design with Traits

#s2gx #groovylang @CedricChampeau

Conclusion

59

• Traits extend the benefit of interfaces to concrete classes• Traits do not suffer the diamond problem• Traits allow a new set of API to be written• APIs can be augmented without modifications or extension

http://docs.groovy-lang.org/2.3.6/html/documentation/core-traits.html

Page 60: Rethinking API Design with Traits

60

@CedricChampeau

melix

http://melix.github.io/blog


Recommended