Traits offer an alternative solution to inheritance that is particularly suited to class-less languages like Javascript. With this approach, the aspects of instance generation and units of reuse are clearly separated while providing methods of composition that are functionally similar to multiple inheritance. Troublesome multiple inheritance issues like the diamond problems are avoided by keeping traits stateless. Any state must be stored in the instance that inherits from the trait.
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Traits composition
1. When “is a” and “has a”
wouldn’t do!
Traits composition
2. Who?
Marielle Lange @widged
Flex developer for Academic
a living (since background (last at
2008) “The Institute for
Adaptive and
Javascript & jQuery
Neural
3. Traits?
• Clearly separates issues of instance generation
and units of reuse
• Composition mechanisms
• Divorced from the inheritance hierarchy ➡
Avoid difficulties experienced with multiple
inheritance.
• Impose no composition order ➡ Avoid
difficulties experienced with mixins.
• Introduced in SmallTalk. Particularly suited to
class-less languages like Javascript. Also found in
class-based ones. Recently added to PHP.
Supported in Scala (Java)
6. Object Oriented
An 'Objects and classes' approach
allows for the design of components
that are fully re-usable across
projects and sometimes across
programming frameworks.
7. Objects
Reference to objects that are meant to
mimic real world behaviour.
• Intuitive and consistent abstractions of
the problem being solved throughout
the analysis, design, implementation,
and maintenance phases of
development.
• Provision for testing and the ability to
embrace change.
8. Classes (Templates for objects)
• Conflicting purposes
• As generators of objects, classes must be
complete and monolithic.
• As units of reuse via inheritance, they
should be small, fine-grained, and possibly
incomplete.
James Gosling, creator of Java, once said
that if he could do Java over again, he
9. Fragile inheritance chains
Is a Bird
lacks flying
Has wings code
➡ flightless duplication
bird
is a FLBTCS?
➡ flightless has a swim
manager?
Has buoyant bird,
body implements
swimmer
11. Traits composition
Bird BirdMan
can swim Bird can swim
(swim trait) can fly
can swim
can fly
Traits are coherent collections of methods
that can be reused anywhere in the
12. Re-use, no
• Halfway between an interface and a class.
• Like interfaces, they define behaviors that
the objects can implement. Unlike
interfaces, the definition includes the actual
implementation.
• Like classes, they provide method
implementations that can be acquired by
objects. However, they are stateless (they
don’t include instance variables), not bound to
a hierarchy (inheritance chain) and do not have
to be complete (they do not have to define all
13. Languages limitations
• Straightforward to implement in languages
like javascript that treat functions as first
class objects.
• var a = function() {} ➡ Functions can be
passed as arguments to other functions,
returning them as the values from other
functions, and assigning them to variables
or storing them in data structures.
• Require language modifications or
preprocessing solutions for languages that
15. Set of methods
• A trait includes:
• Only methods
• No state (no instance variable)
16. Example
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class Greeter {
use HelloWorld;
}
$o = new Greeter();
$o->sayHello(); // =>'Hello World!'
?>
Adapted from: http://php.net/manual/en/language.oop5.traits.php
17. Composition Rules
1. When a class uses a trait, the
behaviour provided by the trait gets
incorporated (flattened) into the
class. This means that the
semantics is basically the same as if
the services (methods) provided by
the trait were implemented in the
class itself.
18. Example
trait HelloWorld {
public function sayHello() { Different
from a
echo 'Hello World!';
}
}
class UniverseGreeter {
delegate in
public function sayHelloUniverse() {
echo 'Hello Universe!'; the decorator
}
} pattern
class Greeter {
use HelloWorld;
$universeDelegate = new UniverseGreeter();
public function sayHelloUniverse() {
echo $universeDelegate->sayHello();
}
}
$o = new Greeter();
$o->sayHello(); // =>'Hello World!'
$o->sayHelloUniverse(); // =>'Hello Universe!'
Adapted from: http://php.net/manual/en/language.oop5.traits.php
19. Composition Rules (ctnd)
2. Methods defined in a class take
precedence over methods provided
by a trait (any trait method can be
overridden by a method with the
same name).
20. Example Class
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello(); // Hello Universe!
?>
source: http://php.net/manual/en/language.oop5.traits.php
21. Composition Rules (ctnd)
3. Composition order is irrelevant. All
the traits have the same
precedence, and hence conflicting
trait methods must be explicitly
disambiguated.
22. Example Conflict
<?php
trait A { class Talker {
public function smallTalk() use A, B {
{ B::smallTalk insteadof A;
echo 'a'; A::bigTalk insteadof B;
} }
public function bigTalk() { }
echo 'A';
} class Aliased_Talker {
} use A, B {
B::smallTalk insteadof A;
trait B { A::bigTalk insteadof B;
public function smallTalk() B::bigTalk as talk;
{ }
}
echo 'b'; The composer has full
}
public function bigTalk() { control over the
?>
echo 'B';
} composition
}
Sum, override, exclusion,
aliasing.
Source: http://php.net/manual/en/language.oop5.traits.php
23. Incompleteness
• Traits don’t have to be complete
• Traits can require methods used by,
but not implemented in, a trait.
• It is the class responsibility to provide
an implementation for every service
required by any of the used traits
(glue code in the class itself, in a
direct or indirect super-class, or in
another trait that is used by the class).
24. Example Requires
<?php
Traits can
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
access state
}
indirectly,
class MyHelloWorld {
private $world; through
use Hello;
public function getWorld() {
return $this->world;
required
}
public function setWorld($val) {
accessor
$this->world = $val;
} services.
}
?>
source: http://php.net/manual/en/language.oop5.traits.php
25. Composite traits
• Traits cannot define a superclass.
• However, in some languages, it is
possible to define composite traits.
That is, traits that are composed of
other traits.
27. Language support
Supported in Smalltalk, PHP, Scala
(Java), C#, JavaScript
28. Objects with traits
• Intuitive and consistent ✓
abstractions of the problem
being solved throughout the
analysis, design,
implementation, and
maintenance phases of
development. ✓
• Provision for testing and the
29. Duplication of code
• In languages that don’t treat functions as first
class objects, traits are typically flattened into
a class by compiler assisted copy and paste.
• Duplication of code to avoid runtime
overhead.
• In essence, stateless traits are incomplete. They
necessarily encode dependency on state in
terms of required accessors methods (getters,
setters) that the composing class must define ➡
Each client class has to implement boilerplate
glue code.
30. Tight coupling to
• A trait solution tightly couples the trait
implementation to the using class.
This can actually reduce the
reusability and utility of the class
itself.
• These are problems that we normally
use design patterns to solve (such as
Decorator, Composite and Bridge). ➡
Encourages a quick solution were
refactoring should be considered.
(source: Traits are the new Eval)
31. Fragile to change
• Fragile to incremental or unanticipated
change
• Changing the signature of a trait method
will impact all the clients that use the
method.
• Adding or deleting methods provided by a
trait may well impact clients by introducing
new conflicts or requirements.
• Adding new state requirement in a trait will
demand new accessors in all client classes.
(source: Stateful traits, PDF)
32. Accessors break encapsulation
• Traits tend to unnecessarily expose information.
• Traits methods used in client-classes must be
public.
• A client class using a trait get to see all
methods, and not just the ones that are truly its
responsibility to implement.
• The glue code in the class, must be public. If
traits require accessors to access state (i.e.,
instance variables), then classes using these
traits must provide public accessors to the
missing state.
33. Initialization
• Since traits cannot contain state,
variables cannot be declared directly
in the trait. Accessors for that variable
are made required methods instead.
• Where should variables be initialized?
• In the class using the trait?
• In the trait and pushed into the
class via a required setter?
34. Trait modification
• In dynamical languages, like
JavaScript, potential for the trait to be
modified after it has been imported.
Core.clone = function(obj) {
If you compose a
return ns.Core.extend({}, obj);
}
trait into your class,
Core.extend = function(obj, added) {
for (key in added) {
then add a method
if (added.hasOwnProperty(key)) {
obj[key] = added[key];
to the trait at run
}
}
time, the method will
return obj;
}
not be available to
your class.
36. Horizontal
• Representations of single
inheritance class hierarchies are
working well in IDEs.
• Traits offer a new orthogonal
relation between classes and traits.
• The addition of breadth makes it
harder to represent and
understand.
(source: Traits are the new Eval)
37. Composition Explorer
• Keeping track of the dependencies
between traits and classes.
• How the responsibilities of a class are
decomposed into several traits and how
these traits are glued together in order
to achieve the required behaviour.
• How the class meets the requirements
of its component traits: the provided
and required methods, the overridden
methods, and the glue methods.
(source: Traits: Composable Units of Behaviour*, PDF)
38. Edit time warnings
• If a modification causes a new
conflict or an unspecified
requirement anywhere in the
system, the affected classes and
traits are automatically added to a
“to do” list.
• The tools should announce a
conflict if a method of the same
name and signature is obtained
(source: Traits: Composable Units of Behaviour*, PDF)
39. Glue code generation
• Generation of required methods that
correspond to instance variable accessors.
• preferably avoiding actual duplication.
• Semi-automated solutions to conflict
resolution.
• List of alternative implementations
• Choosing one generates the
composition clause that excludes the
others, and thus eliminates the conflict.
(source: “ Traits: Composable Units of Behaviour* “ and “Stateful Traits “)
40. Debugging
• Traits should be represented in the
debugger, so that a programmer
can easily map code being executed
to the actual source code (written
with traits).
(source: Adding Traits to (Statically Typed) Languages, PDF)
41. Runtime reflection
• Many programming languages allow to
reflect and sometimes even manipulate
the program being executed.
• It is important that traits are correctly
represented in the reflective
infrastructure of the language, so that
one can for example ask which
methods are provided by a certain trait
or which traits are used by a certain
class.
(source: Adding Traits to (Statically Typed) Languages, PDF)
43. “can do”
• Thinking in terms of what an
instance “can do”, in addition to the
familiar “is a”and “has a” modes of
composition.
• Keeping in mind that you don’t
necessarily have to encapsulate
variables and methods in a class.
44. Functional Pg
Look at Function.prototype.partial = function(){
var fn = this,
Partials args = Array.prototype.slice.call(arguments);
return function(){
var arg = 0;
for ( var i = 0; i < args.length &&
arg < arguments.length; i++ )
if ( args[i] === undefined )
args[i] = arguments[arg++];
return fn.apply(this, args);
};
};
String.prototype.csv2 =
String.prototype.split.partial(/,s*/);
("John, Resig, Boston").csv2() => ["Resig"]