“traits: composable units of behavior” n. schaerli, s. ducasse, o. nierstrasz, and a. black, u...
Post on 19-Dec-2015
214 views
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:
~=
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