Previous Contents Next

Extending Components

We call a collection of data and methods on the data a component. In the functional/modular model, a component consists of the definition of a type and some functions which manipulate the type. Similarly a component in the object model consists of a hierarchy of classes, inheriting from one (single) class and therefore having all of its behaviors. The problem of the extensibility of components consists of wanting on the one hand to extend the behaviors and on the other to extend the data operated on, and all this without modifying the initial program sources. For example a component image can be either a rectangle or a circle which one can draw or move.

  rectangle circle group
draw X X  
move X X  
grow      

We might wish to extend the image component with the method grow and create groups of images. The behavior of the two models differs depending on the direction of the extension: data or methods. First we define, in each model, the common part of the image component, and then we try to extend it.

In the Functional Model

We define the type image as a variant type which contains two cases. The methods take a parameter of type image and carry out the required action.

# type image = Rect of float | Circle of float ;;
# let draw = function Rect r -> ... | Circle c -> ... ;;
# let move = ... ;;


Afterwards, we could encapsulate these global declarations in a simple module.

Extension of Methods

The extension of the methods depends on the representation of the type image in the module. If this type is abstract, it is no longer possible to extend the methods. In the case where the type remains concrete, it is easy to add a grow function which changes the scale of an image by choosing a rectangle or a circle by pattern matching.

Extension of Data Types

The extension of data types cannot be achieved with the type image. In fact Objective CAML types are not extensible, except in the case of the type exn which represents exceptions. It is not possible to extend data while keeping the same type, therefore it is necessary to define a new type n_image in the following way:
type n_image = I of image | G of n_image * n_image;;
Thus we should redefine the methods for this new type, simulating a kind of inheritance. This becomes complex when there are many extensions.

In the Object Model

We define the classes rectangle and circle, subclasses of the abstract class image which has two abstract methods, draw and move.

# class virtual image () =
object(self:'a)
method virtual draw : unit -> unit
method virtual move : float * float -> unit
end;;
# class rectangle x y w h =
object
inherit image ()
val mutable x = x
val mutable y = y
val mutable w = w
val mutable h = h
method draw () = Printf.printf "R: (%f,%f) [%f,%f]" x y w h
method move (dx,dy) = x <- x +. dx; y <- y +. dy
end;;
# class circle x y r =
object
val mutable x = x
val mutable y = y
val mutable r = r
method draw () = Printf.printf "C: (%f,%f) [%f]" x y r
method move (dx, dy) = x <- x +. dx; y <- y +. dy
end;;


The following program constructs a list of images and displays it.

# let r = new rectangle 1. 1. 3. 4.;;
val r : rectangle = <obj>
# let c = new circle 1. 1. 4.;;
val c : circle = <obj>
# let l = [ (r :> image); (c :> image)];;
val l : image list = [<obj>; <obj>]
# List.iter (fun x -> x#draw(); print_newline()) l;;
R: (1.000000,1.000000) [3.000000,4.000000]
C: (1.000000,1.000000) [4.000000]
- : unit = ()


Extension of Data Types

The data are easily extended by adding a subclass of the class image in the following way.

# class group i1 i2 =
object
val i1 = (i1:#image)
val i2 = (i2:#image)
method draw () = i1#draw(); print_newline (); i2#draw()
method move p = i1#move p; i2#move p
end;;


We notice now that the ``type'' image becomes recursive because the class group depends outside inheritance on the class image.

# let g = new group (r:>image) (c:>image);;
val g : group = <obj>
# g#draw();;
R: (1.000000,1.000000) [3.000000,4.000000]
C: (1.000000,1.000000) [4.000000]- : unit = ()


Extension of Methods

We define an abstract subclass of image which contains a new method.

# class virtual e_image () =
object
inherit image ()
method virtual surface : unit -> float
end;;


We can define classes e_rectangle and e_circle which inherit from e_image and from rectangle and circle respectively. We can then work on extended image to use this new method. There is a remaining difficulty with the class group. This contains two fields of type image, so even when inheriting from the class e_image it will not be possible to send the grow message to the image fields. It is thus possible to extend the methods, except in the case of subclasses corresponding to recursive types.

Extension of Data and Methods

To implement extension in both ways, it is necessary to define recursive types in the for of a parameterized class. We redefine the class group.

# class ['a] group i1 i2 =
object
val i1 = (i1:'a)
val i2 = (i2:'a)
method draw () = i1#draw(); i2#draw()
method move p = i1#move p; i2#move p
end;;


We then carry on the same principle for the class e_image.

# class virtual ext_image () =
object
inherit image ()
method virtual surface : unit -> float
end;;
# class ext_rectangle x y w h =
object
inherit ext_image ()
inherit rectangle x y w h
method surface () = w *. h
end;;
# class ext_circle x y r=
object
inherit ext_image ()
inherit circle x y r
method surface () = 3.14 *. r *.r
end;;


The extension of the class group thus becomes

# class ['a] ext_group ei1 ei2 =
object
inherit image()
inherit ['a] group ei1 ei2
method surface () = ei1#surface() +. ei2#surface ()
end;;


We get the following program which constructs a list le of the type ext_image.

# let er = new ext_rectangle 1. 1. 2. 4. ;;
val er : ext_rectangle = <obj>
# let ec = new ext_circle 1. 1. 8.;;
val ec : ext_circle = <obj>
# let eg = new ext_group er ec;;
val eg : ext_rectangle ext_group = <obj>
# let le = [ (er:>ext_image); (ec :> ext_image); (eg :> ext_image)];;
val le : ext_image list = [<obj>; <obj>; <obj>]
# List.map (fun x -> x#surface()) le;;
- : float list = [8; 200.96; 208.96]


Generalization

To generalize the extension of the methods it is preferable to integrate some functions in a method handler and to construct a parameterized class with the return type of the method. For this we define the following class:

# class virtual ['a] get_image (f: 'b -> unit -> 'a) =
object(self:'b)
inherit image ()
method handler () = f(self) ()
end;;


The following classes then possess an additional functional parameter for the construction of their instances.

# class ['a] get_rectangle f x y w h =
object(self:'b)
inherit ['a] get_image f
inherit rectangle x y w h
method get = (x,y,w,h)
end;;
# class ['a] get_circle f x y r=
object(self:'b)
inherit ['a] get_image f
inherit circle x y r
method get = (x,y,r)
end;;


The extension of the class group thus takes two type parameters:

# class ['a,'c] get_group f eti1 eti2 =
object
inherit ['a] get_image f
inherit ['c] group eti1 eti2
method get = (i1,i2)
end;;


We get the program which extends the method of the instance of get_image.

# let etr = new get_rectangle
(fun r () -> let (x,y,w,h) = r#get in w *. h) 1. 1. 2. 4. ;;
val etr : float get_rectangle = <obj>
# let etc = new get_circle
(fun c () -> let (x,y,r) = c#get in 3.14 *. r *. r) 1. 1. 8.;;
val etc : float get_circle = <obj>
# let etg = new get_group
(fun g () -> let (i1,i2) = g#get in i1#handler() +. i2#handler())
(etr :> float get_image) (etc :> float get_image);;
val etg : (float, float get_image) get_group = <obj>
# let gel = [ (etr :> float get_image) ; (etc :> float get_image) ;
(etg :> float get_image) ];;
val gel : float get_image list = [<obj>; <obj>; <obj>]
# List.map (fun x -> x#handler()) gel;;
- : float list = [8; 200.96; 208.96]


The extension of data and methods is easier in the object model when it is combined with the functional model.


Previous Contents Next