home *** CD-ROM | disk | FTP | other *** search
-
-
-
-
-
-
-
-
- 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
-
-
-