Design study to show the differences between decorator mixin classes and Ruby’s mixin modules
-This example shows that the dynamic class structure of NX (and -XOTcl) is able to support Ruby style mixins (called modules) and -decorator style mixins (named after the design pattern Decorator) -in the same script.
nx::test configure -count 1
One important difference between mixin classes in NX and Ruby’s -mixins is the precedence order. While in NX, mixins are decorators -(the mixins have higher precedence than the intrinsic classes, -therefore a mixin class can overload the methods of the current -class and its subclasses), the mixins of Ruby have a lower -precedence (they extend the base behavior; although Ruby’s modules -are not full classes, they are folded into the intrinsic class -hierarchy). Therefore, a Ruby style mixin can be refined by the -class, into which it is mixed in (or by a subclass). Decorator style -mixins modify the behavior of a full intrinsic class tree, while -Ruby style mixins are compositional units for a single class.
To show the differences, we define the method module
, which
-behaves somewhat similar to Ruby’s module
command. This method
-adds the provided class to the precedence order after the current
-class. The easiest way to achieve this is via multiple inheritance
-(i.e. via the superclass
relationship).
package req nx - -nx::Class eval { - :protected method module {name:class} { - nsf::relation [self] superclass [concat $name [:info superclass]] - } -}
For illustration of the behavior of module
we define a class
-Enumerable
somewhat inspired by the Ruby module with the same
-name. We define here just the methods map
, each
, count
, and
-count_if
.
nx::Class create Enumerable { - :property members:0..n - - # The method 'each' applies the provided block on every element of - # 'members' - :public method each {var block} { - foreach member ${:members} { - uplevel [list set $var $member] - uplevel $block - } - } - - # The method 'map' applies the provided block on every element of - # 'members' and returns a list, where every element is the mapped - # result of the source. - :public method map {var block} { - set result [list] - :each $var { - uplevel [list set $var [set $var]] - lappend result [uplevel $block] - } - return $result - } - - # The method 'count' returns the number of elements. - :public method count {} { - return [llength ${:members}] - } - - # The method 'count_if' returns the number of elements for which - # the provided expression is true. - :public method count_if {var expr} { - set result 0 - :each $var { - incr result [expr $expr] - } - return $result - } -}
After having defined the class Enumerable
, we define a class
-Group
using Enumerable
as a Ruby style mixin. This makes
-essentially Group
a subclass of Enumerable
, but with the only
-difference that Group
might have other superclasses as well.
nx::Class create Group { - # - # Include the "module" Enumerable - # - :module Enumerable -}
Define now a group g1
with the three provided members.
% Group create g1 -members {mini trix trax}
-::g1
Since the definition of Group
includes the module Enumerable
,
-this class is listed in the precedence order after the class Group
:
% g1 info precedence
-::Group ::Enumerable ::nx::Object
Certainly, we can call the methods of Enumerable
as usual:
% g1 count -3 - -% g1 map x {list pre-$x-post} -pre-mini-post pre-trix-post pre-trax-post - -% g1 count_if x {[string match tr*x $x] > 0} -2
To show the difference between a module
and a decorator mixin
we
-define a class named Mix
with the single method count
, which
-wraps the result of the underlaying count
method between the
-alpha
and omega
.
nx::Class create Mix { - :public method count {} { - return [list alpha [next] omega] - } -}
When the mixin class is added to g1
, it is added to the front of
-the precedence list. A decorator is able to modify the behavior of
-all of the methods of the class, where it is mixed into.
% g1 object mixin Mix -::Mix - -% g1 info precedence -::Mix ::Group ::Enumerable ::nx::Object - -% g1 count -alpha 3 omega
For the time being, remove the mixin class again.
% g1 object mixin "" -% g1 info precedence -::Group ::Enumerable ::nx::Object
An important difference between NX/XOTcl style mixins (decorators)
-and Ruby style modules is that the decorator will have always a
-higher precedence than the intrinsic classes, while the module
is
-folded into the precedence path.
Define a class ATeam
that uses Enumerable
in the style of a Ruby
-module. The class might refine some of the provided methods. We
-refined the method each
, which is used as well by the other
-methods. In general, by defining each
one can define very
-different kind of enumerators (for lists, databases, etc.).
Since Enumerable
is a module, the definition of each
in the
-class ATeam
has a higher precedence than the definition in the
-class Enumerable
. If Enumerable
would be a decorator style mixin
-class, it would not e possible to refine the definition in the class
-ATeam
, but maybe via another mixin class.
nx::Class create ATeam { - # - # Include the "module" Enumerable - # - :module Enumerable - - # - # Overload "each" - # - :public method each {var block} { - foreach member ${:members} { - uplevel [list set $var $member-[string length $member]] - uplevel $block - } - } - - # - # Use "map", which uses the "each" method defined in this class. - # - :public method foo {} { - return [:map x {string totitle $x}] - } -}
Define now a team t1
with the three provided members.
% ATeam create t1 -members {arthur bill chuck}
-::t1
As above, the precedence of ATeam
is higher than the precedence of
-Enumerable
. Therefore, the object t1
uses the method each
specialized in
-class ATeam
:
% t1 info precedence
-::ATeam ::Enumerable ::nx::Object
-
-% t1 foo
-Arthur-6 Bill-4 Chuck-5
The class ATeam
can be specialized further by a class SpecialForce
:
nx::Class create SpecialForce -superclass ATeam { - # ... -}
Define a special force s1
with the four provided members.
% SpecialForce create s1 -members {Donald Micky Daniel Gustav}
-::s1
As above, the precedence of Enumerable
is lower then the
-precedence of ATeam
and Enumerable
. Therefore ATeam
can refine
-the behavior of Enumerable
, the class SpecialForce
can refine
-the behavior of ATeam
.
% s1 info precedence
-::SpecialForce ::ATeam ::Enumerable ::nx::Object
-
-% s1 foo
-Donald-6 Micky-5 Daniel-6 Gustav-6
Let us look again on decorator style mixin classes. If we add a
-per-class mixin to ATeam
, the mixin class has highest precedence,
-and decorates the instances of ATeam
as well the instances of its
-specializations (like e.g. SpecialForce
).
% ATeam mixin Mix -::Mix - -% s1 info precedence -::Mix ::SpecialForce ::ATeam ::Enumerable ::nx::Object - -% s1 count -alpha 4 omega
This example showed that NX/XOTcl dynamic class structure is able to -support Ruby-style mixins, and decorator style mixins in the same script.