Previous Up Next
5.1 Overlapping


Many abstract datatypes can be defined using either classes or modules. A representative example is the type of stacks. Stacks have been defined as a parametric class in section 3.1.2 where operations on stacks are methods embedded into stack-objects, and as a module defining an abstract type of stacks and the associated operations in section 4.1.1. The following table summarizes the close correspondence between those two implementations.
  class version module version
The type of stacks 'astack 'a Astack.t
Create a stack newstack Astack.created()
Push x on s s#push x Astack.push x s
Pop from s s#pop Astack.pop s

More generally, all algebraic abstract types that are modifiable in place and such that all associated operations are unary (ie. they take only one argument of the abstract type) can be defined as classes or as modules in almost the same way. Here, the choice between those two forms is mainly a question of programming style: some programmers prefer to see the operations of the abstract type as methods attached to values of this type, while others prefer to see them as functions outside values of the abstract type.

Moreover, the two alternatives are still comparable when extending the stack implementation with new operations. For instance, in both cases, a new implementation of stacks can be derived from the older one with an additional method top to explore the top of the stack without popping it. Both implementations, described in Figure 5.1, are straightforward: the class approach uses inheritance while the module approach uses the include construct. (The effect of includeStack is to rebind all components of the Stack structure in the substructure.)
Figure 5.1: Class v.s. Module versions of stacks
      
class ['astack_ext =
  object (self)
    inherit ['astack
    method top =
      let s = self#pop in
      self#push ss
end;;
      
module StackExt =
  struct
    include Stack
    let top p =
      let s = pop p in
      push s ps
  end;;

However, the two approaches definitely differ when considering late binding, which is only possible with the class approach. For instance, redefining the implementation of pop would automatically benefit to the implementation of top (ie. the method top would call the new definition of pop). This mechanism does not have any counterpart in OCaml modules.

In conclusion, if inheritance is needed, the class approach seems more appropriate, and it becomes the only possible (direct) solution if, moreover, late binding is required. Conversely, for abstract datatypes used with binary operations, such as sets with a merge operation, the module approach will be preferable, as long as inheritance is not used. Furthermore, the module approach is the only one that allows to return private data to the user, but abstractly, so as to preserve its integrity. Of course, the module system is also the basis for separate compilation.


Previous Up Next