762 DESIGNING CLASS INTERFACES $23.1 make cartesian (a,b:REAL)is -Initialize with abscissa a,ordinate b. do private_x a.private y:=b cartesian ready :True;polar ready :False ensure cartesian ready,not polar ready end and symmetrically for make polar. The exported operations are easy to write;we can start for example with the procedure variants(we will see the function variants such as infix"+"next): add (other:COMPLEX)is --Add the value of other. do prepare_cartesian;polar ready False privatex=x other.x,private_y =y other.y ensure x=old x other.x;y=old y other.y cartesian ready,not polar ready end (Note the importance in the postcondition of using x and y,not private x and privatey which might not have been up to date before the call.) divide (COMPLEX)is --Divide by = require z.rho /0 --(To be replaced by a numerically more realistic precondition) do prepare polar;cartesian ready:=False private rho:=rho/other.rho private theta =(theta-other,theta)Two pi --Using l as remainder operation ensure rho old rho other.rho theta =(old theta-other.theta)Two pi polar ready;not cartesian ready end and similarly for subtract and multiply.(The precondition and postcondition may need some adaptation to reflect the realities of floating-point computations on computers.)The function variants follow the same pattern:
762 DESIGNING CLASS INTERFACES §23.1 make_cartesian (a, b: REAL) is -- Initialize with abscissa a, ordinate b. do private_x := a; private_ y := b cartesian_ready := True; polar_ready := False ensure cartesian_ready; not polar_ready end and symmetrically for make_polar. The exported operations are easy to write; we can start for example with the procedure variants (we will see the function variants such as infix "+" next): add (other: COMPLEX) is -- Add the value of other. do prepare_cartesian; polar_ready := False private_x := x + other ● x; private_y = y + other ● y ensure x = old x + other ● x; y = old y + other ● y cartesian_ready; not polar_ready end (Note the importance in the postcondition of using x and y, not private_x and private_y which might not have been up to date before the call.) divide (z: COMPLEX) is -- Divide by z. require z ● rho /= 0 -- (To be replaced by a numerically more realistic precondition) do prepare_polar; cartesian_ready := False private_rho := rho / other ● rho private_theta = (theta – other ● theta) \\ Two_pi -- Using \\ as remainder operation ensure rho = old rho / other ● rho theta = (old theta — other ● theta) \\ Two_pi polar_ready; not cartesian_ready end and similarly for subtract and multiply. (The precondition and postcondition may need some adaptation to reflect the realities of floating-point computations on computers.) The function variants follow the same pattern:
$23.1 SIDE EFFECTS IN FUNCTIONS 763 infix "+"(other:COMPLEX):COMPLEX is --Sum of current complex and other do !Result.make cartesian (x other.x,y other.y) ensure Result.x =x other.x;Result.y =y other.y Result.cartesian ready end infix "/"(COMPLEX):COMPLEX is --Quotient of current complex by = require z.rho() --(To be replaced by a numerically more realistic condition) do !Result.make polar (rho other.rho,(theta-other,theta)\\Two pi) ensure Result.rhorho other.rho Result.theta =(old theta-other.theta)Two pi Result.polar ready end and similarly for infix "-and infix "* Note that for the last postcondition clauses of these functions to be valid,cartesian ready and polar ready must be exported to the class itself,by appearing in a clause of the form feature COMPLEY;they are not exported to any other class. But where are the side effects?In the last two functions,they are not directly visible;this is becausex,y,rho and theta,behind their innocent looks,are sneaky little side-effectors!Computing x or y will cause a secret change of representation (a call to prepare cartesian)if the cartesian representation was not ready,and symmetrically for rho and theta.Here for example are x and theta: x:REAL is --Abscissa do prepare cartesian;Result :private x end theta:REAL is --Angle do prepare polar;Result:=private theta end
§23.1 SIDE EFFECTS IN FUNCTIONS 763 infix "+" (other: COMPLEX): COMPLEX is -- Sum of current complex and other do !! Result ● make_cartesian (x + other ● x, y + other ● y) ensure Result ● x = x + other ● x; Result ● y = y + other ● y Result ● cartesian_ready end infix "/" (z: COMPLEX): COMPLEX is -- Quotient of current complex by z. require z ● rho /= 0 -- (To be replaced by a numerically more realistic condition) do !! Result ● make_polar (rho / other ● rho, (theta – other ● theta) \\ Two_pi) ensure Result ● rho = rho / other ● rho Result ● theta = (old theta — other ● theta) \\ Two_pi Result ● polar_ready end and similarly for infix "–" and infix "∗". Note that for the last postcondition clauses of these functions to be valid, cartesian_ready and polar_ready must be exported to the class itself, by appearing in a clause of the form feature {COMPLEX}; they are not exported to any other class. But where are the side effects? In the last two functions, they are not directly visible; this is because x, y, rho and theta, behind their innocent looks, are sneaky little side-effectors! Computing x or y will cause a secret change of representation (a call to prepare_cartesian) if the cartesian representation was not ready, and symmetrically for rho and theta. Here for example are x and theta: x: REAL is -- Abscissa do prepare_cartesian; Result := private_x end theta: REAL is -- Angle do prepare_polar; Result := private_theta end
764 DESIGNING CLASS INTERFACES $23.2 Functions y and rho are similar.All these functions call a procedure which may trigger a change of state.Unlike add and consorts,however,they do not invalidate the previous representation when a new one is computed.For example,ifx is called in a state where cartesian ready is false,both representations (all four real attributes)will be up to date afterwards.This is because the functions may produce side effects on the concrete objects only,not on the associated abstract objects.To express this property more formally:computing =.x or one of the other functions may change the concrete object associated with=,say from c to c2,but always with the guarantee that a(c)=a(c3) where a is the abstraction function.The computer objects c and c,may be different,but they represent the same mathematical object,a complex number. Such side effects are harmless,as they only affect secret attributes and hence cannot be detected by clients. The object-oriented approach encourages such flexible,self-adapting schemes, which internally choose the best implementation according to the needs of the moment. As long as the resulting implementation changes affect the concrete state but not the abstract state,they can appear in functions without violating the Command-Query Separation principle or endangering referential transparency for clients 23.2 HOW MANY ARGUMENTS FOR A FEATURE? In trying to make classes-especially reusable classes-easy to use,you should devote special attention to the number of arguments of features.As we will see,well-understood object technology yields a style of feature interface radically different from what you typically get with traditional approaches;there will,in particular,be far fewer arguments The importance of argument counts When your development relies on a supplier class,features are your day-to-day channel to it.The simplicity of the feature interfaces fundamentally determines the class's ease of use.Various factors influence this,in particular the consistency of the conventions;but in the end a simple numerical criterion dominates everything else:how many arguments do features have?The more arguments,the more you have to remember. This is particularly true of library classes.The criterion for success there is simple: after a potential library user has taken the (preferably short)time to understand what a class is about and,if he decides to use it,selected the set of features that he needs for the moment,he should be able to learn these features quickly and,after as few uses as possible,remember them without having to go back to the documentation.This will only work if features-aside from all other qualities of consistency,proper naming conventions and general quality of the design-have very short argument lists. If you examine a typical subroutine library you will commonly encounter subroutines with many arguments.Here for example is an integration routine from a mathematical library justly renowned for the excellence of its algorithms,but constrained in its interface by the use of traditional subroutine techniques:
764 DESIGNING CLASS INTERFACES §23.2 Functions y and rho are similar. All these functions call a procedure which may trigger a change of state. Unlike add and consorts, however, they do not invalidate the previous representation when a new one is computed. For example, if x is called in a state where cartesian_ready is false, both representations (all four real attributes) will be up to date afterwards. This is because the functions may produce side effects on the concrete objects only, not on the associated abstract objects. To express this property more formally: computing z ● x or one of the other functions may change the concrete object associated with z, say from c1 to c2, but always with the guarantee that a (c1) = a (c2) where a is the abstraction function. The computer objects c1 and c2 may be different, but they represent the same mathematical object, a complex number. Such side effects are harmless, as they only affect secret attributes and hence cannot be detected by clients. The object-oriented approach encourages such flexible, self-adapting schemes, which internally choose the best implementation according to the needs of the moment. As long as the resulting implementation changes affect the concrete state but not the abstract state, they can appear in functions without violating the Command-Query Separation principle or endangering referential transparency for clients. 23.2 HOW MANY ARGUMENTS FOR A FEATURE? In trying to make classes — especially reusable classes — easy to use, you should devote special attention to the number of arguments of features. As we will see, well-understood object technology yields a style of feature interface radically different from what you typically get with traditional approaches; there will, in particular, be far fewer arguments. The importance of argument counts When your development relies on a supplier class, features are your day-to-day channel to it. The simplicity of the feature interfaces fundamentally determines the class’s ease of use. Various factors influence this, in particular the consistency of the conventions; but in the end a simple numerical criterion dominates everything else: how many arguments do features have? The more arguments, the more you have to remember. This is particularly true of library classes. The criterion for success there is simple: after a potential library user has taken the (preferably short) time to understand what a class is about and, if he decides to use it, selected the set of features that he needs for the moment, he should be able to learn these features quickly and, after as few uses as possible, remember them without having to go back to the documentation. This will only work if features — aside from all other qualities of consistency, proper naming conventions and general quality of the design — have very short argument lists. If you examine a typical subroutine library you will commonly encounter subroutines with many arguments. Here for example is an integration routine from a mathematical library justly renowned for the excellence of its algorithms, but constrained in its interface by the use of traditional subroutine techniques:
$23.2 HOW MANY ARGUMENTS FOR A FEATURE? 765 Warning:this is not nonlinear ode an object-oriented (equation count:in INTEGER: interface! epsilon:in out DOUBLE: func:procedure (eq count:INTEGER,a:DOUBLE;eps:DOUBLE: b:ARRAY [DOUBLE];cm:pointer Libtype) left count,coupled count:in INTEGER. ) [And so on.Altogether 19 arguments,including: -4 in out values; -3 arrays,used both as input and output; -6 functions,each with 6 or 7 arguments of which 2 or 3 are arrays!] Since the purpose of this example is not to criticize one particular numerical library but to emphasize the difference between O-O and traditional interfaces,the routine and arguments names have been changed and the syntax (in C in the original)has been adapted.The resulting notation resembles the notation of this book,which,however, would of course exclude such non-O-O mechanisms as in out arguments,explicit pointer manipulation,and arguments(such as fioic and 5 others)that are themselves routines. Several properties make this scheme particularly complex to use: Many arguments are in out,that is to say must be initialized by the caller to pass a certain value and are updated by the routine to return some information.For example epsilon specifies on input whether continuation is required (yes if less than 0;if between 0 and 1,continuation is required unless epsilon<precision,etc.).On output,it provides an estimate of the increment. Many arguments,both to the routine itself and to its own routine arguments,are arrays,which again serve to pass certain values on input and return others on output. Some arguments serve to specify the many possibilities for error processing (stop processing,write message to a file,continue anyway...). Even though high-quality numerical libraries have been in existence for many years and,as mentioned in an earlier chapter,provide some of the most concrete evidence ofreal reuse,they are still not as widely used in scientific computation as they should be.The complexity of their interfaces,and in particular the large number of arguments illustrated by nonlinear ode,are clearly a big part of the reason. On the Math library Part of the complexity comes from the problems handled by these routines.But one and techniques of can do better.An object-oriented numerical library,Math,offers a completely different scientific object-oni- ented computing, approach,consistent with object technology concepts and with the principles of this book. see [Dubois 1997]. An earlier discussion cited the Math library as an example of using object technology to re- The earlier mention architecture older software,and the library indeed uses an existing non-O-O library as its was in“Object--ori- ented re-architec- core engine,since it would have been absurd to duplicate the basic algorithmic work;but turing”,page44l. it provides a modern,O-O client interface.The basic non-linear ODE routine has the form solve -Solve the problem,recording the answer in x and y
§23.2 HOW MANY ARGUMENTS FOR A FEATURE? 765 nonlinear_ode (equation_count: in INTEGER; epsilon: in out DOUBLE; func: procedure (eq_count: INTEGER; a: DOUBLE; eps: DOUBLE; b: ARRAY [DOUBLE]; cm: pointer Libtype) left_count, coupled_count: in INTEGER; …) [And so on. Altogether 19 arguments, including: - 4 in out values; - 3 arrays, used both as input and output; - 6 functions, each with 6 or 7 arguments of which 2 or 3 are arrays!] Since the purpose of this example is not to criticize one particular numerical library but to emphasize the difference between O-O and traditional interfaces, the routine and arguments names have been changed and the syntax (in C in the original) has been adapted. The resulting notation resembles the notation of this book, which, however, would of course exclude such non-O-O mechanisms as in out arguments, explicit pointer manipulation, and arguments (such as func and 5 others) that are themselves routines. Several properties make this scheme particularly complex to use: • Many arguments are in out, that is to say must be initialized by the caller to pass a certain value and are updated by the routine to return some information. For example epsilon specifies on input whether continuation is required (yes if less than 0; if between 0 and 1, continuation is required unless epsilon < , etc.). On output, it provides an estimate of the increment. • Many arguments, both to the routine itself and to its own routine arguments, are arrays, which again serve to pass certain values on input and return others on output. • Some arguments serve to specify the many possibilities for error processing (stop processing, write message to a file, continue anyway…). Even though high-quality numerical libraries have been in existence for many years and, as mentioned in an earlier chapter, provide some of the most concrete evidence of real reuse, they are still not as widely used in scientific computation as they should be. The complexity of their interfaces, and in particular the large number of arguments illustrated by nonlinear_ode, are clearly a big part of the reason. Part of the complexity comes from the problems handled by these routines. But one can do better. An object-oriented numerical library, Math, offers a completely different approach, consistent with object technology concepts and with the principles of this book. An earlier discussion cited the Math library as an example of using object technology to rearchitecture older software, and the library indeed uses an existing non-O-O library as its core engine, since it would have been absurd to duplicate the basic algorithmic work; but it provides a modern, O-O client interface. The basic non-linear ODE routine has the form solve -- Solve the problem, recording the answer in x and y. Warning: this is not an object-oriented interface! precision On the Math library and techniques of scientific object-oriented computing, see [Dubois 1997]. The earlier mention was in “Object-oriented re-architecturing”, page 441
766 DESIGNING CLASS INTERFACES $23.2 In other words it takes no argument at all!You simply create an instance of the class GENERAL BOUNDARY VALUE PROBLEM to represent the mathematical problem to be solved,set its non-default properties through calls to the appropriate procedures,attach it to a"problem solver"object(an instance of the class in which the above routine appears: GENERAL BOUNDARY VALUE PROBLEM SOLVER),and call solve on that object. Attributes of the class,x and y,will provide the handle to the computed answer. More generally,the thorough application ofO-O techniques has a dramatic effect on See [M 1994al for argument counts.Measures on the ISE libraries,published in more detailed elsewhere, detailed library show an average number of arguments ranging from 0.4 for the Base libraries to 0.7 for measurements. the Vision graphical library.For the purposes of comparison with non-O-O libraries we should add I to all these figures,since we count two arguments for xf(a,b)versus three for its non-O-O counterpart f(x,a,b);but even so these averages are strikingly low when compared with the counts for non-O-O routines which,even when not reaching 19 as in the above numerical example,often have 5,10 or 15 arguments. These numbers are not a goal by themselves-and of course not by themselves an indicator of quality.Instead,they are largely the result of a deeper design principle that we will now examine. Operands and options An argument to a routine may be of two different kinds:operands and options. To understand the difference,consider the example of a class DOCUMEN7 and a procedure print.Assume-just to make the example more concrete-that printing will rely on Postscript.A typical call illustrating a possible interface(not compatible with the principle stated below)would be my document.print (printer name,paper size,color or not, Warning.this is not postscript_level,print resolution) the recommended style! Of the five arguments,which ones are truly indispensable?If you do not provide a Postscript level,the routine can use as a default the most commonly available option.The same applies to paper size:you can use LTR(8.5 by 11 inches)in the US,A4(21 by 29.7 centimeters)elsewhere.600 dots per square inch may be a reasonable default for the print resolution,and most printers are non-color.In all these cases,you might have a mechanism supporting installation-level or user-level defaults to override the universal ones(for example if your site has standardized on 1200 dpi resolution).The only argument that remains is the printer name;but here too you might have defined a default printer. This example illustrates the difference between operands and options: Definition:operand and option arguments An operand argument to a routine represents an object on which the routine will operate. An option argument represents a mode of operation
766 DESIGNING CLASS INTERFACES §23.2 In other words it takes no argument at all! You simply create an instance of the class GENERAL_BOUNDARY_VALUE_PROBLEM to represent the mathematical problem to be solved, set its non-default properties through calls to the appropriate procedures, attach it to a “problem solver” object (an instance of the class in which the above routine appears: GENERAL_BOUNDARY_VALUE_PROBLEM_SOLVER), and call solve on that object. Attributes of the class, x and y, will provide the handle to the computed answer. More generally, the thorough application of O-O techniques has a dramatic effect on argument counts. Measures on the ISE libraries, published in more detailed elsewhere, show an average number of arguments ranging from 0.4 for the Base libraries to 0.7 for the Vision graphical library. For the purposes of comparison with non-O-O libraries we should add 1 to all these figures, since we count two arguments for x ● f (a, b) versus three for its non-O-O counterpart f (x, a, b); but even so these averages are strikingly low when compared with the counts for non-O-O routines which, even when not reaching 19 as in the above numerical example, often have 5, 10 or 15 arguments. These numbers are not a goal by themselves — and of course not by themselves an indicator of quality. Instead, they are largely the result of a deeper design principle that we will now examine. Operands and options An argument to a routine may be of two different kinds: operands and options. To understand the difference, consider the example of a class DOCUMENT and a procedure print. Assume — just to make the example more concrete — that printing will rely on Postscript. A typical call illustrating a possible interface (not compatible with the principle stated below) would be my_document ● print (printer_name, paper_size, color_or_not, postscript_level, print_resolution) Of the five arguments, which ones are truly indispensable? If you do not provide a Postscript level, the routine can use as a default the most commonly available option. The same applies to paper size: you can use LTR (8.5 by 11 inches) in the US, A4 (21 by 29.7 centimeters) elsewhere. 600 dots per square inch may be a reasonable default for the print resolution, and most printers are non-color. In all these cases, you might have a mechanism supporting installation-level or user-level defaults to override the universal ones (for example if your site has standardized on 1200 dpi resolution). The only argument that remains is the printer name; but here too you might have defined a default printer. This example illustrates the difference between operands and options: Definition: operand and option arguments An operand argument to a routine represents an object on which the routine will operate. An option argument represents a mode of operation. See [M 1994a] for detailed library measurements. Warning: this is not the recommended style!