home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / msysjour / vol04 / 02b / project / pdesign.txt < prev    next >
Text File  |  1988-10-07  |  30KB  |  727 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.           Object-Oriented Programming in the Real World
  10.           by Zack Urlocker, The Whitewater Group
  11.  
  12.  
  13.             Introduction
  14.  
  15.             Object-oriented programming is a popular theme.  Although
  16.             object-oriented programming languages have been around for
  17.             years on expensive dedicated hardware, the availability of
  18.             efficient object-oriented languages on PCs has created a much
  19.             wider audience.
  20.             
  21.             Many articles have been published on the virtues of OOP, but
  22.             nothing demonstrates the use of encapsulation and inheritance
  23.             better than a real example.  In this article I will describe
  24.             how I designed a critical path project manager, PC-Project, in
  25.             Actor, an object-oriented language for the PC.
  26.             
  27.             The application was easier to develop and understand using an
  28.             object-oriented approach.  The development took about two and a
  29.             half man-weeks, though it was spaced out over a longer period.
  30.             This is about one half to three-quarters of the time I estimate
  31.             it would have taken me in C even though I have more experience
  32.             in C than in Actor.  In addition, there is much higher degree
  33.             of code reuse than there would have been in C.  Even the
  34.             complexities of developing a windowing user-interface were
  35.             easier to tame with an object-oriented language.
  36.             
  37.             The project manager is similar to commercially available
  38.             products such as SuperProject or MacProject, but on a smaller
  39.             scale.  It allows you to define a project as a group of related
  40.             tasks and then compute the critical path of the project, it's
  41.             total cost and so on.  The critical path is the sequence of
  42.             activities that must be completed in the scheduled time to meet
  43.             the project deadline.  Some activities that are done in
  44.             parallel may not be on the critical path, and thus they have
  45.             additional "slack time".  Since Actor is a development
  46.             environment for Microsoft Windows, PC-Project also runs under
  47.             Windows making it easy to use.
  48.                
  49.             A standalone version of PC-Project that runs under Microsoft
  50.             Windows and full source code are available for downloading from
  51.             many BBSs or directly from the author.
  52.  
  53.             What is object-oriented programming?
  54.  
  55.             Unlike traditional languages where data are separate from the
  56.             operations you perform on them, object-oriented languages tie
  57.             data and functionality into modules known as objects.  Each
  58.             object has a set of attributes (data) and operations.  Objects
  59.             include things found in traditional languages such as arrays,
  60.             strings, numbers, characters, files and so on.  Actor includes
  61.  
  62.  
  63.  
  64.   Object-Oriented Programming in the Real World 1
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.             a rich set of predefined objects such as stacks, queues, sets,
  74.             dictionaries, windows, dialog boxes and others.
  75.  
  76.             Object-oriented design
  77.  
  78.             When designing an application in a procedural language such as
  79.             C or Pascal you might begin by designing the data structures
  80.             and then determining the functions that are required.  In
  81.             object-oriented languages, the data and functionality are
  82.             combined in objects, so we don't consider the program in terms
  83.             of routines that manipulate passive data, but rather as
  84.             collection of active objects that perform operations on
  85.             themselves.  Therefore, we need to determine which objects will
  86.             make up the system.  The easiest way to do this is to develop a
  87.             logical model of the system we are trying to create.  In cases
  88.             where there is no clear logical model, we can determine the
  89.             objects based on the user interface.  For example, a
  90.             spreadsheet might include a spreadsheet window and cells as
  91.             various types of objects.
  92.             
  93.             One approach to the project manager is to have a project object
  94.             which is a network of nodes.  Each node is an activity: either
  95.             a task or a milestone.  Tasks are activities which consume time
  96.             and resources.  For example, developing software is a task that
  97.             will take time and money.  A milestone is used to mark the
  98.             completion of some tasks.  For example, you may have a
  99.             milestone to indicate that the specs are written for a product
  100.             and it is time to begin programming and writing the user
  101.             manual.  The milestone itself doesn't take any time or have any
  102.             cost; it just marks the completion of a phase in the project.
  103.             
  104.             Since our application will run under Microsoft Windows we will
  105.             also need to create a project window object that will be able
  106.             to draw a diagram of the network, handle menu commands and so
  107.             on.
  108.             
  109.             As much as possible, we want to preserve the functional
  110.             divisions found in the logical model.  For example, the project
  111.             object shouldn't be concerned with details of how the total
  112.             cost of a task is calculated; let the task worry about it!  The
  113.             only thing the project needs to know is a way of updating its
  114.             total if the cost of a task changes.
  115.             
  116.             Good object-oriented design encourages the creation of abstract
  117.             data objects that clearly define public protocol and a hide
  118.             implementation details.  Although this approach is not required
  119.             by Actor, it is strongly encouraged and can minimize
  120.             maintenance headaches.  The object-oriented design approach
  121.             requires more work "up front", but generally this pays off in
  122.             the long run by providing better encapsulation than the
  123.             traditional procedural approach.
  124.             
  125.             Figure 1 lists some of the objects that will be used in PC-
  126.             Project.
  127.  
  128.  
  129.  
  130.   Object-Oriented Programming in the Real World 2
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.             
  140.             Figure 1.  Objects in PC-Project.
  141.                
  142.                Network      a generic network of nodes with a start and end
  143.                Node         a generic node capable of connecting itself
  144.                
  145.                Project      a network that knows the critical path method
  146.                Activity     a node with an earlyStart and lateFinish
  147.                Milestone    an activity that uses no time or resources
  148.                Task         an activity that has time, resources and cost
  149.                PERTTask     a Task where the time is estimated by PERT
  150.                
  151.                Resource     used by a task; has a name and cost
  152.                
  153.                ProjWindow   a window that can display a network diagram
  154.                GanttWindow  a window that can display a Gantt chart
  155.  
  156.             Messages and methods
  157.  
  158.             An object is capable of responding to messages that are sent to
  159.             it by other objects, by itself, or by the system.  For example,
  160.             you can print an object by sending it a print message.
  161.             Examples of message sends are shown in Figure 2.
  162.             
  163.             Figure 2.  Examples of message sends.
  164.                
  165.                print(A);                /* all objects know how to print */
  166.                draw(rect, hDC);         /* draw a rect in the display */
  167.                cost := calcCost(P);     /* whatever P is, get its cost */
  168.                reSize(aWindow, wp, lp); /* the system tells us to resize */
  169.                          
  170.             Message sends are similar to function calls in procedural
  171.             languages.  However, there are some important differences.
  172.             First of all, the first parameter is the receiver of the
  173.             message; the other parameters are arguments.  Secondly, the
  174.             message makes no assumptions about the "type" of the receiver
  175.             or the arguments; they are simply objects.  This provides us
  176.             with a great deal of flexibility since we can have different
  177.             objects respond to the same message in their own way.  This is
  178.             known as polymorphism, meaning "many behaviors".  For example,
  179.             we might have a calcCost message that can be sent to either a
  180.             project or a task and it will calculate the cost using the
  181.             appropriate algorithm.
  182.             
  183.             The implementation of a message for a class of object is called
  184.             a method, and it corresponds to a function definition in a
  185.             procedural language.  Figure 3 shows a sample method definition
  186.             for calcCost for the Task class.
  187.             
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.   Object-Oriented Programming in the Real World 3
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.             Figure 3.  A sample method definition.
  206.                
  207.                /* This method defines the calcCost message for the Task
  208.                   class. Self refers to the task that receives the message.
  209.                   Time, resources and cost are instance variables defined
  210.                   for the Tasks.
  211.                   The cost is the fixedCost plus the variable cost of each
  212.                   resource used by the task.  If the cost changes, send a
  213.                   message to the network to update its cost. */
  214.                Def  calcCost(self | oldCost)
  215.                {
  216.                  oldCost := cost;      /* store our old cost as a temp */
  217.                  cost := fixedCost;    /* starting value for the cost */
  218.                
  219.                  do(resources,         /* loop through the resources */
  220.                    {using(res)         /* res is the loop variable */
  221.                     cost := cost + getFixedCost(res)
  222.                             + getTime(self) * getVariableCost(res);
  223.                  });
  224.                  if cost <> oldCost then
  225.                    updateCost(network, cost - oldCost);
  226.                  endif;
  227.                  ^cost;                /* return the new cost */
  228.                }
  229.                
  230.             Actor source code looks a lot like Pascal or C.  Most
  231.             programmers find this makes learning Actor quite easy.  The
  232.             method definition begins with the Def keyword followed by the
  233.             name of the method, the receiver (always referred to as self),
  234.             any arguments, and after a vertical bar, any local variables.
  235.             
  236.             The do message is defined for all collection classes and allows
  237.             us to loop through the resources array referring to each
  238.             element of the array in turn.  If later we decide that the
  239.             resources should be implemented as a lookup table or a set,
  240.             calcCost will still work correctly since all of these
  241.             collections understand a do message.
  242.             
  243.             Actor is an interactive environment and so as soon as we write
  244.             a method we can test it out.  Methods are written in an editor
  245.             known as a browser.  As soon as a method is written, it is
  246.             immediately compiled and can be tested.
  247.  
  248.             Classes of objects
  249.  
  250.             Every object belongs to a class.  A class is like a data type
  251.             in procedural languages, but much more powerful.  Classes
  252.             define the data that make up objects and the messages that
  253.             objects understand.  For example, a stack class (actually
  254.             called OrderedCollection) includes instance variables
  255.             firstElement and lastElement and responds to messages such as
  256.             push and pop.  Instance variables are attributes of an object
  257.             and are like fields in a record structure in C or Pascal.  We
  258.             can create new classes and define methods using the browser.
  259.  
  260.  
  261.  
  262.   Object-Oriented Programming in the Real World 4
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.             
  272.             Rather than access the private instance variables of a class
  273.             directly using the "dot notation" (e.g. stack.firstElement) we
  274.             will encapsulate the data by using messages only.  That way we
  275.             reduce the dependencies on the implementation.
  276.             
  277.             For example, if we need to get the time required of an activity
  278.             x we should send a getTime(x) message rather than refer
  279.             directly to the instance variable x.time.  It doesn matter if x
  280.             is a Milestone, a Task, a PERTTask or even a Project; as long
  281.             as it knows how to respond to a getTime message.  This can make
  282.             the critical path recalculation algorithm easier to write since
  283.             we eliminate special cases for Milestones; we just define it's
  284.             getTime method to return zero.  This results in significant
  285.             code savings.
  286.  
  287.             Inheritance
  288.  
  289.             What really makes classes powerful is the use of inheritance.
  290.             We can create descendant classes that build upon the
  291.             functionality already found in the system.  For example, the
  292.             OrderedCollection class mentioned earlier descends from the
  293.             Array class.  Similarly, we can create descendants of classes
  294.             like Window and Dialog to build the user interface to PC-
  295.             Project without having to know the more than 400 Microsoft
  296.             Windows function calls.  Automatically our descendant classes
  297.             will include much of the generic windowing behavior that users
  298.             expect in a Windows application.
  299.             
  300.             Inheritance allows us to re-use code in cases where something
  301.             is "a little different" from an object that already exists.
  302.             For example, I defined the classes Network and Node to provide
  303.             generic network connection capabilities.  We can create a new
  304.             network, connect some nodes to it, disconnect nodes and so on.
  305.             Not very exciting, but it enables us to factor out a fairly
  306.             easy part of the application, test it and then forget about it.
  307.             Since there is nothing inherantly network-like or node-like in
  308.             the system already, these classes descend directly from class
  309.             Object.
  310.             
  311.             Rather than keep track of the connections in the network, the
  312.             node themselves maintain a set of input nodes and output nodes
  313.             as instance variables inputs and outputs.  The network, then,
  314.             just has to maintain the start and end nodes.  The network is
  315.             actually implemented as a doubly-linked list, but this will be
  316.             considered "private" information and will be encapsulated with
  317.             connect and disconnect methods.
  318.             
  319.             The connect method is implemented as addOutputs and addInputs
  320.             messages since both the nodes must know about the connection
  321.             that is made.  These two steps could easily have been done in
  322.             the connect method, but by having each node manage its own
  323.             instance variables we are more flexible and can accomodate
  324.             networks made up of other networks more easily.
  325.  
  326.  
  327.  
  328.   Object-Oriented Programming in the Real World 5
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.             
  338.             Figure 4.  The connect method.
  339.             
  340.             Public protocol :
  341.                
  342.                connect(N1, N2);     /* e.g. N1 -> N2 */
  343.                
  344.             Private implementation:
  345.                
  346.                /* Connect self->aNode by updating self's outputs and
  347.                   aNode's inputs. Also, aNode should know it's network
  348.                   and the position should be set. */
  349.                Def  connect(self, aNode)
  350.                { addInputs(aNode, self);
  351.                  addOutputs(self, aNode);
  352.                  setNetwork(aNode, network);
  353.                  setPosn(aNode, self);
  354.                }
  355.                
  356.                /* To connect node n1->n2, n1's outputs must contain n2. */
  357.                Def  addOutputs(self, aNode)
  358.                { add(outputs, aNode);
  359.                }
  360.                
  361.                /* To connect n1->n2, n2's inputs must contain n1. */
  362.                Def  addInputs(self, aNode)
  363.                { add(inputs, aNode);
  364.                }
  365.             
  366.             The setPosn method is another example of private protocol.
  367.             This method is used to update the onscreen position of the
  368.             node.  The disconnect method is written in a similar fashion.
  369.  
  370.             Interactive tools
  371.  
  372.             Once we've defined the network and node classes we can create a
  373.             network and examine it to make sure it's as we expect.  Actor
  374.             encourages an interactive programming style and has a rich set
  375.             of tools to make it easy to test and debug code one piece at a
  376.             time.
  377.             
  378.             If any of our methods should cause a runtime error a source
  379.             code debugger will automatically pop up and we can fix the code
  380.             on the fly and then resume execution.  Any time we want to
  381.             examine an object we can call up an inspector and see the
  382.             values of the instance variables.  We can also inspect the
  383.             instance variables of an object from within another inspector
  384.             and trace through an entire network easily.
  385.             
  386.             Actor also has a code profiler that can be used to determine
  387.             which methods are executed most to aid us in optimizing our
  388.             application.  We can then selectively use early binding
  389.             techniques to speed up critical code by eliminating the message
  390.  
  391.  
  392.  
  393.  
  394.   Object-Oriented Programming in the Real World 6
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401.  
  402.  
  403.             send overhead.  With the proper use of early binding in
  404.             critical areas we can improve performance by about 25 to 30%.
  405.  
  406.             The project manager classes
  407.  
  408.             Once we're sure that our generic Network and Node classes are
  409.             working properly we can define the Project, Activity and other
  410.             classes listed in Figure 1.
  411.             
  412.             The Project class will descend directly from the Network class
  413.             and include additional functionality related to the
  414.             application.  For example, we need to be able to recalculate
  415.             the critical path of the project.
  416.             
  417.             The Activity class is a formal class that will group behavior
  418.             that is common between its descendants Milestone and Task.  We
  419.             won't ever create Activities themselves (except for testing),
  420.             instead they will always be either a Milestone or a Task.
  421.             Alternatively, we could have had Task descend from Milestone,
  422.             but then we would end up inheriting inappropriate behavior.
  423.             
  424.             Similarly, we could define a class called PERTTask that
  425.             descends from Task that uses the Project Evaluation and Review
  426.             Technique (PERT) for estimating time.  The only methods we
  427.             would have to write would be those related to calculating time;
  428.             everything else would be inherited.
  429.             
  430.             By using inheritance we reduce the amount of code we need to
  431.             write and the amount of testing that needs to be done.  Once a
  432.             method is written for the Activity class we know it will work
  433.             for any descendant classes.  Figure 6 shows a class tree of the
  434.             classes in PC-Project.  Note that all classes descend from the
  435.             Object class.
  436.             
  437.             Figure 6. Class tree diagram.
  438.             
  439.                                            Object
  440.                                              |
  441.                   -----------------------------------------------------
  442.                   |          |               |            |           |
  443.                 Window     Dialog          Network       Node     Resource
  444.                   |          |               |            |
  445.                ProjWindow  PDialog        Project     Activity
  446.                              |                            |
  447.                          MStoneDialog                  --------
  448.                              |                         |      |
  449.                          TaskDialog               Milestone Task
  450.                              |                                |
  451.                          PERTDialog                         PERTTask
  452.  
  453.  
  454.  
  455.  
  456.  
  457.  
  458.  
  459.  
  460.   Object-Oriented Programming in the Real World 7
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.             The recalc algorithm
  470.  
  471.             The user can set automatic recalculation to recalculate the
  472.             critical path any time a change is made; or alternatively he or
  473.             she turn off automatic recalculation, make several changes, and
  474.             then recalculate the entire network.
  475.             
  476.             The critical path recalc algorithm must make two passes: a
  477.             forward pass, recalc1, is used to compute the earlyStart times
  478.             of activities; a backward pass, recalc2, computes the
  479.             lateFinish time and the slack.  When an activity's time
  480.             changes, it sends a recalc1 message forward to all of its
  481.             outputs, who send it to their outputs and so on.
  482.             
  483.             Like today's popular spreadsheets, the project manager uses a
  484.             minimal recalc algorithm so that only parts of the project that
  485.             actually change are recalculated.  The recalc1 message is only
  486.             passed on if the earlyStart or time changes, otherwise the
  487.             outputs don't need to be recalculated.
  488.             
  489.             Figure 7 shows the critical path recalculation algorithm.
  490.             
  491.             Figure 7.  The critical path recalculation algorithm.
  492.             
  493.             Network class methods:
  494.                
  495.                /* Recalculate the entire network. Invalidate all nodes
  496.                   so that a full recalc will be forced.  Then do both the
  497.                   forward and backwards recalc passes. The forward pass
  498.                   must be completed before doing the backwards pass. */
  499.                Def  recalc(self)
  500.                { invalidate(start, new(Set,10)); /* clear all nodes */
  501.                  cost := 0;                      /* recalc cost */
  502.                  recalc1(start, true, true);     /* force entire recalc */
  503.                  recalc2(self);                  /* backwards pass */
  504.                }
  505.                
  506.                /* Do the backwards pass only starting from the end node.
  507.                   The end node is always critical. */
  508.                Def  recalc2(self)
  509.                { recalc2(end);
  510.                  end.critical := true;
  511.                }
  512.                
  513.  
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521.  
  522.  
  523.  
  524.  
  525.  
  526.   Object-Oriented Programming in the Real World 8
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.             Activity class methods:
  536.                
  537.                /* Recalculate the network from this node onwards.
  538.                   This requires forcing a forward recalc1 and a
  539.                   backwards recalc2 from the end of the net. */
  540.                Def  recalc(self)
  541.                { recalc1(self,true,nil);  /* force forward recalc1 */
  542.                  recalc2(network);        /* do backwards recalc2 */
  543.                }
  544.                
  545.                /* Recalculate an activity's earlyStart.  If the user has
  546.                   set an earlyStart, use it instead of recalculating.
  547.                   Send a message to the node's outputs to recalculate only
  548.                   if a forced recalc is required, or if earlyStart changes.
  549.                
  550.                   formula:  ES = max(ES(i) + time(i)) for all inputs i
  551.                
  552.                   arguments:
  553.                     timeChanged:  force a recalc1 if the time has changed
  554.                     costRequired: force a calcCost(self) if true
  555.                */
  556.                Def  recalc1(self, timeChanged, costRequired |oldEarlyStart)
  557.                { if (costRequired)
  558.                    calcCost(self);
  559.                  endif;
  560.                
  561.                  oldEarlyStart := earlyStart;         /* temporary */
  562.                  if (userEarlyStart)                  /* user over ride */
  563.                    earlyStart := userEarlyStart;
  564.                  else
  565.                    earlyStart := 0;                   /* find largest ES */
  566.                    do(inputs,
  567.                      {using(input)
  568.                       earlyStart := max(earlyStart,
  569.                                   getEarlyStart(input) + getTime(input));
  570.                    });
  571.                  endif;
  572.                
  573.                  /* Recalculate outputs if earlyStart changed OR if time
  574.                     changed.  Don't force it to beyond the next level. */
  575.                  if timeChanged or (earlyStart <> oldEarlyStart)
  576.                    do(outputs,
  577.                      {using(output) recalc1(output, nil, costRequired);
  578.                    });
  579.                  endif;
  580.                }
  581.                
  582.  
  583.  
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591.  
  592.   Object-Oriented Programming in the Real World 9
  593.  
  594.  
  595.  
  596.  
  597.  
  598.  
  599.  
  600.  
  601.                /* Recalculate an activity's lateFinish then its slack and
  602.                   determine if its critical.
  603.                   If the user has set a lateFinish, use it instead of
  604.                   recalculating.  LateFinish recalc goes backwards from a
  605.                   node to its inputs.  Always propogate recalc2 since a
  606.                   change to the time of a node will not change lateFinish,
  607.                   but it can change slack and critical, which are only
  608.                   known on the backwards pass.
  609.                
  610.                   formula:  LF = min(LF(i) - time(i)) for all outputs i
  611.                */
  612.                Def  recalc2(self)
  613.                {
  614.                  if userLateFinish
  615.                    lateFinish := userLateFinish;  /* user override */
  616.                  else
  617.                    lateFinish := MAXINT;          /* find smallest LF */
  618.                    do(outputs,
  619.                      {using(output)
  620.                      lateFinish := min(lateFinish,
  621.                                   getLateFinish(output) - getTime(output));
  622.                    });
  623.                  endif;
  624.                
  625.                  calcSlack(self);
  626.                  calcCritical(self);
  627.                
  628.                  /* Continue sending the recalc2 message. */
  629.                  do(inputs,
  630.                    {using(input)
  631.                     recalc2(input);
  632.                  });
  633.                }
  634.  
  635.             Windows objects
  636.  
  637.             Ultimately we'd like our project manager to run in a window
  638.             with pulldown menus and have some sort of graphical
  639.             representation of the project.  Since Actor is a Microsoft
  640.             Windows development environment, this is quite easy.
  641.             
  642.             Microsoft Windows is somewhat object-oriented itself.  When the
  643.             user clicks on the mouse or presses a key, MS-Windows will send
  644.             a message to the appropriate window.  Because of the object-
  645.             oriented approach, programming Microsoft Windows is much easier
  646.             in Actor than in a procedural language like C.  Actor has
  647.             predefined classes for windows, dialog boxes, scroll bars etc.
  648.             
  649.             We will define a new window class, called ProjWindow, which
  650.             inherits the "default" behavior needed in Windows applications.
  651.             Note that ProjWindow and Project are unrelated in the class
  652.             tree.  ProjWindow does not descend from Project since it is not
  653.             project-like.  Rather, a ProjWindow simply contains a project
  654.             object as an instance variable.
  655.  
  656.  
  657.  
  658.   Object-Oriented Programming in the Real World 10
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.  
  666.  
  667.             
  668.             By having two distinct classes we clearly separate the user
  669.             interface issues from the recalculating of the critical path
  670.             handled by the project and its activities.  This makes it easy
  671.             for us to have different types of windows display projects in
  672.             different ways.  Our ProjWindow can display a network diagram
  673.             of the activities while a GanttWindow will display a time line
  674.             graph known as a Gantt chart.  In both cases, the windows will
  675.             send messages to the project to select particular activities,
  676.             edit them, recalculate the critical path and so on.
  677.  
  678.             Conclusion
  679.  
  680.             Object-oriented programming encourages the creation of abstract
  681.             data objects with a well-defined public protocol and a private
  682.             implementation.  The result is a program that is easier to
  683.             develop and maintain.  PC-Project is a good demonstration of
  684.             object-oriented principles put into action.  And with it's
  685.             graphical user-interface, fast recalculation, file load and
  686.             save capabilities, it's a useful application in itself.
  687.  
  688.             About the author
  689.  
  690.             Zack Urlocker is Manager of Developer Relations at The
  691.             Whitewater Group, the developers of Actor.  Urlocker has a
  692.             master's degree in Computer Science from the University of
  693.             Waterloo, Ontario, Canada where his research was in the area
  694.             programming environments.  PC-Project and source code are
  695.             available on disk from the author for $5 shipping to the United
  696.             States; $10 elsewhere.
  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.   Object-Oriented Programming in the Real World 11
  725.  
  726.  
  727.