Copyright (C) 1997 Rich Neidinger, John Swallow, and Todd Will.  Free for
distribution to college and university instructors for personal,
non-commercial use.  If these notebooks are used in a course, the authors
request $20 per student.

24. Object-Oriented Programming

Last revision:  October 16 1996

We continue the study of contexts and packages in the context of
object-oriented programming, the theme of this section.

Basic Object-Oriented Programming

Object-oriented programming refers to a particular style of programming in
which programs are written in a way which permits simple, planned
expansions and additions by later programmers.  The governing principle is
that some functions (the "public" functions) defined by one programmer are
meant to be used by further programmers, while other functions and data
(the "private" ones) are not meant to be used by someone else, since the
functions do very specific tasks with little general value.  This style of
programming also encourages programmers to bind together functions and data
into a structure called an object; this structure encapsulates all "public"
and "private" data and functions and controls access to any of the
functions or data by external agents.  (Object-oriented programming
languages, such as C++, also provide ways for programmers to use functions
without knowing their exact names, using a programming technique called
"polymorphism", but this technique is beyond the scope of this course.)
While Mathematica is not an object-oriented programming language per se, we
can mimic part of this style of programming by using contexts and packages.
We will not form true objects in the sense of object-oriented
programming---you should take a course in object-oriented programming to do
so---but we will consider the separation of functions and data into the
public and the private.

The method we will use is to decide, first, what data and functions should
be "public", common property of any user or programmer using our code. All
other data and functions will be deemed "private", and we will enforce this
distinction by placing private data and functions in a special context.  We
will have two contexts, then, one overarching context which will contain
all of our data and functions associated to a particular programming
problem, and one context within the first, a private context, which
contains those functions and data which we want to prevent others from
modifying.

Example:  A Team Roster

As an example, we'll return to our team roster problem.  Recall the
following solution to the problem of keeping a list of names, together with
the total number of individuals on the team.

Clear[sportsNames,totalNames,addName,deleteName]
sportsNames = {};
totalNames = 0;
addName[s_] :=
                        Module[{},
             (* uses sportsNames, totalNames *)
             sportsNames = Union[sportsNames,{s}];
             totalNames = Length[sportsNames];
          ]
deleteName[s_] :=
          Module[{},
                  (* uses sportsNames, totalNames *)
                  sportsNames = Complement[sportsNames,{s}];
                  totalNames = Length[sportsNames];
          ]

addName["Magic"]
addName["Shaq"]
totalNames

How could we improve on this solution?  First, we notice that while we
would want others to use the functions "addName[ ]" and "deleteName[ ]", we
would not  want others to have the ability to change "sportsNames" or
"totalNames" independently, if only because they might add a name and
forget to increment "totalNames".  Hence we'll need to make "sportsNames"
and "totalNames" private data, while "addName" and "deleteName" can be
public functions.  Similarly, since we don't want others to initialize our
data themselves, we'll provide a public function "sportsInit[ ]" which they
can execute, in which we'll perform the initialization.  Finally, we'll
provide a way for a user to request the number of names on the roster,
using a public function "getNumber[ ]", and a way fora user to request the
roster list, using the public function "getPlayerList[ ]".

Before we get started, let's remove all previous definitions so that we
don't leave any names in the context "Global`".

Remove[addName,deleteName,sportsNames,totalNames]

Below we'll give a package solution, followed by an analysis of the details
of its construction.

BeginPackage["Sports`"];

sportsInit::usage = "sportsInit[] clears the current team roster";

addName::usage = "addName[string] adds a name to the team roster";

deleteName::usage = "deleteName[string] deletes a name from the team roster";

getNumber::usage = "getNumber[] returns the number of players currently on
the team roster";

getPlayerList::usage = "getPlayerList[] returns a list of the players on
the team roster";

Begin["Sports`Private`"];

sportsInit[] := (sportsNames = {}; totalNames = 0;);
addName[s_String] :=
                        Module[{},
             (* uses sportsNames, totalNames *)
             sportsNames = Union[sportsNames,{s}];
             totalNames = Length[sportsNames];
          ];
deleteName[s_String] :=
          Module[{},
                  (* uses sportsNames, totalNames *)
                  sportsNames = Complement[sportsNames,{s}];
                  totalNames = Length[sportsNames];
          ];
getNumber[] := totalNames;
getPlayerList[] := sportsNames;

sportsInit[];

End[];

EndPackage[];

Let's walk through this solution.  First, using "BeginPackage[ ]", we set
up a separate context for our functions and data, so that we won't damage
any other functions or data with the same names.  At the end, "EndPackage[
]" will add "Sports`" to the context search path, so we'll have access to
any functions and data in the context "Sports`".  We then provide usage
lines for each public function, a very important step.  These usage lines
important because they mention each name which we'll want to be public,
thereby establishing those names as names inside the context "Sports`".
Next, we enter a new context, "Sports`Private`".  At this stage any new
names declared will be in this separate context, while any names already
declared will still be public, in "Sports`".  The effect is that
"sportsNames" is a private name, with the long form
"Sports`Private`sportsNames", while "getNumber[ ]" is a public name, with
the long form "Sports`getNumber[ ]".  We make the requisite definitions,
and then execute "sportsInit[ ]", so the initialization will happen whether
or not the user remembers explicitly to do so.  Then "End[ ]" takes us out
of the context "Sports`Private`", without adding that context to the
context search path, and, finally, "EndPackage[ ]" ends the context
"Sports`", adding "Sports`" to the context search path.

