1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<META name="GENERATOR" content="hevea 1.08">
<LINK rel="stylesheet" type="text/css" href="manual.css">
<TITLE>
Advanced examples with classes and modules
</TITLE>
</HEAD>
<BODY >
<A HREF="manual006.html"><IMG SRC ="previous_motif.gif" ALT="Previous"></A>
<A HREF="index.html"><IMG SRC ="contents_motif.gif" ALT="Up"></A>
<A HREF="manual008.html"><IMG SRC ="next_motif.gif" ALT="Next"></A>
<HR>
<H1 CLASS="chapter"><A NAME="htoc43">Chapter 5</A> Advanced examples with classes and modules</H1>
<I>(Chapter written by Didier Rmy)</I><BR>
<BR>
<BR>
<BR>
<BR>
<BR>
In this chapter, we show some larger examples using objects, classes
and modules. We review many of the object features simultaneously on
the example of a bank account. We show how modules taken from the
standard library can be expressed as classes. Lastly, we describe a
programming pattern know of as <EM>virtual types</EM> through the example
of window managers.<BR>
<BR>
<H2 CLASS="section"><A NAME="htoc44">5.1</A> Extended example: bank accounts</H2>
<A NAME="ss:bank-accounts"></A>
In this section, we illustrate most aspects of Object and inheritance
by refining, debugging, and specializing the following
initial naive definition of a simple bank account. (We reuse the
module <TT>Euro</TT> defined at the end of chapter <A HREF="manual005.html#c:objectexamples">3</A>.)
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let euro = new Euro.c;;
</FONT><FONT COLOR=maroon>val euro : float -> Euro.c = <fun>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>let zero = euro 0.;;
</FONT>val zero : Euro.c = <obj>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>let neg x = x#times (-1.);;
</FONT>val neg : < times : float -> 'a; .. > -> 'a = <fun>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class account =
object
val mutable balance = zero
method balance = balance
method deposit x = balance <- balance # plus x
method withdraw x =
if x#leq balance then (balance <- balance # plus (neg x); x) else zero
end;;
</FONT>class account :
object
val mutable balance : Euro.c
method balance : Euro.c
method deposit : Euro.c -> unit
method withdraw : Euro.c -> Euro.c
end
<FONT COLOR=black>#</FONT><FONT COLOR=blue>let c = new account in c # deposit (euro 100.); c # withdraw (euro 50.);;
</FONT>- : Euro.c = <obj>
</FONT></PRE>
We now refine this definition with a method to compute interest.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class account_with_interests =
object (self)
inherit account
method private interest = self # deposit (self # balance # times 0.03)
end;;
</FONT><FONT COLOR=maroon>class account_with_interests :
object
val mutable balance : Euro.c
method balance : Euro.c
method deposit : Euro.c -> unit
method private interest : unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
We make the method <TT>interest</TT> private, since clearly it should not be
called freely from the outside. Here, it is only made accessible to subclasses
that will manage monthly or yearly updates of the account. <BR>
<BR>
We should soon fix a bug in the current definition: the deposit method can
be used for withdrawing money by depositing negative amounts. We can
fix this directly:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class safe_account =
object
inherit account
method deposit x = if zero#leq x then balance <- balance#plus x
end;;
</FONT><FONT COLOR=maroon>class safe_account :
object
val mutable balance : Euro.c
method balance : Euro.c
method deposit : Euro.c -> unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
However, the bug might be fixed more safely by the following definition:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class safe_account =
object
inherit account as unsafe
method deposit x =
if zero#leq x then unsafe # deposit x
else raise (Invalid_argument "deposit")
end;;
</FONT><FONT COLOR=maroon>class safe_account :
object
val mutable balance : Euro.c
method balance : Euro.c
method deposit : Euro.c -> unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
In particular, this does not require the knowledge of the implementation of
the method <TT>deposit</TT>.<BR>
<BR>
To keep trace of operations, we extend the class with a mutable field
<TT>history</TT> and a private method <TT>trace</TT> to add an operation in the
log. Then each method to be traced is redefined.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>type 'a operation = Deposit of 'a | Retrieval of 'a;;
</FONT><FONT COLOR=maroon>type 'a operation = Deposit of 'a | Retrieval of 'a
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class account_with_history =
object (self)
inherit safe_account as super
val mutable history = []
method private trace x = history <- x :: history
method deposit x = self#trace (Deposit x); super#deposit x
method withdraw x = self#trace (Retrieval x); super#withdraw x
method history = List.rev history
end;;
</FONT>class account_with_history :
object
val mutable balance : Euro.c
val mutable history : Euro.c operation list
method balance : Euro.c
method deposit : Euro.c -> unit
method history : Euro.c operation list
method private trace : Euro.c operation -> unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
One may wish to open an account and simultaneously deposit some initial
amount. Although the initial implementation did not address this
requirement, it can be achieved by using an initializer.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class account_with_deposit x =
object
inherit account_with_history
initializer balance <- x
end;;
</FONT><FONT COLOR=maroon>class account_with_deposit :
Euro.c ->
object
val mutable balance : Euro.c
val mutable history : Euro.c operation list
method balance : Euro.c
method deposit : Euro.c -> unit
method history : Euro.c operation list
method private trace : Euro.c operation -> unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
A better alternative is:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class account_with_deposit x =
object (self)
inherit account_with_history
initializer self#deposit x
end;;
</FONT><FONT COLOR=maroon>class account_with_deposit :
Euro.c ->
object
val mutable balance : Euro.c
val mutable history : Euro.c operation list
method balance : Euro.c
method deposit : Euro.c -> unit
method history : Euro.c operation list
method private trace : Euro.c operation -> unit
method withdraw : Euro.c -> Euro.c
end
</FONT></PRE>
Indeed, the latter is safer since the call to <TT>deposit</TT> will automatically
benefit from safety checks and from the trace.
Let's test it:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let ccp = new account_with_deposit (euro 100.) in
let balance = ccp#withdraw (euro 50.) in
ccp#history;;
</FONT><FONT COLOR=maroon>- : Euro.c operation list = [Deposit <obj>; Retrieval <obj>]
</FONT></PRE>
Closing an account can be done with the following polymorphic function:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let close c = c#withdraw (c#balance);;
</FONT><FONT COLOR=maroon>val close : < balance : 'a; withdraw : 'a -> 'b; .. > -> 'b = <fun>
</FONT></PRE>
Of course, this applies to all sorts of accounts. <BR>
<BR>
Finally, we gather several versions of the account into a module <TT>Account</TT>
abstracted over some currency.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let today () = (01,01,2000) (* an approximation *)
module Account (M:MONEY) =
struct
type m = M.c
let m = new M.c
let zero = m 0.
class bank =
object (self)
val mutable balance = zero
method balance = balance
val mutable history = []
method private trace x = history <- x::history
method deposit x =
self#trace (Deposit x);
if zero#leq x then balance <- balance # plus x
else raise (Invalid_argument "deposit")
method withdraw x =
if x#leq balance then
(balance <- balance # plus (neg x); self#trace (Retrieval x); x)
else zero
method history = List.rev history
end
class type client_view =
object
method deposit : m -> unit
method history : m operation list
method withdraw : m -> m
method balance : m
end
class virtual check_client x =
let y = if (m 100.)#leq x then x
else raise (Failure "Insufficient initial deposit") in
object (self) initializer <U>self#deposit</U> y end
module Client (B : sig class bank : client_view end) =
struct
class account x : client_view =
object
inherit B.bank
inherit check_client x
end
let discount x =
let c = new account x in
if today() < (1998,10,30) then c # deposit (m 100.); c
end
end;;
</FONT></PRE>
This shows the use of modules to group several class definitions that can in
fact be thought of as a single unit. This unit would be provided by a bank
for both internal and external uses.
This is implemented as a functor that abstracts over the currency so that
the same code can be used to provide accounts in different currencies.<BR>
<BR>
The class <TT>bank</TT> is the <EM>real</EM> implementation of the bank account (it
could have been inlined). This is the one that will be used for further
extensions, refinements, etc. Conversely, the client will only be given the client view.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>module Euro_account = Account(Euro);;
module Client = Euro_account.Client (Euro_account);;
new Client.account (new Euro.c 100.);;
</FONT></PRE>
Hence, the clients do not have direct access to the <TT>balance</TT>, nor the
<TT>history</TT> of their own accounts. Their only way to change their balance is
to deposit or withdraw money. It is important to give the clients
a class and not just the ability to create accounts (such as the
promotional <TT>discount</TT> account), so that they can
personalize their account.
For instance, a client may refine the <TT>deposit</TT> and <TT>withdraw</TT> methods
so as to do his own financial bookkeeping, automatically. On the
other hand, the function <TT>discount</TT> is given as such, with no
possibility for further personalization.<BR>
<BR>
It is important that to provide the client's view as a functor
<TT>Client</TT> so that client accounts can still be build after a possible
specialization of the <TT>bank</TT>.
The functor <TT>Client</TT> may remain unchanged and be passed
the new definition to initialize a client's view of the extended account.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>module Investment_account (M : MONEY) =
struct
type m = M.c
module A = Account(M)
class bank =
object
inherit A.bank as super
method deposit x =
if (new M.c 1000.)#leq x then
print_string "Would you like to invest?";
super#deposit x
end
module Client = A.Client
end;;
</FONT></PRE>
The functor <TT>Client</TT> may also be redefined when some new features of the
account can be given to the client.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>module Internet_account (M : MONEY) =
struct
type m = M.c
module A = Account(M)
class bank =
object
inherit A.bank
method mail s = print_string s
end
class type client_view =
object
method deposit : m -> unit
method history : m operation list
method withdraw : m -> m
method balance : m
method mail : string -> unit
end
module Client (B : sig class bank : client_view end) =
struct
class account x : client_view =
object
inherit B.bank
inherit A.check_client x
end
end
end;;
</FONT></PRE>
<H2 CLASS="section"><A NAME="htoc45">5.2</A> Simple modules as classes</H2>
<A NAME="ss:modules-as-classes"></A>
One may wonder whether it is possible to treat primitive types such as
integers and strings as objects. Although this is usually uninteresting
for integers or strings, there may be some situations where
this is desirable. The class <TT>money</TT> above is such an example.
We show here how to do it for strings. <BR>
<BR>
<H3 CLASS="subsection"><A NAME="htoc46">5.2.1</A> Strings</H3>
<A NAME="module:string"></A>
A naive definition of strings as objects could be:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ostring s =
object
method get n = String.get n
method set n c = String.set n c
method print = print_string s
method copy = new ostring (String.copy s)
end;;
</FONT><FONT COLOR=maroon>class ostring :
string ->
object
method copy : ostring
method get : string -> int -> char
method print : unit
method set : string -> int -> char -> unit
end
</FONT></PRE>
However, the method <TT>copy</TT> returns an object of the class <TT>string</TT>,
and not an objet of the current class. Hence, if the class is further
extended, the method <TT>copy</TT> will only return an object of the parent
class.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class sub_string s =
object
inherit ostring s
method sub start len = new sub_string (String.sub s start len)
end;;
</FONT><FONT COLOR=maroon>class sub_string :
string ->
object
method copy : ostring
method get : string -> int -> char
method print : unit
method set : string -> int -> char -> unit
method sub : int -> int -> sub_string
end
</FONT></PRE>
As seen in section <A HREF="manual005.html#ss:binary-methods">3.16</A>, the solution is to use
functional update instead. We need to create an instance variable
containing the representation <TT>s</TT> of the string.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class better_string s =
object
val repr = s
method get n = String.get n
method set n c = String.set n c
method print = print_string repr
method copy = {< repr = String.copy repr >}
method sub start len = {< repr = String.sub s start len >}
end;;
</FONT><FONT COLOR=maroon>class better_string :
string ->
object ('a)
val repr : string
method copy : 'a
method get : string -> int -> char
method print : unit
method set : string -> int -> char -> unit
method sub : int -> int -> 'a
end
</FONT></PRE>
As shown in the inferred type, the methods <TT>copy</TT> and <TT>sub</TT> now return
objects of the same type as the one of the class.<BR>
<BR>
Another difficulty is the implementation of the method <TT>concat</TT>.
In order to concatenate a string with another string of the same class,
one must be able to access the instance variable externally. Thus, a method
<TT>repr</TT> returning s must be defined. Here is the correct definition of
strings:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ostring s =
object (self : 'mytype)
val repr = s
method repr = repr
method get n = String.get n
method set n c = String.set n c
method print = print_string repr
method copy = {< repr = String.copy repr >}
method sub start len = {< repr = String.sub s start len >}
method concat (t : 'mytype) = {< repr = repr ^ t#repr >}
end;;
</FONT><FONT COLOR=maroon>class ostring :
string ->
object ('a)
val repr : string
method concat : 'a -> 'a
method copy : 'a
method get : string -> int -> char
method print : unit
method repr : string
method set : string -> int -> char -> unit
method sub : int -> int -> 'a
end
</FONT></PRE>
Another constructor of the class string can be defined to return an
uninitialized string of a given length:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class cstring n = ostring (String.create n);;
</FONT><FONT COLOR=maroon>class cstring : int -> ostring
</FONT></PRE>
Here, exposing the representation of strings is probably harmless. We do
could also hide the representation of strings as we hid the currency in the
class <TT>money</TT> of section <A HREF="manual005.html#ss:friends">3.17</A>.<BR>
<BR>
<H4 CLASS="subsubsection">Stacks</H4>
<A NAME="module:stack"></A>
There is sometimes an alternative between using modules or classes for
parametric data types.
Indeed, there are situations when the two approaches are quite similar.
For instance, a stack can be straightforwardly implemented as a class:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>exception Empty;;
</FONT><FONT COLOR=maroon>exception Empty
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['a] stack =
object
val mutable l = ([] : 'a list)
method push x = l <- x::l
method pop = match l with [] -> raise Empty | a::l' -> l <- l'; a
method clear = l <- []
method length = List.length l
end;;
</FONT>class ['a] stack :
object
val mutable l : 'a list
method clear : unit
method length : int
method pop : 'a
method push : 'a -> unit
end
</FONT></PRE>
However, writing a method for iterating over a stack is more
problematic. A method <TT>fold</TT> would have type
<TT>('b -> 'a -> 'b) -> 'b -> 'b</TT>. Here <TT>'a</TT> is the parameter of the stack.
The parameter <TT>'b</TT> is not related to the class <TT>'a stack</TT> but to the
argument that will be passed to the method <TT>fold</TT>.
A naive approach is to make <TT>'b</TT> an extra parameter of class <TT>stack</TT>:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['a, 'b] stack2 =
object
inherit ['a] stack
method fold f (x : 'b) = List.fold_left f x l
end;;
</FONT><FONT COLOR=maroon>class ['a, 'b] stack2 :
object
val mutable l : 'a list
method clear : unit
method fold : ('b -> 'a -> 'b) -> 'b -> 'b
method length : int
method pop : 'a
method push : 'a -> unit
end
</FONT></PRE>
However, the method <TT>fold</TT> of a given object can only be
applied to functions that all have the same type:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let s = new stack2;;
</FONT><FONT COLOR=maroon>val s : ('_a, '_b) stack2 = <obj>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>s#fold (+) 0;;
</FONT>- : int = 0
<FONT COLOR=black>#</FONT><FONT COLOR=blue>s;;
</FONT>- : (int, int) stack2 = <obj>
</FONT></PRE>
A better solution is to use polymorphic methods, which were
introduced in Objective Caml version 3.05. Polymorphic methods makes
it possible to treat the type variable <TT>'b</TT> in the type of <TT>fold</TT> as
universally quantified, giving <TT>fold</TT> the polymorphic type
<TT>Forall 'b. ('b -> 'a -> 'b) -> 'b -> 'b</TT>.
An explicit type declaration on the method <TT>fold</TT> is required, since
the type checker cannot infer the polymorphic type by itself.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['a] stack3 =
object
inherit ['a] stack
method fold : 'b. ('b -> 'a -> 'b) -> 'b -> 'b
= fun f x -> List.fold_left f x l
end;;
</FONT><FONT COLOR=maroon>class ['a] stack3 :
object
val mutable l : 'a list
method clear : unit
method fold : ('b -> 'a -> 'b) -> 'b -> 'b
method length : int
method pop : 'a
method push : 'a -> unit
end
</FONT></PRE>
<H3 CLASS="subsection"><A NAME="htoc47">5.2.2</A> Hashtbl</H3>
<A NAME="module:hashtbl"></A>
A simplified version of object-oriented hash tables should have the
following class type.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class type ['a, 'b] hash_table =
object
method find : 'a -> 'b
method add : 'a -> 'b -> unit
end;;
</FONT><FONT COLOR=maroon>class type ['a, 'b] hash_table =
object method add : 'a -> 'b -> unit method find : 'a -> 'b end
</FONT></PRE>
A simple implementation, which is quite reasonable for small hastables is
to use an association list:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['a, 'b] small_hashtbl : ['a, 'b] hash_table =
object
val mutable table = []
method find key = List.assoc key table
method add key valeur = table <- (key, valeur) :: table
end;;
</FONT><FONT COLOR=maroon>class ['a, 'b] small_hashtbl : ['a, 'b] hash_table
</FONT></PRE>
A better implementation, and one that scales up better, is to use a
true hash tables... whose elements are small hash tables!
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['a, 'b] hashtbl size : ['a, 'b] hash_table =
object (self)
val table = Array.init size (fun i -> new small_hashtbl)
method private hash key =
(Hashtbl.hash key) mod (Array.length table)
method find key = table.(self#hash key) # find key
method add key = table.(self#hash key) # add key
end;;
</FONT><FONT COLOR=maroon>class ['a, 'b] hashtbl : int -> ['a, 'b] hash_table
</FONT></PRE>
<H3 CLASS="subsection"><A NAME="htoc48">5.2.3</A> Sets</H3>
<A NAME="module:set"></A>
Implementing sets leads to another difficulty. Indeed, the method
<TT>union</TT> needs to be able to access the internal representation of
another object of the same class. <BR>
<BR>
This is another instance of friend functions as seen in section
<A HREF="manual005.html#ss:friends">3.17</A>. Indeed, this is the same mechanism used in the module
<TT>Set</TT> in the absence of objects.<BR>
<BR>
In the object-oriented version of sets, we only need to add an additional
method <TT>tag</TT> to return the representation of a set. Since sets are
parametric in the type of elements, the method <TT>tag</TT> has a parametric type
<TT>'a tag</TT>, concrete within
the module definition but abstract in its signature.
From outside, it will then be guaranteed that two objects with a method <TT>tag</TT>
of the same type will share the same representation.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>module type SET =
sig
type 'a tag
class ['a] c :
object ('b)
method is_empty : bool
method mem : 'a -> bool
method add : 'a -> 'b
method union : 'b -> 'b
method iter : ('a -> unit) -> unit
method tag : 'a tag
end
end;;
module Set : SET =
struct
let rec merge l1 l2 =
match l1 with
[] -> l2
| h1 :: t1 ->
match l2 with
[] -> l1
| h2 :: t2 ->
if h1 < h2 then h1 :: merge t1 l2
else if h1 > h2 then h2 :: merge l1 t2
else merge t1 l2
type 'a tag = 'a list
class ['a] c =
object (_ : 'b)
val repr = ([] : 'a list)
method is_empty = (repr = [])
method mem x = List.exists ((=) x) repr
method add x = {< repr = merge [x] repr >}
method union (s : 'b) = {< repr = merge repr s#tag >}
method iter (f : 'a -> unit) = List.iter f repr
method tag = repr
end
end;;
</FONT></PRE>
<H2 CLASS="section"><A NAME="htoc49">5.3</A> The subject/observer pattern</H2>
<A NAME="ss:subject-observer"></A>
The following example, known as the subject/observer pattern, is often
presented in the literature as a difficult inheritance problem with
inter-connected classes.
The general pattern amounts to the definition a pair of two
classes that recursively interact with one another. <BR>
<BR>
The class <TT>observer</TT> has a distinguished method <TT>notify</TT> that requires
two arguments, a subject and an event to execute an action.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class virtual ['subject, 'event] observer =
object
method virtual notify : 'subject -> 'event -> unit
end;;
</FONT><FONT COLOR=maroon>class virtual ['a, 'b] observer :
object method virtual notify : 'a -> 'b -> unit end
</FONT></PRE>
The class <TT>subject</TT> remembers a list of observers in an instance variable,
and has a distinguished method <TT>notify_observers</TT> to broadcast the message
<TT>notify</TT> to all observers with a particular event <TT>e</TT>.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['observer, 'event] subject =
object (self)
val mutable observers = ([]:'observer list)
method add_observer obs = observers <- (obs :: observers)
method notify_observers (e : 'event) =
List.iter (fun x -> x#notify self e) observers
end;;
</FONT><FONT COLOR=maroon>class ['a, 'b] subject :
object ('c)
constraint 'a = < notify : 'c -> 'b -> unit; .. >
val mutable observers : 'a list
method add_observer : 'a -> unit
method notify_observers : 'b -> unit
end
</FONT></PRE>
The difficulty usually relies in defining instances of the pattern above
by inheritance. This can be done in a natural and obvious manner in
Ocaml, as shown on the following example manipulating windows.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>type event = Raise | Resize | Move;;
</FONT><FONT COLOR=maroon>type event = Raise | Resize | Move
<FONT COLOR=black>#</FONT><FONT COLOR=blue>let string_of_event = function
Raise -> "Raise" | Resize -> "Resize" | Move -> "Move";;
</FONT>val string_of_event : event -> string = <fun>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>let count = ref 0;;
</FONT>val count : int ref = {contents = 0}
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['observer] window_subject =
let id = count := succ !count; !count in
object (self)
inherit ['observer, event] subject
val mutable position = 0
method identity = id
method move x = position <- position + x; self#notify_observers Move
method draw = Printf.printf "{Position = %d}\n" position;
end;;
</FONT>class ['a] window_subject :
object ('b)
constraint 'a = < notify : 'b -> event -> unit; .. >
val mutable observers : 'a list
val mutable position : int
method add_observer : 'a -> unit
method draw : unit
method identity : int
method move : int -> unit
method notify_observers : event -> unit
end
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['subject] window_observer =
object
inherit ['subject, event] observer
method notify s e = s#draw
end;;
</FONT>class ['a] window_observer :
object
constraint 'a = < draw : unit; .. >
method notify : 'a -> event -> unit
end
</FONT></PRE>
Unsurprisingly the type of <TT>window</TT> is recursive.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let window = new window_subject;;
</FONT><FONT COLOR=maroon>val window : < notify : 'a -> event -> unit; _.. > window_subject as 'a =
<obj>
</FONT></PRE>
However, the two classes of <TT>window_subject</TT> and <TT>window_observer</TT> are not
mutually recursive.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let window_observer = new window_observer;;
</FONT><FONT COLOR=maroon>val window_observer : < draw : unit; _.. > window_observer = <obj>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>window#add_observer window_observer;;
</FONT>- : unit = ()
<FONT COLOR=black>#</FONT><FONT COLOR=blue>window#move 1;;
</FONT>{Position = 1}
- : unit = ()
</FONT></PRE>
Classes <TT>window_observer</TT> and <TT>window_subject</TT> can still be extended by
inheritance. For instance, one may enrich the <TT>subject</TT> with new
behaviors and refined the behavior of the observer.
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['observer] richer_window_subject =
object (self)
inherit ['observer] window_subject
val mutable size = 1
method resize x = size <- size + x; self#notify_observers Resize
val mutable top = false
method raise = top <- true; self#notify_observers Raise
method draw = Printf.printf "{Position = %d; Size = %d}\n" position size;
end;;
</FONT><FONT COLOR=maroon>class ['a] richer_window_subject :
object ('b)
constraint 'a = < notify : 'b -> event -> unit; .. >
val mutable observers : 'a list
val mutable position : int
val mutable size : int
val mutable top : bool
method add_observer : 'a -> unit
method draw : unit
method identity : int
method move : int -> unit
method notify_observers : event -> unit
method raise : unit
method resize : int -> unit
end
<FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['subject] richer_window_observer =
object
inherit ['subject] window_observer as super
method notify s e = if e <> Raise then s#raise; super#notify s e
end;;
</FONT>class ['a] richer_window_observer :
object
constraint 'a = < draw : unit; raise : unit; .. >
method notify : 'a -> event -> unit
end
</FONT></PRE>
We can also create a different kind of observer:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>class ['subject] trace_observer =
object
inherit ['subject, event] observer
method notify s e =
Printf.printf
"<Window %d <== %s>\n" s#identity (string_of_event e)
end;;
</FONT><FONT COLOR=maroon>class ['a] trace_observer :
object
constraint 'a = < identity : int; .. >
method notify : 'a -> event -> unit
end
</FONT></PRE>
and attached several observers to the same object:
<PRE><FONT COLOR=black>#</FONT><FONT COLOR=blue>let window = new richer_window_subject;;
</FONT><FONT COLOR=maroon>val window :
< notify : 'a -> event -> unit; _.. > richer_window_subject as 'a = <obj>
<FONT COLOR=black>#</FONT><FONT COLOR=blue>window#add_observer (new richer_window_observer);;
</FONT>- : unit = ()
<FONT COLOR=black>#</FONT><FONT COLOR=blue>window#add_observer (new trace_observer);;
</FONT>- : unit = ()
<FONT COLOR=black>#</FONT><FONT COLOR=blue>window#move 1; window#resize 2;;
</FONT><Window 1 <== Move>
<Window 1 <== Raise>
{Position = 1; Size = 1}
{Position = 1; Size = 1}
<Window 1 <== Resize>
<Window 1 <== Raise>
{Position = 1; Size = 3}
{Position = 1; Size = 3}
- : unit = ()
</FONT></PRE>
<HR>
<A HREF="manual006.html"><IMG SRC ="previous_motif.gif" ALT="Previous"></A>
<A HREF="index.html"><IMG SRC ="contents_motif.gif" ALT="Up"></A>
<A HREF="manual008.html"><IMG SRC ="next_motif.gif" ALT="Next"></A>
</BODY>
</HTML>
|