Skip to content

Commit 9b27090

Browse files
committed
Merge branch 'Pharo14' of github.com:pharo-project/pharo into systemSupport
2 parents 5ae2b96 + 37b4665 commit 9b27090

163 files changed

Lines changed: 1758 additions & 2107 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/Traits/TraitDefinition.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#Traits in Pharo
2+
3+
## Trait in a Nutshell
4+
5+
Traits are kind of class fragments: they define methods and state.
6+
Classes or traits can use traits. When they do, they acquire all the trait methods and behavior.
7+
Now the composer (a class or a trait) can define to change some part of the trait it uses.
8+
With trait, definitions in the composer always take precedence over the definition defined in the used traits.
9+
10+
### Simple Example
11+
12+
#### Definition
13+
14+
Here we define a trait named `TFun`. It defines a simple method named what which returns a string.
15+
16+
```Smalltalk
17+
Trait << #TFun
18+
package: 'MyPackage'
19+
20+
TFun >> what
21+
^ 'It is fun'
22+
```
23+
24+
#### Use
25+
26+
Now we define a class named `WithFun` that uses the traits `TFun` (the message `traits:` is the message to add traits to a class).
27+
28+
```
29+
Object << #WithFun
30+
traits: { TFun };
31+
package: 'MyPackage'
32+
```
33+
34+
Then we instantiate the class and send the message `what`. The message is resolved and the expected result is return.
35+
The class WithFun has all the methods defined in the trait `TFun`.
36+
37+
```
38+
WithFun new what
39+
> 'It is fun'
40+
```
41+
42+
### Method Definition
43+
44+
You can then add a new method to the trait. As any other methods it can send message to existing methods.
45+
Here we add the method `superWhat`.
46+
47+
```
48+
TFun >> superWhat
49+
50+
^ self what , '. Really fun'
51+
```
52+
53+
We can now send this message to instances of the class `WithFun`.
54+
55+
```
56+
WithFun new superWhat
57+
> 'It is fun. Really fun'
58+
```
59+
60+
### Other users
61+
62+
The same trait can be used by multiple classes or traits.
63+
Here we defined a new class named `WithFun2` that uses the trait `TFun`.
64+
65+
```
66+
Object << #WithFun2
67+
traits: { TFun };
68+
package: 'MyPackage'
69+
```
70+
71+
And we can see that instances of this new class can execute the methods
72+
of the trait.
73+
74+
```
75+
WithFun2 new superWhat
76+
> 'It is fun. Really fun'
77+
```
78+
79+
### Overriding trait methods
80+
81+
Now image that the class `WithFun` needs to have another behavior.
82+
We can redefine locally a method. This is this locally define method that will be executed
83+
instead of the method from the trait. This only impacts the class defining the specific method.
84+
85+
The class `WithFun redefines the method `superWhat` as follows:
86+
```
87+
WithFun >> superWhat
88+
89+
^ self what , '. Really fun and powerful'
90+
```
91+
92+
Now the following expression shows that only the implementation of the class `WithFun` gets modified
93+
by the new definition.
94+
95+
```
96+
{ WithFun new . WithFun2 new} collect: #superWhat
97+
>> #('It is fun. Really fun and powerful' 'It is fun. Really fun')
98+
```
99+
100+
101+
102+
103+
### Using multiple traits
104+
105+
106+
107+
You can also use multiple Traits with your class with the `#+` message.
108+
109+
```Smalltalk
110+
MySuperClass << #MyClass
111+
traits: { TNameOfMyTrait + TNameOfMySecondTrait };
112+
package: 'MyPackage'
113+
```
114+
115+
## Abstract methods
116+
117+
We might need to call a method for which the implementation will be specific to the class using the trait. To manage this case, a Trait can hold methods that explicitely declare that user should define it. These methods contain a call to `#explicitRequirement` message.
118+
119+
```Smalltalk
120+
TMyTrait >> addButton: aButton
121+
self buttons add: aButton
122+
```
123+
124+
```Smalltalk
125+
TMyTrait >> buttons
126+
^ self explicitRequirement
127+
```
128+
129+
> Some Pharo developers create Traits with all their methods calling `#explicitRequirement` message. Doing this kind of simulate an interface (as Java's interfaces). Users of one of these traits thus declare that they support the interface it defines and override all methods defined by the trait.
130+
131+
## Stateful traits
132+
133+
Since Pharo 7, it is possible to add slots to Traits. This will make you trait a stateful trait.
134+
135+
Examples:
136+
137+
```Smalltalk
138+
Trait << #MDLWithConfigurableRightPanel
139+
slots: { #panelComponent . #toolbar };
140+
package: 'MaterialDesignLite-Extensions'
141+
```
142+
143+
```Smalltalk
144+
Trait << #FamixTWithEnumValues
145+
slots: { #enumValues => FMMany type: #FamixTEnumValue opposite: #parentEnum };
146+
package: 'Famix-Traits-EnumValue'
147+
```
148+
149+
## Traits initialization
150+
151+
Traits do not include a way to initialize classes using them, it relies on conventions.
152+
153+
One way to manage this might be to implement a method named `initializeTMyTraitName` on each traits needing an initialization and to call all those methods on the class using them.
154+
155+
In case of trait composition (See [Trait composition](#trait-composition)), a trait composed of other traits can also implement a initialize method calling the one of the Traits it includes.
156+
157+
## Customize method received from a Trait
158+
When a class uses a trait, it is possible for it to reject or alias some methods.
159+
160+
### Reject some methods received from the trait
161+
In some case it is needed to reject a method of a Trait. It can be achieved using `#-` message.
162+
163+
```Smalltalk
164+
TestCase << #StackTest
165+
traits: { TEmptyTest - {#testIfNotEmptyifEmpty. #testIfEmpty. #testNotEmpty} + (TCloneTest - {#testCopyNonEmpty}) };
166+
slots: { #empty. #nonEmpty } ;
167+
package: 'Collections-Tests-Stack'
168+
```
169+
170+
### Alias some methods received from the trait
171+
It is possible to alias some methods received from a trait. If, for example you alias `#aliasedMethod` with `#methodAlias` as shown below, your class will hold both `#methodAlias` and `#aliasedMethod`.
172+
173+
```
174+
Object << #MyObjectUsingTraitByAliasingMethod
175+
traits: { TTraitToBeUsed @ { #methodAlias -> #aliasedMethod } };
176+
package: 'TestTraitAliasing'
177+
```
178+
179+
Here is a simple example. Consider a situation when a trait `TLocated` implements a method `moveTo:` that defines the movement of an object to a given cell. The user of this trait needs to implement the post-movement operation. Usually, this would be done by overriding the `moveTo:` method and calling `super moveTo: aCell` in the first line of the new implementation. However, the super calls can not be used with traits as they install methods directly into the code of their users. The simple workaround would be to create an allias `basicMoveTo:` for the trait method and then call it from the new `moveTo:` method implemented by the user class:
180+
181+
```st
182+
TLocated >> moveTo: aCell
183+
"Define the movement"
184+
185+
Object << #Antelope
186+
traits: { TLocated @ { #basicMoveTo: -> #moveTo: } };
187+
...
188+
189+
Antelope >> moveTo: aCell
190+
self basicMoveTo: aCell.
191+
"Do some post-movement actions"
192+
```
193+
194+
## Customize instance variables received from a (stateful) Trait
195+
When a class uses a trait, it is possible for it to reject or alias some instance variables.
196+
197+
### Reject some instance variables received from the trait
198+
In some case it is needed to reject an instance variable of a Trait. It can be achieved using `#--` message. It works similarly to methods rejecting explaining in previous section.
199+
200+
```
201+
Object << #MyObjectUsingTraitByRejectingInstVar
202+
traits: {} TTraitToBeUsed asTraitComposition -- #instVarNameToRemove };
203+
package: 'TestTraitAliasing'
204+
```
205+
206+
> `#asTraitComposition` needs to sent to the trait because `#--` message is not understood by trait but by trait composition.
207+
208+
### Alias some instance variables received from the trait
209+
It is possible to alias some instance variables received from a trait. If, for example you alias `#aliasedInstVar` with `#instVarAlias` as shown below, your class will hold both `#instVarAlias` and `#aliasedInstVar`.
210+
211+
```
212+
Object << #MyObjectUsingTraitByAliasingInstVar
213+
traits: { (TTraitToBeUsed >> { #instVarAlias -> #aliasedInstVar }) } ;
214+
package: 'TestTraitAliasing'
215+
```
216+
217+
## Trait composition
218+
219+
Traits are composable, this mean that you can have Traits using other traits. It is done in the same way than class using a Trait:
220+
221+
```Smalltalk
222+
Trait << TMyComposedTrait
223+
traits: { TMyFirstTrait + TMySecondTrait} ;
224+
package: 'MyPackage'
225+
```
226+
227+
Example:
228+
229+
```Smalltalk
230+
Trait << #EpTEventVisitor
231+
traits: { EpTCodeChangeVisitor };
232+
package: 'Epicea-Visitors'
233+
```
234+
## Conflicts
235+
236+
Two kinds of *conflicts* can happen with methods implemented on Traits.
237+
238+
1. A method is present on a used Trait, but the class using this Trait also implements this method. In that case, the method lookup will select the method from the class. It is an equivalent of an override of method.
239+
240+
2. Two traits implementing the same method are used. In that case, if the method is called it will raise an error `traitConflict`.
241+
242+
A way to solve both cases is to use method aliasing and to remove the conflicting method:
243+
```Smalltalk
244+
Object << #MyObjectUsingTraitByAliasingMethod
245+
traits: { TTraitToBeUsed @ { #methodAlias -> #conflictingMethod } - { #conflictingMethod } };
246+
package: 'TestTraitAliasing'
247+
```
248+
249+
Another way to solve case 2. is to implement a method on the class using the trait in order to chose the behavior wanted.

0 commit comments

Comments
 (0)