







          Object-Oriented Programming in the Real World
          by Zack Urlocker, The Whitewater Group


            Introduction

            Object-oriented programming is a popular theme.  Although
            object-oriented programming languages have been around for
            years on expensive dedicated hardware, the availability of
            efficient object-oriented languages on PCs has created a much
            wider audience.
            
            Many articles have been published on the virtues of OOP, but
            nothing demonstrates the use of encapsulation and inheritance
            better than a real example.  In this article I will describe
            how I designed a critical path project manager, PC-Project, in
            Actor, an object-oriented language for the PC.
            
            The application was easier to develop and understand using an
            object-oriented approach.  The development took about two and a
            half man-weeks, though it was spaced out over a longer period.
            This is about one half to three-quarters of the time I estimate
            it would have taken me in C even though I have more experience
            in C than in Actor.  In addition, there is much higher degree
            of code reuse than there would have been in C.  Even the
            complexities of developing a windowing user-interface were
            easier to tame with an object-oriented language.
            
            The project manager is similar to commercially available
            products such as SuperProject or MacProject, but on a smaller
            scale.  It allows you to define a project as a group of related
            tasks and then compute the critical path of the project, it's
            total cost and so on.  The critical path is the sequence of
            activities that must be completed in the scheduled time to meet
            the project deadline.  Some activities that are done in
            parallel may not be on the critical path, and thus they have
            additional "slack time".  Since Actor is a development
            environment for Microsoft Windows, PC-Project also runs under
            Windows making it easy to use.
               
            A standalone version of PC-Project that runs under Microsoft
            Windows and full source code are available for downloading from
            many BBSs or directly from the author.

            What is object-oriented programming?

            Unlike traditional languages where data are separate from the
            operations you perform on them, object-oriented languages tie
            data and functionality into modules known as objects.  Each
            object has a set of attributes (data) and operations.  Objects
            include things found in traditional languages such as arrays,
            strings, numbers, characters, files and so on.  Actor includes



  Object-Oriented Programming in the Real World 1








            a rich set of predefined objects such as stacks, queues, sets,
            dictionaries, windows, dialog boxes and others.

            Object-oriented design

            When designing an application in a procedural language such as
            C or Pascal you might begin by designing the data structures
            and then determining the functions that are required.  In
            object-oriented languages, the data and functionality are
            combined in objects, so we don't consider the program in terms
            of routines that manipulate passive data, but rather as
            collection of active objects that perform operations on
            themselves.  Therefore, we need to determine which objects will
            make up the system.  The easiest way to do this is to develop a
            logical model of the system we are trying to create.  In cases
            where there is no clear logical model, we can determine the
            objects based on the user interface.  For example, a
            spreadsheet might include a spreadsheet window and cells as
            various types of objects.
            
            One approach to the project manager is to have a project object
            which is a network of nodes.  Each node is an activity: either
            a task or a milestone.  Tasks are activities which consume time
            and resources.  For example, developing software is a task that
            will take time and money.  A milestone is used to mark the
            completion of some tasks.  For example, you may have a
            milestone to indicate that the specs are written for a product
            and it is time to begin programming and writing the user
            manual.  The milestone itself doesn't take any time or have any
            cost; it just marks the completion of a phase in the project.
            
            Since our application will run under Microsoft Windows we will
            also need to create a project window object that will be able
            to draw a diagram of the network, handle menu commands and so
            on.
            
            As much as possible, we want to preserve the functional
            divisions found in the logical model.  For example, the project
            object shouldn't be concerned with details of how the total
            cost of a task is calculated; let the task worry about it!  The
            only thing the project needs to know is a way of updating its
            total if the cost of a task changes.
            
            Good object-oriented design encourages the creation of abstract
            data objects that clearly define public protocol and a hide
            implementation details.  Although this approach is not required
            by Actor, it is strongly encouraged and can minimize
            maintenance headaches.  The object-oriented design approach
            requires more work "up front", but generally this pays off in
            the long run by providing better encapsulation than the
            traditional procedural approach.
            
            Figure 1 lists some of the objects that will be used in PC-
            Project.



  Object-Oriented Programming in the Real World 2








            
            Figure 1.  Objects in PC-Project.
               
               Network      a generic network of nodes with a start and end
               Node         a generic node capable of connecting itself
               
               Project      a network that knows the critical path method
               Activity     a node with an earlyStart and lateFinish
               Milestone    an activity that uses no time or resources
               Task         an activity that has time, resources and cost
               PERTTask     a Task where the time is estimated by PERT
               
               Resource     used by a task; has a name and cost
               
               ProjWindow   a window that can display a network diagram
               GanttWindow  a window that can display a Gantt chart

            Messages and methods

            An object is capable of responding to messages that are sent to
            it by other objects, by itself, or by the system.  For example,
            you can print an object by sending it a print message.
            Examples of message sends are shown in Figure 2.
            
            Figure 2.  Examples of message sends.
               
               print(A);                /* all objects know how to print */
               draw(rect, hDC);         /* draw a rect in the display */
               cost := calcCost(P);     /* whatever P is, get its cost */
               reSize(aWindow, wp, lp); /* the system tells us to resize */
                         
            Message sends are similar to function calls in procedural
            languages.  However, there are some important differences.
            First of all, the first parameter is the receiver of the
            message; the other parameters are arguments.  Secondly, the
            message makes no assumptions about the "type" of the receiver
            or the arguments; they are simply objects.  This provides us
            with a great deal of flexibility since we can have different
            objects respond to the same message in their own way.  This is
            known as polymorphism, meaning "many behaviors".  For example,
            we might have a calcCost message that can be sent to either a
            project or a task and it will calculate the cost using the
            appropriate algorithm.
            
            The implementation of a message for a class of object is called
            a method, and it corresponds to a function definition in a
            procedural language.  Figure 3 shows a sample method definition
            for calcCost for the Task class.
            








  Object-Oriented Programming in the Real World 3








            Figure 3.  A sample method definition.
               
               /* This method defines the calcCost message for the Task
                  class. Self refers to the task that receives the message.
                  Time, resources and cost are instance variables defined
                  for the Tasks.
                  The cost is the fixedCost plus the variable cost of each
                  resource used by the task.  If the cost changes, send a
                  message to the network to update its cost. */
               Def  calcCost(self | oldCost)
               {
                 oldCost := cost;      /* store our old cost as a temp */
                 cost := fixedCost;    /* starting value for the cost */
               
                 do(resources,         /* loop through the resources */
                   {using(res)         /* res is the loop variable */
                    cost := cost + getFixedCost(res)
                            + getTime(self) * getVariableCost(res);
                 });
                 if cost <> oldCost then
                   updateCost(network, cost - oldCost);
                 endif;
                 ^cost;                /* return the new cost */
               }
               
            Actor source code looks a lot like Pascal or C.  Most
            programmers find this makes learning Actor quite easy.  The
            method definition begins with the Def keyword followed by the
            name of the method, the receiver (always referred to as self),
            any arguments, and after a vertical bar, any local variables.
            
            The do message is defined for all collection classes and allows
            us to loop through the resources array referring to each
            element of the array in turn.  If later we decide that the
            resources should be implemented as a lookup table or a set,
            calcCost will still work correctly since all of these
            collections understand a do message.
            
            Actor is an interactive environment and so as soon as we write
            a method we can test it out.  Methods are written in an editor
            known as a browser.  As soon as a method is written, it is
            immediately compiled and can be tested.

            Classes of objects

            Every object belongs to a class.  A class is like a data type
            in procedural languages, but much more powerful.  Classes
            define the data that make up objects and the messages that
            objects understand.  For example, a stack class (actually
            called OrderedCollection) includes instance variables
            firstElement and lastElement and responds to messages such as
            push and pop.  Instance variables are attributes of an object
            and are like fields in a record structure in C or Pascal.  We
            can create new classes and define methods using the browser.



  Object-Oriented Programming in the Real World 4








            
            Rather than access the private instance variables of a class
            directly using the "dot notation" (e.g. stack.firstElement) we
            will encapsulate the data by using messages only.  That way we
            reduce the dependencies on the implementation.
            
            For example, if we need to get the time required of an activity
            x we should send a getTime(x) message rather than refer
            directly to the instance variable x.time.  It doesn matter if x
            is a Milestone, a Task, a PERTTask or even a Project; as long
            as it knows how to respond to a getTime message.  This can make
            the critical path recalculation algorithm easier to write since
            we eliminate special cases for Milestones; we just define it's
            getTime method to return zero.  This results in significant
            code savings.

            Inheritance

            What really makes classes powerful is the use of inheritance.
            We can create descendant classes that build upon the
            functionality already found in the system.  For example, the
            OrderedCollection class mentioned earlier descends from the
            Array class.  Similarly, we can create descendants of classes
            like Window and Dialog to build the user interface to PC-
            Project without having to know the more than 400 Microsoft
            Windows function calls.  Automatically our descendant classes
            will include much of the generic windowing behavior that users
            expect in a Windows application.
            
            Inheritance allows us to re-use code in cases where something
            is "a little different" from an object that already exists.
            For example, I defined the classes Network and Node to provide
            generic network connection capabilities.  We can create a new
            network, connect some nodes to it, disconnect nodes and so on.
            Not very exciting, but it enables us to factor out a fairly
            easy part of the application, test it and then forget about it.
            Since there is nothing inherantly network-like or node-like in
            the system already, these classes descend directly from class
            Object.
            
            Rather than keep track of the connections in the network, the
            node themselves maintain a set of input nodes and output nodes
            as instance variables inputs and outputs.  The network, then,
            just has to maintain the start and end nodes.  The network is
            actually implemented as a doubly-linked list, but this will be
            considered "private" information and will be encapsulated with
            connect and disconnect methods.
            
            The connect method is implemented as addOutputs and addInputs
            messages since both the nodes must know about the connection
            that is made.  These two steps could easily have been done in
            the connect method, but by having each node manage its own
            instance variables we are more flexible and can accomodate
            networks made up of other networks more easily.



  Object-Oriented Programming in the Real World 5








            
            Figure 4.  The connect method.
            
            Public protocol :
               
               connect(N1, N2);     /* e.g. N1 -> N2 */
               
            Private implementation:
               
               /* Connect self->aNode by updating self's outputs and
                  aNode's inputs. Also, aNode should know it's network
                  and the position should be set. */
               Def  connect(self, aNode)
               { addInputs(aNode, self);
                 addOutputs(self, aNode);
                 setNetwork(aNode, network);
                 setPosn(aNode, self);
               }
               
               /* To connect node n1->n2, n1's outputs must contain n2. */
               Def  addOutputs(self, aNode)
               { add(outputs, aNode);
               }
               
               /* To connect n1->n2, n2's inputs must contain n1. */
               Def  addInputs(self, aNode)
               { add(inputs, aNode);
               }
            
            The setPosn method is another example of private protocol.
            This method is used to update the onscreen position of the
            node.  The disconnect method is written in a similar fashion.

            Interactive tools

            Once we've defined the network and node classes we can create a
            network and examine it to make sure it's as we expect.  Actor
            encourages an interactive programming style and has a rich set
            of tools to make it easy to test and debug code one piece at a
            time.
            
            If any of our methods should cause a runtime error a source
            code debugger will automatically pop up and we can fix the code
            on the fly and then resume execution.  Any time we want to
            examine an object we can call up an inspector and see the
            values of the instance variables.  We can also inspect the
            instance variables of an object from within another inspector
            and trace through an entire network easily.
            
            Actor also has a code profiler that can be used to determine
            which methods are executed most to aid us in optimizing our
            application.  We can then selectively use early binding
            techniques to speed up critical code by eliminating the message




  Object-Oriented Programming in the Real World 6








            send overhead.  With the proper use of early binding in
            critical areas we can improve performance by about 25 to 30%.

            The project manager classes

            Once we're sure that our generic Network and Node classes are
            working properly we can define the Project, Activity and other
            classes listed in Figure 1.
            
            The Project class will descend directly from the Network class
            and include additional functionality related to the
            application.  For example, we need to be able to recalculate
            the critical path of the project.
            
            The Activity class is a formal class that will group behavior
            that is common between its descendants Milestone and Task.  We
            won't ever create Activities themselves (except for testing),
            instead they will always be either a Milestone or a Task.
            Alternatively, we could have had Task descend from Milestone,
            but then we would end up inheriting inappropriate behavior.
            
            Similarly, we could define a class called PERTTask that
            descends from Task that uses the Project Evaluation and Review
            Technique (PERT) for estimating time.  The only methods we
            would have to write would be those related to calculating time;
            everything else would be inherited.
            
            By using inheritance we reduce the amount of code we need to
            write and the amount of testing that needs to be done.  Once a
            method is written for the Activity class we know it will work
            for any descendant classes.  Figure 6 shows a class tree of the
            classes in PC-Project.  Note that all classes descend from the
            Object class.
            
            Figure 6. Class tree diagram.
            
                                           Object
                                             |
                  -----------------------------------------------------
                  |          |               |            |           |
                Window     Dialog          Network       Node     Resource
                  |          |               |            |
               ProjWindow  PDialog        Project     Activity
                             |                            |
                         MStoneDialog                  --------
                             |                         |      |
                         TaskDialog               Milestone Task
                             |                                |
                         PERTDialog                         PERTTask








  Object-Oriented Programming in the Real World 7








            The recalc algorithm

            The user can set automatic recalculation to recalculate the
            critical path any time a change is made; or alternatively he or
            she turn off automatic recalculation, make several changes, and
            then recalculate the entire network.
            
            The critical path recalc algorithm must make two passes: a
            forward pass, recalc1, is used to compute the earlyStart times
            of activities; a backward pass, recalc2, computes the
            lateFinish time and the slack.  When an activity's time
            changes, it sends a recalc1 message forward to all of its
            outputs, who send it to their outputs and so on.
            
            Like today's popular spreadsheets, the project manager uses a
            minimal recalc algorithm so that only parts of the project that
            actually change are recalculated.  The recalc1 message is only
            passed on if the earlyStart or time changes, otherwise the
            outputs don't need to be recalculated.
            
            Figure 7 shows the critical path recalculation algorithm.
            
            Figure 7.  The critical path recalculation algorithm.
            
            Network class methods:
               
               /* Recalculate the entire network. Invalidate all nodes
                  so that a full recalc will be forced.  Then do both the
                  forward and backwards recalc passes. The forward pass
                  must be completed before doing the backwards pass. */
               Def  recalc(self)
               { invalidate(start, new(Set,10)); /* clear all nodes */
                 cost := 0;                      /* recalc cost */
                 recalc1(start, true, true);     /* force entire recalc */
                 recalc2(self);                  /* backwards pass */
               }
               
               /* Do the backwards pass only starting from the end node.
                  The end node is always critical. */
               Def  recalc2(self)
               { recalc2(end);
                 end.critical := true;
               }
               













  Object-Oriented Programming in the Real World 8








            Activity class methods:
               
               /* Recalculate the network from this node onwards.
                  This requires forcing a forward recalc1 and a
                  backwards recalc2 from the end of the net. */
               Def  recalc(self)
               { recalc1(self,true,nil);  /* force forward recalc1 */
                 recalc2(network);        /* do backwards recalc2 */
               }
               
               /* Recalculate an activity's earlyStart.  If the user has
                  set an earlyStart, use it instead of recalculating.
                  Send a message to the node's outputs to recalculate only
                  if a forced recalc is required, or if earlyStart changes.
               
                  formula:  ES = max(ES(i) + time(i)) for all inputs i
               
                  arguments:
                    timeChanged:  force a recalc1 if the time has changed
                    costRequired: force a calcCost(self) if true
               */
               Def  recalc1(self, timeChanged, costRequired |oldEarlyStart)
               { if (costRequired)
                   calcCost(self);
                 endif;
               
                 oldEarlyStart := earlyStart;         /* temporary */
                 if (userEarlyStart)                  /* user over ride */
                   earlyStart := userEarlyStart;
                 else
                   earlyStart := 0;                   /* find largest ES */
                   do(inputs,
                     {using(input)
                      earlyStart := max(earlyStart,
                                  getEarlyStart(input) + getTime(input));
                   });
                 endif;
               
                 /* Recalculate outputs if earlyStart changed OR if time
                    changed.  Don't force it to beyond the next level. */
                 if timeChanged or (earlyStart <> oldEarlyStart)
                   do(outputs,
                     {using(output) recalc1(output, nil, costRequired);
                   });
                 endif;
               }
               










  Object-Oriented Programming in the Real World 9








               /* Recalculate an activity's lateFinish then its slack and
                  determine if its critical.
                  If the user has set a lateFinish, use it instead of
                  recalculating.  LateFinish recalc goes backwards from a
                  node to its inputs.  Always propogate recalc2 since a
                  change to the time of a node will not change lateFinish,
                  but it can change slack and critical, which are only
                  known on the backwards pass.
               
                  formula:  LF = min(LF(i) - time(i)) for all outputs i
               */
               Def  recalc2(self)
               {
                 if userLateFinish
                   lateFinish := userLateFinish;  /* user override */
                 else
                   lateFinish := MAXINT;          /* find smallest LF */
                   do(outputs,
                     {using(output)
                     lateFinish := min(lateFinish,
                                  getLateFinish(output) - getTime(output));
                   });
                 endif;
               
                 calcSlack(self);
                 calcCritical(self);
               
                 /* Continue sending the recalc2 message. */
                 do(inputs,
                   {using(input)
                    recalc2(input);
                 });
               }

            Windows objects

            Ultimately we'd like our project manager to run in a window
            with pulldown menus and have some sort of graphical
            representation of the project.  Since Actor is a Microsoft
            Windows development environment, this is quite easy.
            
            Microsoft Windows is somewhat object-oriented itself.  When the
            user clicks on the mouse or presses a key, MS-Windows will send
            a message to the appropriate window.  Because of the object-
            oriented approach, programming Microsoft Windows is much easier
            in Actor than in a procedural language like C.  Actor has
            predefined classes for windows, dialog boxes, scroll bars etc.
            
            We will define a new window class, called ProjWindow, which
            inherits the "default" behavior needed in Windows applications.
            Note that ProjWindow and Project are unrelated in the class
            tree.  ProjWindow does not descend from Project since it is not
            project-like.  Rather, a ProjWindow simply contains a project
            object as an instance variable.



  Object-Oriented Programming in the Real World 10








            
            By having two distinct classes we clearly separate the user
            interface issues from the recalculating of the critical path
            handled by the project and its activities.  This makes it easy
            for us to have different types of windows display projects in
            different ways.  Our ProjWindow can display a network diagram
            of the activities while a GanttWindow will display a time line
            graph known as a Gantt chart.  In both cases, the windows will
            send messages to the project to select particular activities,
            edit them, recalculate the critical path and so on.

            Conclusion

            Object-oriented programming encourages the creation of abstract
            data objects with a well-defined public protocol and a private
            implementation.  The result is a program that is easier to
            develop and maintain.  PC-Project is a good demonstration of
            object-oriented principles put into action.  And with it's
            graphical user-interface, fast recalculation, file load and
            save capabilities, it's a useful application in itself.

            About the author

            Zack Urlocker is Manager of Developer Relations at The
            Whitewater Group, the developers of Actor.  Urlocker has a
            master's degree in Computer Science from the University of
            Waterloo, Ontario, Canada where his research was in the area
            programming environments.  PC-Project and source code are
            available on disk from the author for $5 shipping to the United
            States; $10 elsewhere.



























  Object-Oriented Programming in the Real World 11


