Design study to show differences between NX/XOTcl style mixin +classes and Ruby’s mixin modules. The 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) at the same time.
package req nx
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), 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).
We define the method module, which behaves somewhat similar to +Ruby’s module command and 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).
nx::Class eval { + :protected method module {name:class} { + :configure -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 + } +}
Create a class Group, which is essentially a subclass of "Enumerable".
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 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 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 at the same time.