Logo-small

3.6 Traits in 10 minutes

In this chapter we will show you how to define a trait and apply it to a class. Remember traits are just groups of methods that can be reused in different classes.

Scenario: Announcer/Subscriber

Imagine that you want to define a trait to represent an announcing behavior: the fact that an object can register interest to another one and be notified when an announcement is fired.

For example, imagine that we have a BulletinBoard that can announce event such as AddedItem and a BoardListener that can listen and react to event.

BoardListener>>listenAndAdd
"self new listenAndReact"

| obj |
obj := BulletinBoard new.
obj on: AddedItem do: [:an | Transcript show: 'Added Item' ; cr].
^ obj

Each time the board add an item it will raise an event as follow:

BulletinBoard>>announceNewAddedItem
self announce: (AddedItem new)

Now

BoardListener new listenAndReact

makes sure that each time the method announceNewAddedItem is executed on the created bulletinBoard the event is received by the listener.

Open an inspector on the expression above and send the message and you will see the trace in the Transcript. Now how can we take benefit of Trait to reuse Announcer behavior amount classes? First we define a Trait TAnnouncer, second we use it to define the class BulletinBoard.

Getting the right tool to edit traits

To code with trait make sure that you loaded the O2 Browser since it supports traits programming.

	Gofer new
squeaksource: 'MetacelloRepository';
package: 'ConfigurationOfO2';
load

Then execute

	ConfigurationOfO2 loadDefault

Open it, create a package, create a dummy class (yes there is a bug you cannot access the class/trait template creation method otherwise), select the template. You can also directly type the trait definition

Trait named: #TAnnouncer
uses: {}
category: 'BulletinBoard'

Defining the Trait TAnnouncer

Now we will just define some methods which are nearly copy and paste from Announcer for the sake of the example. Notice that we convert direct access into accessors because (right now) traits cannot define state.

TAnnouncer>>announce: anAnnouncement
| announcement |
announcement := anAnnouncement asAnnouncement.
self subscriptions ifNil: [ ^ announcement ].
self subscriptions keysAndValuesDo: [ :class :actions|
(class handles: announcement)
ifTrue: [ actions valueWithArguments: (Array with: announcement) ] ].
^ announcement
TAnnouncer>>on: anAnnouncementClass do: aValuable
^ self subscribe: anAnnouncementClass do: aValuable

Now the trait is ready to be used.

Applying the trait

We define the class BulletinBoard as follows:

Object subclass: #BulletinBoard
uses: TAnnouncer
instanceVariableNames: 'subscriptions'
classVariableNames: ''
poolDictionaries: ''
category: 'BulletinBoard'

Automatically the class BulletinBoard gets the methods defined in the trait: announce: and on:do:. If you add a method to the trait the class TAnnouncer will get it too.

Now you have to define two accessor for the

BulletinBoard>>subscriptions
^ subscriptions
BulletinBoard>>subscriptions: aDictionary
subscriptions := aDictionary

Now you can define the AddedItem announcement

Announcement subclass: #AddedItem
instanceVariableNames: 'topic timestamp'
classVariableNames: ''
poolDictionaries: ''
category: 'BulletinBoard'

and define the method

BulletinBoard>>announceNewAddedItem
self announce: (AddedItem new)

You are done.

Going deeper

When applying a trait to a class it may face different situations.

Excluding a method

First it may happen that you do not want all the methods from the trait. For example to remove from the class BulletinBoard the hypothetical method methodIDoNotWant coming from the trait TAnnouncer. You can simply exclude it as follows:

Object subclass: #BulletinBoard
uses: TAnnouncer - {#methodIDoNotWant}
instanceVariableNames: 'subscriptions'
classVariableNames: ''
poolDictionaries: ''
category: 'BulletinBoard'

Not that another class using the trait TAnnouncer will not get impacted by this definition. Only the class BulletinBoard gets impacted. It decides during its definition the methods that it wants to exclude.

Method defined in a class hide a trait method

You may have a method in the class that has the same name that a method in one of the traits used by the class. Methods defined in the class always takes precedence over the methods defined in a trait. The trait composer (you when you define a class) always has the control of the composition. You can decide what methods end up in the class.

How to deal with conflicts

If you use more than one trait you may have the same method defined in two different traits. In such a case the system identifies this as a conflict. To solve a conflict, you have two choices: either you exclude the method from one trait and in such a case then the other method is one that will get used in the class, or you redefine locally the method in the class. Remember that methods defined in the class take precedence over trait methods.

Using Alias to access hidden method

You may want to invoke a method defined in the traits. This is really simple. Just invoke it as a method defined in the class. Remember that trait methods are just methods that are added to the class and that we can potentially remove the trait and copy all its methods in the class to get the same result. For example the method myOn:do: can invoke the method on:do: as follow:

Bulletin>>myOn: anAnnouncementClass do: aValuable
Transcript show: 'my on: do:'; cr.
^ self on: anAnnouncementClass do: aValuable

Now the question is how can we invoke a method when we already redefined the method in the class. Imagine that we want to add trace to the method on:do:, we can define it in the class as follow:

Bulletin>>on: anAnnouncementClass do: aValuable
Transcript show: 'my on: do:'; cr.
^ self on: anAnnouncementClass do: aValuable

But this definition clearly loops. The solution is to use an alias: i.e., to give a new name to the method coming from the trait and use this new name. To do that we use the expression TAnnouncer @ {#announcerOn:do:->#on:do:}. It means that the method on:do: from the trait TAnnouncer now can be invoked using the selector announcerOn:do:.

Object subclass: #BulletinBoard
uses: TAnnouncer @ {#announcerOn:do:->#on:do:}
instanceVariableNames: 'subscriptions'
classVariableNames: ''
poolDictionaries: ''
category: 'BulletinBoard'

Once you recompile the class, a new method announcerOn:do: is available in the class and you can define the method on:do: as follow:

BullettinBoard>>on: anAnnouncementClass do: aValuable
Transcript show: 'local on: do:'; cr.
^ self announcerOn: anAnnouncementClass do: aValuable

Trait Composition Explained

Now that you know how to define a trait and reuse it. You may wonder what happens with superclass and methods defined in the superclass. This is really simple. Really simple. Traits application does not change inheritance nor self nor super. You should consider that a trait can disappear in the class that use it. So a class using a trait is the same thing that a class having the methods defined in the trait.

be continued@@ ! Discussions Having a trait TAnnouncer may be not the best solution and may be just invoking a new instance of the class Announcer is enough. Now traits are powerful when you do not have to subclass from a given root superclass but want to get the defined behavior. For example, in SandStoneDB your domain objects should inherit from SBActiveRecord or something like that. With trait, you can turn this class into a trait and after apply it to your domain object without having to change your superclass. In the future we want to have state in traits. We just define a trait like that

User Contributed Notes

lippens.m (17 December 2012, 6:10 am)

as daliot.oh suggests, the most interesting part is missing: multiple traits and collisions

holger (20 August 2012, 1:36 am)

"The trait composer (you when you define a class)"

 

there is something missing between "you" and "when".

daliot.oh (10 April 2011, 5:57 am)

Using ore than one traits case is missing.

Add a Note

Licensed under Creative Commons BY-NC-SA | Published using Pier |