Dec 132017
 
PC-Project is a complete critical path project management application that runs under Microsoft Windows. PC-Project is written entirely in Actor, and includes full source code.
File PROJ0928.ZIP from The Programmer’s Corner in
Category Windows 3.X Files
PC-Project is a complete critical path project management application that runs under Microsoft Windows. PC-Project is written entirely in Actor, and includes full source code.
File Name File Size Zip Size Zip Type
ACTIVITY.CLS 10736 3216 deflated
DATE.ACT 2880 1051 deflated
DATE.CLS 5362 1727 deflated
FIX_LE.ACT 5142 1708 deflated
GANTTWIN.CLS 6349 2212 deflated
HOUSE.PRJ 2570 956 deflated
INSTALL.ACT 3831 1512 deflated
INSTALL.BAT 3440 1242 deflated
MILESTON.CLS 1187 493 deflated
MSTONEDI.CLS 1185 498 deflated
NETWORK.CLS 4916 1611 deflated
NODE.CLS 5779 1748 deflated
PDESIGN.TXT 30483 8283 deflated
PDIALOG.CLS 1945 775 deflated
PERT.PRJ 2596 930 deflated
PERTDIAL.CLS 1140 463 deflated
PERTTASK.CLS 2524 899 deflated
PMANUAL.TXT 38495 10147 deflated
PRINTER.ACT 1451 661 deflated
PRINTER.CLS 5250 1670 deflated
PROJ.ACT 3157 1360 deflated
PROJ.H 3678 1085 deflated
PROJ.ICO 1038 209 deflated
PROJ.LOD 2421 804 deflated
PROJ.RC 38435 8435 deflated
PROJAPP.ACT 2256 977 deflated
PROJDIAL.CLS 897 445 deflated
PROJECT.CLS 7240 2315 deflated
PROJECT.EXE 87664 34265 deflated
PROJECT.IMA 169800 82284 deflated
PROJECT.RC 22998 4552 deflated
PROJSEAL.LOD 2376 744 deflated
PROJWIND.CLS 23323 6258 deflated
PWSETDIA.CLS 1820 713 deflated
READPROJ.TX 6016 2430 deflated
READPROJ.TXT 5908 2425 deflated
RESDIALO.CLS 987 444 deflated
RESOURCE.CLS 4827 1528 deflated
TASK.CLS 4929 1677 deflated
TASKDIAL.CLS 1060 452 deflated
TEST.PRJ 1887 727 deflated
TEXTPRIN.CLS 3233 1165 deflated
WORK.ICO 1038 116 deflated

Download File PROJ0928.ZIP Here

Contents of the PDESIGN.TXT file










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




 December 13, 2017  Add comments

Leave a Reply