“traits: composable units of behavior” n. schaerli, s. ducasse, o. nierstrasz, and a. black, u...

38
“Traits: Composable Units of Behavior” N. Schaerli, S. Ducasse, O. Nierstrasz, and A. Black, U Bern / OHSU, ECOOP 2003 “Traits: A Mechanism for Fine-grained Reuse” S. Ducasse, O. Nierstrasz, N. Schaerli, R. Wuyts, and A. Black, ACM TOPLAS (forthcoming) Presented by Evgeniy Gabrilovich Traits Traits are primitive units of code reuse that consist only of methods (i.e., no state)

Post on 19-Dec-2015

214 views

Category:

Documents


0 download

TRANSCRIPT

• “Traits: Composable Units of Behavior”N. Schaerli, S. Ducasse, O. Nierstrasz, andA. Black, U Bern / OHSU, ECOOP 2003

• “Traits: A Mechanism for Fine-grained Reuse”S. Ducasse, O. Nierstrasz, N. Schaerli, R. Wuyts, and A. Black, ACM TOPLAS (forthcoming)

Presented by Evgeniy Gabrilovich

Traits

Traits are primitive units of code reuse that consist only of

methods(i.e., no state)

The quest for better reuse mechanisms

• Common forms of inheritance– Single / multiple / mixin inheritance

• Limitations of the different forms of inheritance• Traits, traits, traits …

– Introduction to traits– Usage examples– Discussion of trait properties– Implementation

• Smalltalk-80, Scala, C#/Rotor, Perl 6 (“roles”)

Key observation about reuse

• A class is not necessarily an appropriate element of reuse

• Two contradictory roles of classes– Generator of instances

A class must be completeEach class should have a unique place in the

class hierarchy

– Unit of reuseA class should be smallUnits of reuse should be applicable at numerous

arbitrary places

Single inheritance• Not expressive enough for factoring out

common features shared by classes in a complex hierarchy– Code duplication

• Smalltalk example

ReadStream WriteStream

ReadWriteStream

?

ReadStream WriteStream

ReadWriteStream

Code duplication

PositionableStream

read + write

functions

“Multiple inheritance is good, but there is no good way to do it” (Steve Cook, OOPSLA’87)

Problem #1: Conflicting features that are

inherited along different paths

– Conflicting methods• Can be resolved by overriding

– Conflicting state variables

A

B

D

C

Problem #2: Limited compositional power

A

readwrite:

B

readwrite:

A

readwrite:

SyncA

readwrite:acquireLockreleaseLock

read self acquireLock res := super read self releaseLock ^ res

write: val self acquireLock super write: val self releaseLock

SyncReadWrite

readwrite:acquireLockreleaseLock

SyncA SyncB

Multiple inheritance example in C++: base class with abstract methods

