View · Search · Index

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.