| ////////////////////////////////////////// |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| |
| ////////////////////////////////////////// |
| |
| = Traits |
| |
| Traits are a structural construct of the language which allows: |
| |
| * composition of behaviors |
| * runtime implementation of interfaces |
| * behavior overriding |
| * compatibility with static type checking/compilation |
| |
| They can be seen as *interfaces* carrying both *default implementations* and *state*. A trait is defined using the |
| `trait` keyword: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0] |
| ---- |
| <1> declaration of a trait |
| <2> declaration of a method inside a trait |
| |
| Then it can be used like a normal interface using the `implements` keyword: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=bird,indent=0] |
| ---- |
| <1> Adds the trait `FlyingAbility` to the `Bird` class capabilities |
| <2> instantiate a new `Bird` |
| <3> the `Bird` class automatically gets the behavior of the `FlyingAbility` trait |
| |
| Traits allow a wide range of capabilities, from simple composition to testing, which are described thoroughly in this section. |
| |
| == Methods |
| === Public methods |
| |
| Declaring a method in a trait can be done like any regular method in a class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0] |
| ---- |
| <1> declaration of a trait |
| <2> declaration of a method inside a trait |
| |
| === Abstract methods |
| |
| In addition, traits may declare _abstract_ methods too, which therefore need to be implemented in the class implementing the trait: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=greetable,indent=0] |
| ---- |
| <1> implementing class will have to declare the `name` method |
| <2> can be mixed with a concrete method |
| |
| Then the trait can be used like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=greetable_person,indent=0] |
| ---- |
| <1> implement the trait `Greetable` |
| <2> since `name` was abstract, it is required to implement it |
| <3> then `greeting` can be called |
| |
| === Private methods |
| |
| Traits may also define private methods. Those methods will not appear in the trait contract interface: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=private_method_in_trait,indent=0] |
| ---- |
| <1> define a private method `greetingMessage` in the trait |
| <2> the public `greet` message calls `greetingMessage` by default |
| <3> create a class implementing the trait |
| <4> `greet` can be called |
| <5> but not `greetingMessage` |
| |
| WARNING: Traits only support `public` and `private` methods. Neither `protected` nor `package private` scopes are |
| supported. |
| |
| === Final methods |
| |
| If we have a class implementing a trait, conceptually implementations from the trait methods |
| are "inherited" into the class. But, in reality, there is no base class containing such |
| implementations. Rather, they are woven directly into the class. A final modifier on a method |
| just indicates what the modifier will be for the woven method. While it would likely be |
| considered bad style to inherit and override or multiply inherit methods with the same |
| signature but a mix of final and non-final variants, Groovy doesn't prohibit this scenario. |
| Normal method selection applies and the modifier used will be determined from the resulting method. |
| You might consider creating a base class which implements the desired trait(s) if you |
| want trait implementation methods that can't be overridden. |
| |
| == The meaning of this |
| |
| `this` represents the implementing instance. Think of a trait as a superclass. This means that when you write: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=meaningofthis_header,indent=0] |
| ---- |
| |
| then calling: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=meaningofthis_snippet,indent=0] |
| ---- |
| |
| will return the same instance: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=meaningofthis_assert,indent=0] |
| ---- |
| |
| == Interfaces |
| |
| Traits may implement interfaces, in which case the interfaces are declared using the `implements` keyword: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_implementing_interface,indent=0] |
| ---- |
| <1> declaration of a normal interface |
| <2> add `Named` to the list of implemented interfaces |
| <3> declare a class that implements the `Greetable` trait |
| <4> implement the missing `name` method |
| <5> the `greeting` implementation comes from the trait |
| <6> make sure `Person` implements the `Named` interface |
| <7> make sure `Person` implements the `Greetable` trait |
| |
| == Properties |
| |
| A trait may define properties, like in the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_with_property,indent=0] |
| ---- |
| <1> declare a property `name` inside a trait |
| <2> declare a class which implements the trait |
| <3> the property is automatically made visible |
| <4> it can be accessed using the regular property accessor |
| <5> or using the regular getter syntax |
| |
| == Fields |
| === Private fields |
| |
| Since traits allow the use of private methods, it can also be interesting to use private fields to store state. Traits |
| will let you do that: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_with_private_field,indent=0] |
| ---- |
| <1> declare a private field `count` inside a trait |
| <2> declare a public method `count` that increments the counter and returns it |
| <3> declare a class that implements the `Counter` trait |
| <4> the `count` method can use the private field to keep state |
| |
| TIP: This is a major difference with http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html[Java 8 virtual extension methods]. While virtual extension methods |
| do not carry state, traits can. Moreover, traits in Groovy are supported starting with Java 6, because their implementation does not rely on virtual extension methods. This |
| means that even if a trait can be seen from a Java class as a regular interface, that interface will *not* have default methods, only abstract ones. |
| |
| === Public fields |
| |
| Public fields work the same way as private fields, but in order to avoid the http://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem[diamond problem], |
| field names are remapped in the implementing class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_with_public_field,indent=0] |
| ---- |
| <1> declare a public *field* inside the trait |
| <2> declare a class implementing the trait |
| <3> create an instance of that class |
| <4> the public field is available, but renamed |
| |
| The name of the field depends on the fully qualified name of the trait. All dots (`.`) in package are replaced with an underscore (`_`), and the final name includes a double underscore. |
| So if the type of the field is `String`, the name of the package is `my.package`, the name of the trait is `Foo` and the name of the field is `bar`, |
| in the implementing class, the public field will appear as: |
| |
| [source,groovy] |
| ---- |
| String my_package_Foo__bar |
| ---- |
| |
| WARNING: While traits support public fields, it is not recommended to use them and considered as a bad practice. |
| |
| == Composition of behaviors |
| |
| Traits can be used to implement multiple inheritance in a controlled way. For example, we can have the following traits: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=flying_simple,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=speaking_simple,indent=0] |
| ---- |
| |
| And a class implementing both traits: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=speakingduck,indent=0] |
| ---- |
| <1> the `Duck` class implements both `FlyingAbility` and `SpeakingAbility` |
| <2> creates a new instance of `Duck` |
| <3> we can call the method `fly` from `FlyingAbility` |
| <4> but also the method `speak` from `SpeakingAbility` |
| |
| Traits encourage the reuse of capabilities among objects, and the creation of new classes by the composition of existing behavior. |
| |
| == Overriding default methods |
| |
| Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, we |
| can slightly change the example above, by having a duck which quacks: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=quackingduck,indent=0] |
| ---- |
| <1> define a method specific to `Duck`, named `quack` |
| <2> override the default implementation of `speak` so that we use `quack` instead |
| <3> the duck is still flying, from the default implementation |
| <4> `quack` comes from the `Duck` class |
| <5> `speak` no longer uses the default implementation from `SpeakingAbility` |
| |
| == Extending traits |
| === Simple inheritance |
| Traits may extend another trait, in which case you must use the `extends` keyword: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_inherit,indent=0] |
| ----- |
| <1> the `Named` trait defines a single `name` property |
| <2> the `Polite` trait *extends* the `Named` trait |
| <3> `Polite` adds a new method which has access to the `name` property of the super-trait |
| <4> the `name` property is visible from the `Person` class implementing `Polite` |
| <5> as is the `introduce` method |
| |
| === Multiple inheritance |
| |
| Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in the `implements` |
| clause: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=trait_multiple_inherit,indent=0] |
| ---- |
| <1> `WithId` trait defines the `id` property |
| <2> `WithName` trait defines the `name` property |
| <3> `Identified` is a trait which inherits both `WithId` and `WithName` |
| |
| == Duck typing and traits |
| === Dynamic code |
| Traits can call any dynamic code, like a normal Groovy class. This means that you can, in the body of a method, call |
| methods which are supposed to exist in an implementing class, without having to explicitly declare them in an interface. |
| This means that traits are fully compatible with duck typing: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=ducktyping,indent=0] |
| ---- |
| <1> the `SpeakingDuck` expects the `quack` method to be defined |
| <2> the `Duck` class does implement the method using _methodMissing_ |
| <3> calling the `speak` method triggers a call to `quack` which is handled by `methodMissing` |
| |
| === Dynamic methods in a trait |
| |
| It is also possible for a trait to implement MOP methods like `methodMissing` or `propertyMissing`, in which case implementing classes |
| will inherit the behavior from the trait, like in this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=dynamicobject,indent=0] |
| ---- |
| <1> create a trait implementing several MOP methods |
| <2> the `Dynamic` class defines a property |
| <3> the `Dynamic` class defines a method |
| <4> calling an existing property will call the method from `Dynamic` |
| <5> calling an non-existing property will call the method from the trait |
| <6> will call `setProperty` defined on the trait |
| <7> will call `getProperty` defined on the trait |
| <8> calling an existing method on `Dynamic` |
| <9> but calling a non existing method thanks to the trait `methodMissing` |
| |
| == Multiple inheritance conflicts |
| === Default conflict resolution |
| |
| It is possible for a class to implement multiple traits. If some trait defines a method with the same signature as a |
| method in another trait, we have a conflict: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=multiple_inherit_default,indent=0] |
| ----- |
| <1> trait `A` defines a method named `exec` returning a `String` |
| <2> trait `B` defines the very same method |
| <3> class `C` implements both traits |
| |
| In this case, the default behavior is that the method from the *last declared trait* in the `implements` clause wins. |
| Here, `B` is declared after `A` so the method from `B` will be picked up: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=multiple_inherit_default_assert,indent=0] |
| ---- |
| |
| === User conflict resolution |
| |
| In case this behavior is not the one you want, you can explicitly choose which method to call using the `Trait.super.foo` syntax. |
| In the example above, we can ensure the method from trait A is invoked by writing this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=multiple_inherit_user,indent=0] |
| ---- |
| <1> explicit call of `exec` from the trait `A` |
| <2> calls the version from `A` instead of using the default resolution, which would be the one from `B` |
| |
| == Runtime implementation of traits |
| === Implementing a trait at runtime |
| Groovy also supports implementing traits dynamically at runtime. It allows you to "decorate" an existing object using a |
| trait. As an example, let's start with this trait and the following class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=runtime_header,indent=0] |
| ---- |
| <1> the `Extra` trait defines an `extra` method |
| <2> the `Something` class does *not* implement the `Extra` trait |
| <3> `Something` only defines a method `doSomething` |
| |
| Then if we do: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=runtime_fail,indent=0] |
| ---- |
| |
| the call to extra would fail because `Something` is not implementing `Extra`. It is possible to do it at runtime with |
| the following syntax: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=runtime_success,indent=0] |
| ---- |
| <1> use of the *as* keyword to coerce an object to a trait *at runtime* |
| <2> then `extra` can be called on the object |
| <3> and `doSomething` is still callable |
| |
| IMPORTANT: When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteed |
| that the coerced object will implement both the trait *and* the interfaces that the original object implements, but |
| the result will *not* be an instance of the original class. |
| |
| === Implementing multiple traits at once |
| |
| Should you need to implement several traits at once, you can use the `withTraits` method instead of the `as` keyword: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=withtraits_header,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=withtraits_fail,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=withtraits_success,indent=0] |
| ---- |
| <1> call to `methodFromA` will fail because `C` doesn't implement `A` |
| <2> call to `methodFromB` will fail because `C` doesn't implement `B` |
| <3> `withTrait` will wrap `c` into something which implements `A` and `B` |
| <4> `methodFromA` will now pass because `d` implements `A` |
| <5> `methodFromB` will now pass because `d` also implements `B` |
| |
| IMPORTANT: When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteed |
| that the coerced object will implement both the traits *and* the interfaces that the original object implements, but |
| the result will *not* be an instance of the original class. |
| |
| == Chaining behavior |
| |
| Groovy supports the concept of _stackable traits_. The idea is to delegate from one trait to the other if the current trait |
| is not capable of handling a message. To illustrate this, let's imagine a message handler interface like this: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=messagehandler,indent=0] |
| ----- |
| |
| Then you can compose a message handler by applying small behaviors. For example, let's define a default handler in the |
| form of a trait: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=defaulthandler,indent=0] |
| ----- |
| |
| Then any class can inherit the behavior of the default handler by implementing the trait: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=simplehandler,indent=0] |
| ----- |
| |
| Now what if you want to log all messages, in addition to the default handler? One option is to write this: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=simplehandlerwithlogging,indent=0] |
| ----- |
| <1> explicitly implement the `on` method |
| <2> perform logging |
| <3> continue by delegating to the `DefaultHandler` trait |
| |
| This works but this approach has drawbacks: |
| |
| . the logging logic is bound to a "concrete" handler |
| . we have an explicit reference to `DefaultHandler` in the `on` method, meaning that if we happen to change the trait that our class implements, code will be broken |
| |
| As an alternative, we can write another trait which responsibility is limited to logging: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=logginghandler,indent=0] |
| ----- |
| <1> the logging handler is itself a handler |
| <2> prints the message it receives |
| <3> then `super` makes it delegate the call to the next trait in the chain |
| |
| Then our class can be rewritten as this: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=handlerwithlogger,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=handlerwithlogger_assert,indent=0] |
| ----- |
| |
| which will print: |
| |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=handlerwithlogger_assert_output,indent=0] |
| ---- |
| |
| As the priority rules imply that `LoggerHandler` wins because it is declared last, then a call to `on` will use |
| the implementation from `LoggingHandler`. But the latter has a call to `super`, which means the next trait in the |
| chain. Here, the next trait is `DefaultHandler` so *both* will be called: |
| |
| The interest of this approach becomes more evident if we add a third handler, which is responsible for handling messages |
| that start with `say`: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=sayhandler,indent=0] |
| ----- |
| <1> a handler specific precondition |
| <2> if the precondition is not met, pass the message to the next handler in the chain |
| |
| Then our final handler looks like this: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=implementinghandler,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=implementinghandler_assert,indent=0] |
| ----- |
| |
| Which means: |
| |
| * messages will first go through the logging handler |
| * the logging handler calls `super` which will delegate to the next handler, which is the `SayHandler` |
| * if the message starts with `say`, then the handler consumes the message |
| * if not, the `say` handler delegates to the next handler in the chain |
| |
| This approach is very powerful because it allows you to write handlers that do not know each other and yet let you |
| combine them in the order you want. For example, if we execute the code, it will print: |
| |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=implementinghandler_output,indent=0] |
| ---- |
| |
| but if we move the logging handler to be the second one in the chain, the output is different: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=alternatehandler,indent=0] |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=alternatehandler_assert,indent=0] |
| ---- |
| |
| prints: |
| |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=alternatehandler_output,indent=0] |
| ---- |
| |
| The reason is that now, since the `SayHandler` consumes the message without calling `super`, the logging handler is |
| not called anymore. |
| |
| === Semantics of super inside a trait |
| |
| If a class implements multiple traits and a call to an unqualified `super` is found, then: |
| |
| . if the class implements another trait, the call delegates to the next trait in the chain |
| . if there isn't any trait left in the chain, `super` refers to the super class of the implementing class (_this_) |
| |
| For example, it is possible to decorate final classes thanks to this behavior: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=decoratefinalclass,indent=0] |
| ---- |
| <1> define a trait named `Filtering`, supposed to be applied on a `StringBuilder` at runtime |
| <2> redefine the `append` method |
| <3> remove all 'o's from the string |
| <4> then delegate to `super` |
| <5> in case `toString` is called, delegate to `super.toString` |
| <6> runtime implementation of the `Filtering` trait on a `StringBuilder` instance |
| <7> the string which has been appended no longer contains the letter `o` |
| |
| In this example, when `super.append` is encountered, there is no other trait implemented by the target object, so the |
| method which is called is the original `append` method, that is to say the one from `StringBuilder`. The same trick |
| is used for `toString`, so that the string representation of the proxy object which is generated delegates to the |
| `toString` of the `StringBuilder` instance. |
| |
| == Advanced features |
| === SAM type coercion |
| |
| If a trait defines a single abstract method, it is candidate for SAM (Single Abstract Method) type coercion. For example, |
| imagine the following trait: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=sam_trait,indent=0] |
| ---- |
| <1> the `greet` method is not abstract and calls the abstract method `getName` |
| <2> `getName` is an abstract method |
| |
| Since `getName` is the _single abstract method_ in the `Greeter` trait, you can write: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=sam_trait_assignment,indent=0] |
| ---- |
| <1> the closure "becomes" the implementation of the `getName` single abstract method |
| |
| or even: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=sam_trait_method,indent=0] |
| ---- |
| <1> the greet method accepts the SAM type Greeter as parameter |
| <2> we can call it directly with a closure |
| |
| === Differences with Java 8 default methods |
| |
| In Java 8, interfaces can have default implementations of methods. If a class implements an interface and does not provide |
| an implementation for a default method, then the implementation from the interface is chosen. Traits behave the same but |
| with a major difference: the implementation from the trait is *always* used if the class declares the trait in its interface |
| list *and* that it doesn't provide an implementation *even* if a super class does. |
| |
| This feature can be used to compose behaviors in an very precise way, in case you want to override the behavior of an |
| already implemented method. |
| |
| To illustrate the concept, let's start with this simple example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=forceoverride_header,indent=0] |
| ---- |
| |
| In this example, we create a simple test case which uses two properties (_config_ and _shell_) and uses those in |
| multiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration. |
| One option is to create a subclass of `SomeTest`: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=forceoverride_extends,indent=0] |
| ---- |
| |
| It works, but what if you have actually multiple test classes, and that you want to test the new configuration for all |
| those test classes? Then you would have to create a distinct subclass for each test class: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=forceoverride_extends2,indent=0] |
| ---- |
| |
| Then what you see is that the `setup` method of both tests is the same. The idea, then, is to create a trait: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=forceoverride_trait,indent=0] |
| ---- |
| |
| Then use it in the subclasses: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=forceoverride_miss_header,indent=0] |
| ... |
| ---- |
| |
| It would allow us to dramatically reduce the boilerplate code, and reduces the risk of forgetting to change the setup |
| code in case we decide to change it. Even if `setup` is already implemented in the super class, since the test class declares |
| the trait in its interface list, the behavior will be borrowed from the trait implementation! |
| |
| This feature is in particular useful when you don't have access to the super class source code. It can be used to |
| mock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keep |
| the overridden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course, |
| is to override the method in *every* place you would have used the new code. |
| |
| |
| IMPORTANT: It's worth noting that if you use runtime traits, the methods from the trait are *always* preferred to those of the proxied |
| object: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=runtime_forceoverride,indent=0] |
| ---- |
| <1> the `Person` class defines a `name` property which results in a `getName` method |
| <2> `Bob` is a trait which defines `getName` as returning `Bob` |
| <3> the default object will return _Alice_ |
| <4> `p2` coerces `p` into `Bob` at runtime |
| <5> `getName` returns _Bob_ because `getName` is taken from the *trait* |
| |
| IMPORTANT: Again, don't forget that dynamic trait coercion returns a distinct object which only implements the original |
| interfaces, as well as the traits. |
| |
| == Differences with mixins |
| |
| There are several conceptual differences with mixins, as they are available in Groovy. Note that we are talking about |
| runtime mixins, not the @Mixin annotation which is deprecated in favour of traits. |
| |
| First of all, methods defined in a trait are visible in bytecode: |
| |
| * internally, the trait is represented as an interface (without default or static methods) and several helper classes |
| * this means that an object implementing a trait effectively implements an _interface_ |
| * those methods are visible from Java |
| * they are compatible with type checking and static compilation |
| |
| Methods added through a mixin are, on the contrary, only visible at runtime: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=diff_mixin,indent=0] |
| ---- |
| <1> class `A` defines `methodFromA` |
| <2> class `B` defines `methodFromB` |
| <3> mixin B into A |
| <4> we can call `methodFromA` |
| <5> we can also call `methodFromB` |
| <6> the object is an instance of `A` |
| <7> but it's *not* an instanceof `B` |
| |
| The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instances |
| are *not* modified, so if you mixin some class into another, there isn't a third class generated, and methods which respond to |
| A will continue responding to A even if mixed in. |
| |
| == Static methods, properties and fields |
| |
| WARNING: The following instructions are subject to caution. Static member support is work in progress and still experimental. The |
| information below is valid for {groovyVersion} only. |
| |
| It is possible to define static methods in a trait, but it comes with numerous limitations: |
| |
| * Traits with static methods cannot be compiled statically or type checked. All static methods, |
| properties and field are accessed dynamically (it's a limitation from the JVM). |
| * Static methods do not appear within the generated interfaces for each trait. |
| * The trait is interpreted as a _template_ for the implementing class, which means that each |
| implementing class will get its own static methods, properties and fields. So a static member |
| declared on a trait doesn't belong to the `Trait`, but to it's implementing class. |
| * You should typically not mix static and instance methods of the same signature. The normal |
| rules for applying traits apply (including multiple inheritance conflict resolution). If the |
| method chosen is static but some implemented trait has an instance variant, a compilation error |
| will occur. If the method chosen is the instance variant, the static variant will be ignored |
| (the behavior is similar to static methods in Java interfaces for this case). |
| |
| Let's start with a simple example: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=staticfield_header,indent=0] |
| ----- |
| <1> the static field is declared in the trait |
| <2> a static method is also declared in the trait |
| <3> the static field is updated _within_ the trait |
| <4> a static method _init_ is made available to the implementing class |
| <5> the static field is _remapped_ to avoid the diamond issue |
| |
| As usual, it is not recommended to use public fields. Anyway, should you want this, you must understand that the following code would fail: |
| |
| [source,groovy] |
| ----- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=staticfield_notontrait,indent=0] |
| ----- |
| |
| because there is _no_ static field _CALLED_ defined on the trait itself. Likewise, if you have two distinct implementing classes, each one gets a distinct static field: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=staticfield_distinct,indent=0] |
| ---- |
| <1> class `Bar` implements the trait |
| <2> class `Baz` also implements the trait |
| <3> `init` is only called on `Bar` |
| <4> the static field `CALLED` on `Bar` is updated |
| <5> but the static field `CALLED` on `Baz` is not, because it is *distinct* |
| |
| == Inheritance of state gotchas |
| |
| We have seen that traits are stateful. It is possible for a trait to define fields or properties, but when a class implements a trait, it gets those fields/properties on |
| a per-trait basis. So consider the following example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=intcouple,indent=0] |
| ---- |
| |
| |
| The trait defines two properties, `x` and `y`, as well as a `sum` method. Now let's create a class which implements the trait: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=intcouple_impl,indent=0] |
| ---- |
| |
| The result of calling `f` is `3`, because `f` delegates to `sum` in the trait, which has state. But what if we write this instead? |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override,indent=0] |
| ---- |
| <1> Override property `x` |
| <2> Override property `y` |
| <3> Call `sum` from trait |
| |
| If you call `elem.f()`, what is the expected output? Actually it is: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override_assert,indent=0] |
| ---- |
| |
| The reason is that the `sum` method accesses the _fields_ of the trait. So it is using the `x` and `y` values defined |
| in the trait. If you want to use the values from the implementing class, then you need to dereference fields by using |
| getters and setters, like in this last example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=intcouple_impl_override_directgetter,indent=0] |
| ---- |
| |
| == Self types |
| === Type constraints on traits |
| |
| Sometimes you will want to write a trait that can only be applied to some type. For example, you may want to apply a |
| trait on a class that extends another class which is beyond your control, and still be able to call those methods. |
| To illustrate this, let's start with this example: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=selftype_intro,indent=0] |
| ---- |
| <1> A `Service` class, beyond your control (in a library, ...) defines a `sendMessage` method |
| <2> A `Device` class, beyond your control (in a library, ...) |
| <3> Defines a communicating trait for devices that can call the service |
| <4> Defines `MyDevice` as a communicating device |
| <5> The method from the trait is called, and `id` is resolved |
| |
| It is clear, here, that the `Communicating` trait can only apply to `Device`. However, there's no explicit |
| contract to indicate that, because traits cannot extend classes. However, the code compiles and runs perfectly |
| fine, because `id` in the trait method will be resolved dynamically. The problem is that there is nothing that |
| prevents the trait from being applied to any class which is *not* a `Device`. Any class which has an `id` would |
| work, while any class that does not have an `id` property would cause a runtime error. |
| |
| The problem is even more complex if you want to enable type checking or apply `@CompileStatic` on the trait: because |
| the trait knows nothing about itself being a `Device`, the type checker will complain saying that it does not find |
| the `id` property. |
| |
| One possibility is to explicitly add a `getId` method in the trait, but it would not solve all issues. What if a method |
| requires `this` as a parameter, and actually requires it to be a `Device`? |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=selftype_securityservice,indent=0] |
| ---- |
| |
| If you want to be able to call `this` in the trait, then you will explicitly need to cast `this` into a `Device`. This can |
| quickly become unreadable with explicit casts to `this` everywhere. |
| |
| [[traits-selftype]] |
| === The @SelfType annotation |
| |
| In order to make this contract explicit, and to make the type checker aware of the _type of itself_, Groovy provides |
| a `@SelfType` annotation that will: |
| |
| * let you declare the types that a class that implements this trait must inherit or implement |
| * throw a compile time error if those type constraints are not satisfied |
| |
| So in our previous example, we can fix the trait using the `@groovy.transform.SelfType` annotation: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=selftype_fixed,indent=0] |
| ---- |
| |
| Now if you try to implement this trait on a class that is *not* a device, a compile-time error will occur: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=selftype_compiletimeerror,indent=0] |
| ---- |
| |
| The error will be: |
| |
| ---- |
| class 'MyDevice' implements trait 'Communicating' but does not extend self type class 'Device' |
| ---- |
| |
| In conclusion, self types are a powerful way of declaring constraints on traits without having to declare the contract |
| directly in the trait or having to use casts everywhere, maintaining separation of concerns as tight as it should be. |
| |
| == Limitations |
| === Compatibility with AST transformations |
| |
| CAUTION: Traits are not officially compatible with AST transformations. Some of them, like `@CompileStatic` will be applied |
| on the trait itself (not on implementing classes), while others will apply on both the implementing class and the trait. |
| There is absolutely no guarantee that an AST transformation will run on a trait as it does on a regular class, so use it |
| at your own risk! |
| |
| === Prefix and postfix operations |
| |
| Within traits, prefix and postfix operations are not allowed if they update a field of the trait: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[tags=prefix_postfix,indent=0] |
| ---- |
| <1> `x` is defined within the trait, postfix increment is not allowed |
| <2> `x` is defined within the trait, prefix decrement is not allowed |
| |
| A workaround is to use the `+=` operator instead. |
| |