class SyncReadWrite { public: virtual int read() { acquireLock(); result = directRead(); releaseLock(); return result; } virtual void write(int n) { acquireLock(); directWrite(n); releaseLock(); }

protected: virtual void acquireLock() { // acquire lock } virtual void releaseLock() { // release lock }

virtual int directRead() = 0; virtual void directWrite(int n) = 0;

}; // end of class SyncReadWrite

Multiple inheritance example in C++: code duplication in child classes

class SyncA : public A, SyncReadWrite { public: virtual int read() { return SyncReadWrite::read(); } virtual void write(int n) { SyncReadWrite::write(n); }protected: virtual int directRead() { return A::read(); } virtual void directWrite(int n) { A::write(n); }}; // end of class SyncA

class SyncB : public B, SyncReadWrite { public: virtual int read() { return SyncReadWrite::read(); } virtual void write(int n) { SyncReadWrite::write(n); }protected: virtual int directRead() { return B::read(); } virtual void directWrite(int n) { B::write(n); }}; // end of class SyncB

Code duplication

Let’s mix it!

• A mixin is an (abstract subclass) specification– may be applied to various classes to extend them with

the same set of features

• Key idea: create components designed for reuse rather than for instantiation

• Implementation– C++ – using templates– Java – using (multiple inheritance of) interfaces– Smalltalk – language extension using a mixin

invocation operator

Mixin example

Rectangle

asString

asString

^ super asString, ‘ ‘, self color asString

Rectangle + MColor

asStringserializeOn:

MColor

asString

serializeOn:

Rectangle + MColor + MBorder

asStringserializeOn:

MyRectangle

asString

^ super asString, ‘ ‘, self borderWidth asString

MBorder

asString

serializeOn:

Mixins are not a panacea

Problems? You bet!– It is impossible to control how individual

features are composed– Composition of mixins is messy

• Mixins applied later in the chain (silently) override identically named features of earlier mixins

• Reestablishing the original behavior requires changes in other mixins

– Modifying a mixin used in several places in a class hierarchy may be particularly unpleasant

Mixins in C++: no multiple inheritance

template <class Super>class MSyncReadWrite : public Super {public: virtual int read() { acquireLock(); result = Super::read(); releaseLock(); return result; } virtual void write(int n) { acquireLock(); Super::write(n); releaseLock(); }

protected: virtual void acquireLock() { // acquire lock } virtual void releaseLock() { // release lock }}; // end of MSyncReadWrite

class SyncA : public MSyncReadWrite<A> {};class SyncB : public MSyncReadWrite<B> {};

Introduction to traits

• Lightweight software components– Basic building blocks for classes– Primitive units of code reuse

• A trait provides methods that implement behavior

• A trait requires methods that parameterize the provided behavior (optional)– Required methods are used by – not

implemented in – a trait

Sample traits

TCircle

=hash<<=…areaboundscircumferencescaleBy:…

centercenter:radiusradius:

TDrawing

drawrefreshrefreshOn:

boundsdrawOn:

Geometry-related behavior Drawing-related behavior

Sample trait implementation in (an extension of) Smalltalk

Trait named: #TDrawing uses: {}

draw ^ self drawOn: World canvas

refresh ^ self refreshOn: World canvas

refreshOn: …

bounds self requirement

drawOn: aCanvas self requirement

Trait composition

• Classes are composed from traits– Provide required methods– Resolved conflicts due to trait composition

• Traits can be nested– The semantics for the containing class is not

nested– Traits are inlined

• Nested traits are equivalent to flattened traits

Addressing known problems

• Traits specify no state– Methods provided by traits never access state

variable directly• Only using required methods

– Can only cause method conflicts• Can be resolved by overriding or by exclusion

• Traits are detached from the inheritance hierarchy

• Traits are not composed in any particular order

Class = Superclass +State + Traits + Glue methods

TCircle

=hash<<=…areaboundscircumferencescaleBy:…

centercenter:radiusradius:

TDrawing

drawrefreshrefreshOn:

boundsdrawOn:

Circle

initializedrawOn:centercenter:radiusradius:

Sample class using traitsObject subclass: #Circle instanceVariableNames: ‘center radius’ traits: {TCircle . TDrawing}

initialize center := 0@0. radius := 50 center ^ center radius ^ radius

drawOn: aCanvas …

center: aPoint

center := aPoint

radius: aNumber

radius := aNumber

Completing a class definition

• In order for a class to be complete, all the requirements of the traits must be satisfied

• These methods can be implemented– in the class itself,– in a direct or indirect superclass,– by another trait that is used by the class.

The flattening property

The semantics of a method defined in a trait is identical to the semantics of the same method defined in a class that uses the trait. – The keyword super has no special semantics

for traits– super simply refers to the superclass of the

class that uses the trait.

Nested traits

TCircle

<=hash

TGeometry

areaboundsscaleBy:…

centercenter:radiusradius:

TMagnitude

<=>between:and:

<

TEquality

~= =hash

Flattened nested traits

TCircle

<=hash

areaboundsscaleBy:…

centercenter:radiusradius:

<=>between:and:

~=

More nested traits

TEquality

~= =hash

TColorredgreensaturationred:…=hash

rgbrgb:

Lack of composition order

• The order of trait composition is irrelevant

Conflicting trait methods must be explicitly disambiguated

• Precedence rules for conflict resolution– Class methods take precedence over trait

methods (= overriding trait methods)– Traits methods take precedence over

superclass methods (flattening property!)

Method conflicts

TCircle

=hash~=…areaboundscircumferencescaleBy:…

centercenter:radiusradius:

TDrawing

drawrefreshrefreshOn:

boundsdrawOn:

Circleinitialize=hashrgbrgb:drawOn:centercenter:radiusradius:

TColor

=hash~=red…

rgbrgb:

TEquality

~= =hash

traits: { TCircle @ { #circleHash -> #hash. #circleEqual: -> #= } }

Conflict resolution

• Conflicts must be explicitly resolved – Conflicting methods are excluded, and turned

into required ones

• Possible ways to resolve a conflict:– Define an overriding method in the class or in

the composite trait– Rename conflicting methods (aliasing)– Avoid a conflict by explicitly excluding some

methodstraits: { TCircle . TDrawing . TColor - { #=. #hash. #~=. } }

C++ RevisitedDocument

MSyncReadWrite<MLogOpenClose<Document> >

MLogOpenClose<Document>

MyDocument

Document

TLogOpenClose<Document>

MyDocument

TSyncReadWrite<Document>

virtual

Traits in C++template <class Super>class TLogOpenClose : virtual public Super {public: virtual void open() { … } virtual void close() { … } virtual void reset() { … }

protected: virtual void log(const char* s) { … }}; // end of TLogOpenClose

template <class Super>class TSyncReadWrite : virtual public Super {public: virtual void read() { … } virtual void write(int n) { … } virtual void reset() { … }

protected: virtual void acquireLock() { … } virtual void releaseLock() { … }}; // end of TSyncReadWrite

class MyDocument : public TLogOpenClose<Document>, public TSyncReadWrite<Document> { … // glue code};

Traits in C++: what we can and cannot do

• Nested traitstemplate <class Super>

class TLogAndSync

: virtual public TLogOpenClose<Super>,

virtual public TSyncReadWrite<Super> { };

• Composition of traits– Sum … OK– Aliasing … can be simulated using scope operator ::– Exclusion … nope

• Every conflict must be resolved by overriding the conflicting methods

Review of trait properties• Untangling reusability and classes

– Classes are instance generators and hence have to be complete

– Units of reuse should be small

• Trait composition operates at a finer granularity than inheritance– Single inheritance is retained– Flattening property

• Conflicting features– Methods only, no state– If the same method from the same trait is obtained via

multiple paths, there is no conflict

Evaluation against identified problems: code duplication

Stream

positionposition:

WriteStream

nextPut:position:

ReadStream

next

ReadWriteStream

next

Code duplication

Stream hierarchy refactored using traits

Stream

positionposition:

WriteStreamReadStream ReadWriteStream

TReadStream TReadStreamTWriteStream

TWriteStream

Evaluation against identified problems: duplicated wrappers

Like mixins, traits can explicitly refer to a method implemented by the superclass

B

readwrite:

A

readwrite:

SyncReadWrite

readwrite:acquireLockreleaseLock

SyncA SyncB

TSyncReadWrite

readwrite:acquireLockreleaseLock

readwrite:

A

readwrite:

SyncA

Mixin example (revisited)

Rectangle

asString

asString

^ super asString, ‘ ‘, self color asString

Rectangle + MColor

asStringserializeOn:

MColor

asString

serializeOn:

Rectangle + MColor + MBorder

asStringserializeOn:

MyRectangle

asString

^ super asString, ‘ ‘, self borderWidth asString

MBorder

asString

serializeOn:

Evaluation against identified problems: composition

• Total ordering– Trait composition does not impose total

ordering

• Dispersal of glue code– Glue code is always located in the combining

entity• The superordinate entity is in complete charge of

plugging together the components that implement its features

– Glue code is separated from the implementation of features

Traits offer more control

Rectangle

asString

MyRectangle

TColor

asStringserializeOn:

TBorder

asStringserializeOn:

Glue code

Evaluation against identified problems: fragile hierarchies

• Every method conflict must be resolved explicitly– This requires extra work …

• but you always get what you expect

• Changes mandated by conflict resolution are limited to the direct users of the trait– A direct client can simply exclude a new trait method

• Not so with mixins– introducing a new method may require changes to

many other components or addition of new components