Steven S. Skiena
Why Objects are Good Things
Modules provide a logical grouping of procedures on a related topic.
Objects provide a logical grouping of data and associated operations.
The emphasis of modules is on procedures; the emphasis of objects is on data. Modules are verbs followed by nouns: Push(S,x), while objects are nouns followed by verbs: S.Push(x).
This provides only an alternate notation for dealing with things, but different notations can sometimes make it easier to understand things - the history of Calculus is an example.
Objects do a great job of encapsulating the data items within, because the only access to them is through the methods, or associated procedures.
MODULE StackObj EXPORTS Main; (*24.01.95. LB*) (* Stack implemented as object type. *) IMPORT SIO; TYPE ET = INTEGER; (*Type of elements*) Stack = OBJECT top: Node := NIL; (*points to stack*) METHODS push(elem:ET):= Push; (*Push implements push*) pop() :ET:= Pop; (*Pop implements pop*) empty(): BOOLEAN:= Empty; (*Empty implements empty*) END; (*Stack*) Node = REF RECORD info: ET; (*Stands for any information*) next: Node (*Points to the next node in the stack *) END; (*Node*) PROCEDURE Push(stack: Stack; elem:ET) = (*stack: receiver object (self)*) VAR new: Node := NEW(Node, info:= elem); (*Element instantiate*) BEGIN new.next:= stack.top; stack.top:= new; (*new element added to top*) END Push; PROCEDURE Pop(stack: Stack): ET = (*stack: receiver object (self)*) VAR first: ET; BEGIN first:= stack.top.info; (*Info copied from first element*) stack.top:= stack.top.next; (*first element removed*) RETURN first END Pop; PROCEDURE Empty(stack: Stack): BOOLEAN = (*stack: receiver object (self)*) BEGIN RETURN stack.top = NIL END Empty; VAR stack1, stack2: Stack := NEW(Stack); (*2 stack objects created*) i1, i2: INTEGER; BEGIN stack1.push(2); (*2 pushed onto stack1*) stack2.push(6); (*6 pushed onto stack2*) i1:= stack1.pop(); (*pop element from stack1*) i2:= stack2.pop(); (*pop element from stack2*) SIO.PutInt(i1); SIO.PutInt(i2); SIO.Nl(); END StackObj.
Object-oriented programming is a popular, recent way of thinking about program organization.
OOP is typically characterized by three major ideas:
When we define an object type (class), we can specify that it be derived from (subtype to) another class. For example, we can specialize the Stack object into a GarbageCan:
TYPE GarbageCan = Stack OBJECT OVERRIDES pop():= Yech; (* Remove something from can?? *) dump():= RemoveAll; (* Discard everything from can *) END; (*GarbageCan*)
The GarbageCan type is a form of stack (you can still push in it the same way), but we have modified the pop and dump methods.
This subtype-supertype relation defines a hierarchy (rooted tree) of classes. The appropriate method for a given object is determined at run time (dynamic binding) according to the first class at or above the current class to define the method.
OOP and the Calculator Program
How might object-oriented programming ideas have helped in writing the calculator program?
Many of you noticed that the linked stack type was similar to the long integer type, and wanted to reuse the code from one in another.
The following type hierarchy shows one way we could have exploited this, by creating special stack methods push and pop, and overwriting the add and subtract methods for general long-integers.
Philosophical issue: should Long-Integer be a subtype of Positive-Long-Integer or visa versa?
Why didn't I urge you to do it this way? In my opinion, the complexity of mastering and using the OOP features of Modula-3 would very much overwhelm the code savings from such a small program. Object-oriented features differ significantly from language to language, but the basic principles outlined here are fairly common.
However, you should see why inheritance can be a big win in organizing larger programs.