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.