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
.
1
4
*.
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
.
1
4
*.
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.