webtechconf - Munich, October 29th 2013 - Peter Seliger / @petsel
Frontend Engineer at XING AG
Role
, Trait
and Mixin
.[LibraryName].extends
approaches.
Object
Function
and closures ...
call
and apply
, ...
prototype
too.
prototype
slot of constructor functions.
call
or apply
.
TRAIT
/MIXIN
modules.
delegation example part I
var cat = {
sound : "meow",
makeSound : function () {
console.log(this.sound);
}
};
var dog = {
sound: "woof"
};
console.log("cat.sound", cat.sound); // "meow"
console.log("dog.sound", dog.sound); // "woof"
console.log("typeof cat.makeSound", (typeof cat.makeSound)); // "function"
console.log("typeof dog.makeSound", (typeof dog.makeSound)); // "undefined"
cat.makeSound.call(dog); // "woof"
delegation example part II
var cat = {sound: "meow"}, dog = {sound: "woof"};
var Talkative = function () {
this.makeSound = function () {
console.log(this.sound);
};
};
console.log("typeof cat.makeSound", (typeof cat.makeSound)); // "undefined"
console.log("typeof dog.makeSound", (typeof dog.makeSound)); // "undefined"
Talkative.call(cat);
Talkative.call(dog);
cat.makeSound(); // "meow"
dog.makeSound(); // "woof"
Role
s, Trait
s and Mixin
s.Role
s, Trait
s and Mixin
s.Trait
Nathanael Schärli et.al., Universität Bern, 25th November 2002
Nathanael Schärli, Universität Bern, 03.02.2005
Role
s, Trait
s and Mixin
s.SCG Trait
(very briefly)Role
s, Trait
s and Mixin
s.Role
s in »Perl 6« as well as in the »Perl 5« based
»Moose«-Framework are allowed to be stateful too.
Role
s are also supported by the
»Joose«-Framework,
a »Moose« inspired JavaScript Meta-Object System created by
Malte Ubl / @cramforce.
Mixin
s, and
Mixin
concept to »LISP«.
Role
s, Trait
s and Mixin
s.Enumerable_first_last
Allocable
and Queue
Observable_SignalsAndSlots
Allocable
and Observable
and Queue
Queue
composed by its factory
Role
s, Trait
s and Mixin
s in JS.Role
Any function object that is a container for at least one public behavior or acts as collection of more than one public behavior and is intended to neither being invoked by the call operator »()
« nor with the »new
« operator but always should be applied to objects by invoking one of the[Function]
s call methods - either[call]
or[apply]
- is considered to be a Role.
Role
s, Trait
s and Mixin
s in JS.Trait
A purely stateless implementation of a Role should be called Trait.
Role
s, Trait
s and Mixin
s in JS.Trait
var Trait = (function () {
var
behavior_01 = function () {
// implementation of behavior.
},
behavior_02 = function () {
// implementation of behavior.
}
;
var Trait = function () {
// stateless trait implementation.
var compositeType = this;
compositeType.behavior_01 = behavior_01;
compositeType.behavior_02 = behavior_02;
};
return Trait;
}());
// usage.
var obj = {
// object description.
};
Trait.call(obj); // [obj] now features additional behavior applied by [Trait].
Role
s, Trait
s and Mixin
s in JS.Trait
Enumerable_first_last
var Enumerable_first_last = (function () {
var
first = function () {
return this[0];
},
last = function () {
return this[this.length - 1];
}
;
return function () {
this.first = first;
this.last = last;
};
}());
var
allListItems = document.getElementsByTagName("li"),
allSections = document.getElementsByTagName("section")
;
Enumerable_first_last.call(allListItems);
Enumerable_first_last.call(allSections);
console.log("allListItems", [allListItems, (allListItems[0] === allListItems.first()), (allListItems[allListItems.length - 1] === allListItems.last())]);
console.log("allSections", [allSections, (allSections[0] === allSections.first()), (allSections[allSections.length - 1] === allSections.last())]);
Enumerable_first_last.call(Array.prototype);
console.log('["1st", "2nd", "3rd"].first()', ["1st", "2nd", "3rd"].first());
console.log('["first", "second", "third"].last()', ["first", "second", "third"].last());
Array.prototype.last = null;
Array.prototype.first = null;
console.log('["1st", "2nd", "3rd"].first', ["1st", "2nd", "3rd"].first);
console.log('["first", "second", "third"].last', ["first", "second", "third"].last);
Role
s, Trait
s and Mixin
s in JS.Privileged Trait
An implementation of a Role that relies on additionally injected state but does only read and never does mutate it should be called Privileged Trait.
Role
s, Trait
s and Mixin
s in JS.Privileged Trait
var PrivilegedTrait = (function () {
var
behavior_02 = function () {
// e.g. implementation of behavior.
return "behavior_02";
}
;
var PrivilegedTrait = function (injectedReadOnlyState) {
var compositeType = this;
compositeType.behavior_01 = function () {
/*
implementation of behavior is not allowed
to mutate [injectedReadOnlyState] but shall
only read it.
nevertheless if [injectedReadOnlyState] was
a reference it still could be mutable but only
remotely from outside this trait modules scope.
*/
return injectedReadOnlyState;
};
compositeType.behavior_02 = behavior_02;
};
return PrivilegedTrait;
}());
// usage.
var obj = {
// object description.
};
PrivilegedTrait.call(obj, "injectedReadOnlyState"); // [obj] now features additional behavior applied by [PrivilegedTrait].
Role
s, Trait
s and Mixin
s in JS.Privileged Trait
Allocable
var Allocable = (function () {
var makeArray = (function (proto_slice) {
return function (listType) {
return proto_slice.call(listType);
};
}(Array.prototype.slice));
return function (list) {
var allocable = this;
allocable.valueOf = allocable.toArray = function () {
return makeArray(list);
};
allocable.toString = function () {
return ("" + list);
};
allocable.size = function () {
return list.length;
};
};
}());
var Queue = function () {
var
queue = this,
list = [],
onEnqueue = function (type) {
queue.dispatchEvent({target: queue, type: "enqueue", item: type/*, even more key:value pairs */});
},
onDequeue = function (type) {
queue.dispatchEvent({target: queue, type: "dequeue", item: type/*, even more key:value pairs */});
},
onEmpty = function () {
queue.dispatchEvent({target: queue, type: "empty"/*, even more key:value pairs */});
}
;
//Observable.call(queue);
Allocable.call(queue, list);
queue.enqueue = function (type) {
list.push(type);
// onEnqueue(type);
};
queue.dequeue = function () {
var type = list.shift();
/*
if (list.length <= 0) {
onEmpty();
}
onDequeue(type);
*/
return type;
};
};
var queue = new Queue;
console.log("queue", [queue, queue.toArray(), queue.toString(), queue.size()]);
console.log('queue.enqueue("hallo")', [queue.enqueue("hallo"), queue.toArray(), queue.toString(), queue.size()]);
console.log('queue.enqueue("world")', [queue.enqueue("world"), queue.toArray(), queue.toString(), queue.size()]);
console.log('queue.dequeue()', [queue.dequeue(), queue.toArray(), queue.toString(), queue.size()]);
console.log('queue.dequeue()', [queue.dequeue(), queue.toArray(), queue.toString(), queue.size()]);
Role
s, Trait
s and Mixin
s in JS.Mixin
An implementation of a Role that does create mutable state on its own in order to solve its task(s) but does never rely on additionally injected state should be called Mixin.
Role
s, Trait
s and Mixin
s in JS.Mixin
var Mixin = (function () {
var
AdditionalState = function () {
// implementation of a custom state type [Mixin] relies on.
},
behavior_02 = function () {
// e.g. implementation of behavior.
return "behavior_02";
}
;
var Mixin = function () {
var
compositeType = this,
additionalState = new AdditionalState(compositeType) // (mutable) additional state.
;
compositeType.behavior_01 = function () {
/*
implementation of behavior is allowed
to mutate [additionalState].
*/
};
compositeType.behavior_02 = behavior_02;
};
return Mixin;
}());
// usage.
var obj = {
// object description.
};
Mixin.call(obj); // [obj] now features additional behavior applied by [Mixin].
Role
s, Trait
s and Mixin
s in JS.Mixin
Observable_SignalsAndSlots
var Observable_SignalsAndSlots = (function () {
// the »Observable« Mixin Module.
// ... implementation ...
var
Event = function (target/*:[EventTarget(observable)]*/, type/*:[string|String]*/) {
this.type = type;
this.target = target;
},
EventListener = function (target/*:[EventTarget(observable)]*/, type/*:[string|String]*/, handler/*:[Function]*/) {
var defaultEvent = new Event(target, type); // default [Event] object
this.handleEvent = function (evt/*:[string|String|Event-like-Object]*/) { /*:void*/
// ... implementation ...
};
},
EventTargetMixin = function () {
// [EventTargetMixin] will be exposed as »Observable« Mixin.
var eventMap = {};
this.addEventListener = function (type/*:[string|String]*/, handler/*:[Function]*/) { /*:[EventListener|undefined]*/
var
event = eventMap[type],
listener = new EventListener(this, type, handler)
;
// ... implementation ...
};
this.dispatchEvent = function (evt/*:[string|String|Event-like-Object]*/) { /*:[true|false]*/
// ... implementation ...
};
}
;
return EventTargetMixin;
}());
Role
s, Trait
s and Mixin
s in JS.Privileged Mixin
An implementation of a Role that relies either on mutation of additionally injected state only or on both, creation of mutable state and additionally injected state, regardless if the latter then gets mutated or not, should be called Privileged Mixin.
Role
s, Trait
s and Mixin
s in JS.Privileged Mixin
var PrivilegedMixin = (function () {
var
AdditionalState = function () {
// implementation of a custom state type [PrivilegedMixin] relies on.
},
behavior_02 = function () {
// e.g. implementation of behavior.
return "behavior_02";
}
;
var PrivilegedMixin = function (injectedState) {
var
compositeType = this,
//additionalState = new AdditionalState(compositeType) // (mutable) additional state.
additionalState = new AdditionalState(compositeType, injectedState) // (mutable) additional state.
;
compositeType.behavior_01 = function () {
/*
- implementation of behavior is allowed to mutate [additionalState].
- it is also allowed to manipulate [injectedState]
*/
};
compositeType.behavior_02 = behavior_02;
};
return PrivilegedMixin;
}());
// usage.
var obj = {
// object description.
};
PrivilegedMixin.call(obj, "injectedState"); // [obj] now features additional behavior applied by [PrivilegedMixin].
Trait
and Mixin
based Type/Object Composition in JS.Trait
and Mixin
based Type/Object Composition in JS.
var CompositeTypeFactory = (function () {
var CompositeType = function (type_configuration) {
var compositeType = this;
/*
- do implement something type specific
- do something with e.g. [type_configuration]
*/
var locallyScopedTypeSpecificReference = [];
Mixin.apply(compositeType);
PrivilegedTrait.apply(compositeType, locallyScopedTypeSpecificReference);
};
CompositeType.prototype = {
/*
- if necessary do assign and/or describe
the [CompositeType] constructor's prototype.
*/
};
/*
- purely stateless trait implementations almost always
should be exclusively applied to a constructors prototype.
*/
Trait.call(CompositeType.prototype);
var
createType = function (arg_0/*[, arg_1[, arg_2[,...]]]*/) {
return (new CompositeType({
/*
do something with [arguments] ...
... e.g. creating a type configuration.
*/
}));
},
isType = function (type) {
return (type instanceof CompositeType);
}
;
return {
create: createType,
isType: isType
};
}());
Trait
and Mixin
based Type/Object Composition in JS.
Trait
and Mixin
implementations should resolve conflicts by making use of
AOP inspired
method modifiers.
Function.prototype.before
Function.prototype.after
Function.prototype.around