Let's examine how our package works.

getNumber[]

addName["Shaq"]

getPlayerList[]

getNumber[]

Now suppose we want to alter the private data.  We can't seem to get to it!

totalNames

sportsNames

Why is this?  Because the names are part of "Sports`Private`", which is not
in the context search path.  It is true that by specifying
"Sports`Private`totalNames", we can get to the actual definition, and
therefore change the value, but this causes an appropriate burden on the
new programmer.  If the package is to be circumvented in its intent, there
are still ways to alter the data directly, but the new programmer will have
to go to relatively great lengths to do so.  (Incidentally, Mathematica
provides certain attributes, such as "Protected", "ReadProtected", and
"Locked", which prevent changes to definition, but we won't use them, under
the premise that security levels need not be high in our environment!)

Designing Programs Using Packages

Object-oriented concepts, implemented using packages, are therefore
desirable components of any Mathematica code.  These object-oriented
concepts dovetail nicely with bottom-up analysis, so in designing a
package, it is best to attempt both.  Decide what data, and what functions
performing simple operations on this data, will be needed before you treat
the larger problem.  While you do so, decide which functions and data
should be private and which should be public.

The standard format of code for packages is as follows.  First, enter a
"BeginPackage[ ]" statement.  Then, mention each and every name which you
will want to be public in your package.  One way to do so is to provide
usage lines for each and every item of data and function which the users
will use; another way is just to enter the name followed by a semicolon.
Next, use a "Begin[ ]" statement with a special context name, usually
including the name "Private`".  Proceed to enter the definition of each
function, public or not, keeping in mind that all new names will be part of
the private context.  Finally, exit the private context using "End[ ]", and
finish the package with "EndPackage[ ]".  To begin, mimic the example
above.

IMPORTANT: Realize that in programming a package, the order of
execution---and the separation of some lines into their own cells, such as
"BeginPackage[ ]", for example---is crucial.  If you write a package,
execute its cells, and then go back to make changes, simply executing the
cells you've changed may not perform the operation you desire.  It may be
best to execute "Remove["*"]", which removes all user-defined definitions
in the "Global`" context, and then "Remove["Context`*"]," substituting for
"Context" each of the contexts you are using, separately.  Finally,
re-execute each cell, in order.  At this point you will be in a good
position to debug your program.  (At worst, you may need to save your file,
restart Mathematica, and read the notebook back in.)

Another Example

We wish to design a package which will keep track of many grades of equal
weight in a class.  The package should have functions which permit (a)
initialization, (b) entering in a grade, (c) removing a grade, (d) printing
out current grades, (e) computing the current average, and (f) determining,
given the grades already entered, what average grade is necessary on the
remaining assignments in order that the final average be a 60, 70, 80, or a
90.  Just as in the example above, we will have private data:  the list of
grades.  We will also have public functions, namely, "gradeInit[ ]",
"addGrade[ ]", "deleteGrade[ ]", "listGrades[ ]", "findAverage[ ]", and
"whatDoINeed[ ]".

BeginPackage["Grades`"];

gradeInit::usage = "gradeInit[] sets the number of current grades to 0";

addGrade::usage = "addGrade[n] adds a numeric grade n to the list of grades";

deleteGrade::usage = "deleteGrade[n] deletes a numeric grade n from the list";

listGrades::usage = "listGrades[] lists the grades currently entered";

findAverage::usage = "findAverage[] determines the average of the current
grades";

whatDoINeed::usage = "whatDoINeed[n] determines, given n, the total number
of grades in the course, the grades necessary to end the course with an
average of 60, 70, 80, and 90";

Begin["Grades`Private`"];

gradeInit[] := (grades={};);

addGrade[n_] := (grades = Append[grades,n];);

deleteGrade[n_] := Module[{a},
                        a = Position[grades,n];
                        If[Length[a]>=1,
                           grades = Delete[grades,Take[a,1]],
                           Print["Grade not found\n"]];
                        ];
                        
listGrades[] := grades;

findAverage[] := If[Length[grades]>0,
                 Apply[Plus,grades]/Length[grades],
                 Print["No grades\n"]];
                
whatDoINeed[n_] := Module[{a,b,c},
                        Which[
                           Length[grades]<1,
                              {60,70,80,90},
                           Length[grades]<n,
                b[c_] := (c*n-Length[grades]*findAverage[])/
                   (n-Length[grades]);
                Map[b,{60.0,70.0,80.0,90.0}],
             Length[grades]>=n,
             Print["Final average already determined: ",
                findAverage[]];
           ]];
        
gradeInit[];

End[];

EndPackage[];

Here are some sample user commands:

addGrade[90]; addGrade[90]; addGrade[60];
whatDoINeed[4]

The student cannot attain a 90 average, given one more grade, but could
attain an 80 average by earning an 80 on the next test.