Overview: 3.1 Object-Oriented Programming: Objects, Messages, and Methods

Up: GEOS SDK TechDocs | Up | Prev: 3 Object-Oriented Programming | Next: 3.2 Classes and Inheritance

Procedural programming uses routines that act with globally-accessible or locally-defined data. Those routines must know about other routines in the program and must understand the data structure and organization used by other routines. Although some amount of isolation is applied to each routine, in essence they are all part of the greater whole of the program.

Objects, in their simplest sense, consist of data ( instance data ) and routines ( methods ) that act on that data. Objects may interact with each other but may not alter other objects' instance data; this rule leads to strict modularity and cleaner program design. Objects do not need to understand how other objects work or how another object's instance data is arranged; instead, only the interface to the other object is required.

Objects interact with each other via messages . Messages may indicate status, provide notification, pass data, or instruct the recipient to perform an action. When an object receives a message, it executes an appropriate method . A method may change the object's instance data, send messages to other objects, call kernel routines, or perform calculations--anything that can be done in a normal program procedure can be done in a method. (Note that occasionally the term message handler is used for method .)

Every object is represented by an Object Pointer (optr), a data structure that uniquely identifies the object's location in the system. This data structure is a combination of two special memory handles that provide abstraction of the object's location--this allows the object to be moved in memory or swapped to disk, or even saved to and retrieved from files. Object pointers are used to identify objects in many situations, the most common being when a message is sent. The intended recipient of the message is indicated by its optr.

Objects may interact with each other even if they are in different threads of execution. This is made possible by message queues and the kernel's message dispatcher . When a message is sent, it is first passed to the kernel's dispatcher with the recipient's optr. The dispatcher then puts the message in the recipient object's message queue. If other messages have been sent to the recipient but not handled yet, then the message will wait in the queue until the others have been handled. Otherwise, the message will be handled immediately. (For timing-critical messages, the sender can indicate that the message must be handled immediately; this is important in a multithreaded system.)

A thread is a single entity that runs a certain set of code and maintains a certain set of data. Only one thread may be using the processor at any given time; when a context switch occurs, one thread loses the processor and another takes it over. Each thread may run code for many objects, and an application can have several threads.

As an example, the figure below shows the conceptual model of a Counter object. The Counter maintains a single instance data field, currentCount . It also has three predefined actions:

Messages can carry data (parameters) with them and can also have return values. For example, if the Counter in the example could be set to an arbitrary value (rather than reset to zero), the object sending the set message would have to indicate the value to be set. This would be passed in the same way as a normal C parameter.

If a message is supposed to return a value to the sender, the sender must make sure the message is handled immediately. In this case, the message acts like a remote routine call, blocking the thread of the caller until the message has been handled and the value returned.

Objects can also share data when necessary. Rather than share instance data, however, each object can have an instance data field containing the handle of a sharable block of memory holding the subject data. Then each object could access the shared block through the normal memory manager. Handles are offsets into a table maintained by the kernel; this table contains pointers to the actual memory. This abstraction of the pointer allows the kernel to move the memory block around without forcing everything else to update their pointers. Handles can also be passed as message parameters, allowing large blocks of data to be passed along from object to object.

The figure above shows an example of two objects interacting via messages. In this example, the Requestor object requires the Calculator object to perform a calculation given two numbers. However, since the timing of the calculation is not critical, the result is not returned but is instead sent back by the Calculator object in a subsequent message. This allows the Requestor object to continue with other tasks until the result is ready. (Note that if the Calculator object could be used by more than one requestor, it would have to also be passed the requestor's object pointer. Otherwise, it would have no idea where to send the return message. This example assumes the Calculator object is used by only the one requestor and inherently knows the requestor's optr.)


Up: GEOS SDK TechDocs | Up | Prev: 3 Object-Oriented Programming | Next: 3.2 Classes and Inheritance