Object-Oriented Programming Languages
Object-Oriented Programming Languages
J. Chitu Okoli
As software engineering has grown as a discipline over the past thirty years, the goal of producing large, scaleable, reliable systems has been an increasingly difficult challenge for software developers. As software systems grow in size and complexity, it becomes increasingly difficult to manage and write correct systems. Software “correctness” simply means freedom from bugs (Cox 1984), that is, having a system that does what it is supposed to and does not do what it is not supposed to.
Over the years, computer scientists have developped programming models and methodologies in their attempt to meet this challenge. The evolution of these methodologies has largely been shaped by two factors. The first has been the need to confront the increasing complexity, as I have just described. The second driving factor involves the realization that after several years of software development by the programming community, developers continually write programs that solve problems very similar or almost identical to those already solved by other programmers, sometimes even in the same organization. The need to reuse software has gradually become more evident, especially since the 1980s. Thus, the complexity of modern systems and the need to reuse code have influenced several of the methodologies developped over the years.
Handling Complexity: Encapsulation
Two programming methodologies that have arisen as a response to the complexity of systems are information hiding, data abstraction and modularization.
A major prohibitor of scalability in software systems is the fact that when the data that a system needs to manipulate change, major rework of the program is necessary because so much of the implementation code might be based on the structuring of these data. The amount of recoding necessary might be so much that the program maintainers might very well choose less desirable and roundabout means to solving their problems. This problem exists in the first place because the implementation of a program is dependent on the structure of the data that the program uses. Data abstraction is the principle of separating data from the code that manipulates it, which allows the modification of data structures and a few primitive functions that directly manipulate it. These functions give a common interface to any other program code that needs to access or manipulate this data such that the calling code does not need to know (or perhaps even should not know) how the data is manipulated. Thus, the data, or information, is hidden from those modules that do not need to access it directly. For this reason, data abstraction is often said to emphasize what [is manipulated], not how.
A concept similar to data abstraction is that of modularization. This refers to breaking up a program into modules of code, each of which is a semi-autonomous entity. An example of this is the top-down approach to solving problems: Break the entire problem into several smaller problems, which are in turn broken down to smaller problems. Do this until each subproblem is small enough to be understood, solved in detail (coded), and tested for correctness. This modular approach helps developers to better understand and solve the problem at hand. It also enables the subproblems to be independently tested and maintained without affecting or being affected by the correctness or incorrectness of other modules. Since several small modules are easier to handle than one huge whole, modularization is an essential tool for creating large systems.
When combined, data abstraction and modularization together form the powerful concept of encapsulation, in which a module is used to implement an abstraction of data. Data can be encapsulated in a module which allows access to the data only through a defined interface which not only prevents undefined and undesirable modification of the data, but also hides the details of implementation from the module that needs access to the data. Thus modification of the data structure or implementation is limited to the module that implements the abstract data type; no other module in the system need be affected. Thus encapsulation helps to hide the complexity of systems by making modifications simpler.
Increasing Reusablity: Behaviour sharing
There are many approaches to software reusability and many methodologies that facilitate reuse, but I will focus on only one of these: In software systems, it is common that certain modules, data structures, or other elements will be used again and again. Sometimes, these elements require some modification, but modification of existing elements is often more efficient (in system production time and effort) than starting from scratch. This modification could involve elements existing in the same system or similar elements from other systems. One way to reuse these similar elements is to copy them in their entirety and modify the copy with the necessary special behaviour. This is sometimes feasible, but when several different elements are needed each of which differs only slightly from the others, this can become tedious and error-prone.
Another way to reuse such elements is to keep the original element intact as a kind of template to refer to, and then define new elements in terms of their differences from the original template. When this method is employed, it is understood that behaviours that are not defined or redefined in the new elements are meant to be shared with those of the template elements. This behaviour sharing avoids the redundancy of restating previously defined structures and behaviours of elements and, by reusing the behaviour descriptions, saves development time.
These concepts were developed over several years by software developpers and computer scientists, and their combination led to the formation of the concept of object-oriented programming.
complexity need to reuse
encapsulation + behaviour sharing = object-oriented programming
What is Object-Oriented Programming?
Being a paradigm in computing that developed over many years and from several different approaches, object-oriented programming (OOP) is rather hard to define. It is hard to define what is common among all object-oriented systems, languages and applications, because even though they employ similar general concepts, the techniques used to implement these concepts vary greatly.
It would be helpful to first define an object. An object is an entity that encapsulates data with the methods (operations or functions) that can be applied to that data. (Data and methods are collectively called the members of an object). The data describes the state of an object, and the object’s methods define what operations are possible or permissible upon that data. An object is basically a unit of encapsulating this data-method interaction. Thus encapsulation is an essential part of OOP. In object-oriented thinking (Coad 1993, pp.1-112), an object is thought of as the data it encapsulates and the methods that it applies to itself: an object is capable of manipulating its own data in every way necessary, and its methods are the necessary interfaces by which any other object can access the object’s data.
Object definition and creation is an essential part of all object-oriented systems (OOSs), and an OOS must facilitate these procedures. All OOSs implement some means of extending the definition or description of objects through shared behaviour. I have already described how the incentive of reusablility has been a major factor in forming this characteristic, but another major benefit of shared-behaviour is that they enable data abstraction in that if object A calls the print method of object B which shares object C’s behaviour, A does not need to know whether it is calling B’s or C’s implementation of “print”, that detail is left to B. Another benefit of behaviour sharing is that certain implementions of this concept enable taxonomical classification of the problem (Blair 1990, p.8), which helps understandablility, which facilitates maintainability. This will be discussed further later.
What is an Object-Oriented Programming Language?
With the importance of object encapsulation and behaviour sharing made clear, an object-oriented programming language (OOPL) is obviously a language that supports the implemention of object-oriented principles. Peter Wegner (1986) offers the definition of an OOPL as a language that “[supports] both the management of collections of data abstrations by requiring data abstractions to have a type, and the composition of abstract data types through an inheritance mechanism”. I believe this definition falls short in that it constrains an OOPL to implement behaviour sharing by means of inheritance; however, inheritance is not the only means of achieving shared behaviour. Thus, I offer the following definition: An object-oriented programming language is one which supports encapsulated objects which can share behaviour. Thus, I believe the implementation of encapsulation and support for shared behaviour to be the defining qualities of an OOPL.
There are several such languages. The first of them all was Simula (Birtwistle 1979), a spin off from Algol-60. It added the ideas of class and inheritance (these will be described later) to produce a programming language designed for simulation and modeling (Blair 1990, p.138). Simula was designed long before object-oriented concepts were more concretely developed; in particular, more modern views of encapsulation were not well developed in this language.
Smalltalk, developed around 1980, was one of the earliest modern OOPLs. It is a pure OOPL designed to make graphical programming easier (Sutherlands 1995). Eiffel is a recently designed pure OOPL designed primarily to address the goals of reusability, extendibility and reliablility (ISE 1995).
In addition to these new languages designed to be object-oriented, several older, non-object-oriented languages have been extended to support OOP. Among these are C++ (Stroustrup 86) and Objective-C, both object-oriented extensions to C. The Common-Lisp Object System (CLOS) (Paepcke 1993; Franz 1995) is based on LISP and Modula-3 (Wyant 1995) is in the Pascal/Modula tradition. Others include Ada 95, OO COBOL and Object Pascal, to name a few more popular ones. A list of OOPLs can be obtained from the internet at , among other places.
Now that we have a general idea of what we expect in an OOPL and what OOPLs are out there, let us examine in greater detail some object-oriented features of these languages.
Features of Object-Oriented Programming Languages
We can examine OOPLs from two perspectives: First, we need to examine those features that make the language object-oriented, that is, we need to compare how different languages implement encapsulation and behaviour sharing. The other perspective is a more pragmatic approach which does not have so much to do with anything particularly object-oriented, but examines language features that make certain OOPLs more acceptable than others. Such pragmatic issues include the available programming environments, the efficiency of language code, and memory management (Cook 1990).
Pure and Hybrid OOPLs
A pure OOPL is one in which all data types are objects. Smalltalk is the main pure OOPL. Most languages are hybrid, meaning that primitive data types such as integers, reals, characters, etc. exist which are not objects and which are not treated in the same ways as objects. This is especially the case in OOPLs that are extensions of non-object-oriented languages, such as C++ and OO COBOL. CLOS attempts to strike a compromise: “CLOS integrates the object system as far as possible with the Common Lisp substrate, and most values can be treated uniformly as objects” (Cook 1990).
The principle reason why most OOPL designers are reluctant to make their languages purely object-oriented is that implementing simple and often-used types (like integers and characters) as objects usually produces inefficient code simply because of the abstraction implemented into objects (Franz 1995). Furthermore, system-level programming is extremely difficult in pure OOPL languages, which excludes such languages from a major application domain (Cox 1984; Wyant 1995) from A counter-argument would be that there is greatly increased flexibility for the programmer in handling simple data types as objects. CLOS seems to achieve a reasonable compromise.
Encapsulation: Information Hiding
OOPLs vary in the degree to which they give routines outside an object access to members of that object. In Simula, there is no information hiding, with both data and members of any object being fully accessible to the outside world. Smalltalk and Objective-C refuse access to object data (variables) from routines outside the object, but give routines full access to the object’s methods. Thus, data can be accessed indirectly through an object’s methods, but only if the object chooses to define a method that gives this access. The CLOS mechanism is similar, except that accessors are used to access object data, rather than methods; an accessor is a function (outside the object) which is declared to have access to specific data members. (Cook 1990)
Other languages, like Eiffel and C++, allow the programmer to tailor the accessibility of methods and data. Eiffel allows the programmer to specify which members are accessible from outside the object (Cook 1990). C++ goes further: the programmer can not only specify which members are available to the general public, but can also specify friends, which are functions or other classes that can access those members normally accessible only by methods within the object.
The two major implementations of the object-oriented goal of behaviour sharing are by classification and prototyping. These two perspectives of behaviour sharing lead to very different types of object-oriented systems, so they will be examined separately.
Behaviour Sharing by Classification and Inheritance
By far the predominant view is the use of classification and inheritance; indeed, many consider inheritance an essential characteristic of an object-oriented system. Classification in OOP has a similar meaning to the same term in biological taxonomy: to group objects into classes (analogous to taxa in biology) of objects based on similarity of behaviour. Classes can be described in two ways. The simpler way is to define all the data encapsulated by objects of the class and all the methods applicable to this encapsulated data (the methods are usually also encapsulated in the objects; CLOS, however, is an exception to this general design [Cook 1990]). The other way to describe classes is to define new classes in terms of how they differ from another class, from which they inherit data and methods. In other words, if class D is derived from class B (which is called class D’s base class), class D is defined by the following specification:
D inherits all of B’s members
specify the members that D has in addition to those of B
specify the inherited members that are modified from B’s by redefining them
specify any members inherited from B that are deleted in D (I know of no OOPLs that delete inherited members
A derived class can be base class for a new derived class, which can in turn be the base class for another class, ad infinitum. Thus, class hierarchies can be formed with the root class having several subclasses. The base class(es) of a class are called superclasses.
Classes are treated somewhat variously in class-based languages, but they are uniorm in implementing classes as data types, which is essential for the objects to be manipulated as data and thus support OOP (Wegner 1986). A class can be considered as a kind of template from which objects are created (instantiated is the OOP term for object creation). With this perspective, a class is a guarantee of what an object of that class will look like.
Although all class-based languages treat classes as types, some go further by treating them as objects as well; in these OOPLs, classes are object instatiations of metaclasses (classes which define classes). Languages that support metaclasses include CLOS and Smalltalk. This facility, metalevel programming, greatly extends the expressive power of these languages (Cook 1990).
The simple inheritance of one base class’s members by a derived class is called single inheritance, which is supported by all class-based languages. Some OOPLs, including C++, CLOS, OO COBOL and Eiffel, support multiple inheritance, the ability to inherit members from more than one base class. Multiple inheritance greatly increases the power and flexibility of the inheritance mechanism, but it is often frowned upon because this increased flexibility comes at the cost of an almost proportionate increase in complexity, both for the language implementers and the programmers (Gallagher 1990; Sutherland 1995; Wyant 1995). This complexity arises mainly from the need to resolve conflicting data variable and method names; these conflicts sometimes need to be resolved at runtime in the case of dynamic OOPLs (those with classes typed at runtime).
Snyder (1986) pointed out that although inheritance is essential in class-based languages for implementing data sharing, it actually violates the principle of information hiding in encapsulated objects. A derived class instantiates different kinds of objects from its base class(es), and it is not always desireable for a derived class to manipulate data from its base(s). This problem is handled in some languages like C++ by allowing a class to explicitly specify whether a specific data member or method is accessible only to methods defined in that class, whether it is also accessible to derived classes but still not accessible outside the base or derived classes, or whether it is accessible to the whole world.
Behaviour Sharing by Prototyping and Delegation
Prototyping is the other major perspective taken in implementing behaviour sharing in object-oriented systems, and although it is not as widely practiced as classification, it is slowly gaining popularity (Gallagher 1990). The prototype approach is to create an object or prototype with explicitly defined data and behaviour (methods), and to share the behaviour of that prototype directly by creating new objects called extension objects whose definitions include references to the prototype(s) and how the new objects explicitly differ from the prototype(s). Sharing is achieved by delegating, or forwarding, messages that refer to shared members to the prototype(s). Languages that take this perspective of OOP include Act1 and DELEGATION.
Let me give an example to illustrate this concept. Suppose a tricycle object exists in the system. Its data members are color (=blue) and num_wheels (=3). There is one method, repaint(newcolor), that changes the value of color to the passed value of newcolor. If a bicycle object is to be created which shares tricycle’s behaviour, the system would need to create a reference to tricycle as bicycle’s prototype, and to create a new version of num_wheels and assign it a value of 2. Note that although bicycle’s other variables are shared with tricycle, because num_wheels needs to be changed, a new version must be created so that the prototype’s copy of the variable remains intact and uncorrupted.
Now if bicycle’s color is needed, since bicycle has no color variable of its own, bicycle would refer to tricycle’s version of color. This forward reference to the prototype’s member is called delegation. The message to retrieve bicycle’s color is said to be delegated to bicycle’s prototype, tricycle. Delegation is how prototype-based OOP implements behaviour sharing; it is the analog of inheritance in class-based systems. (Lieberman 1986)
Note the process that follows if bicycle should receive a repaint(red) message: First, the system searches bicycle’s definition for a repaint method and, not finding any, delegates the message to bicycle’s prototype, tricycle. The system also implicitly sends along with the delegated message a reference to the original object which received the message, that is, a reference to bicycle. So when tricycle receives the message to change the color to red, it knows that rather than changing its own color variable, it needs to change the color variable in bicycle which will then be created and changed.
A Comparison of Classification and Prototyping
Gallagher (1990) described three fundamental differences in approach between classification and prototyping. The first has to do with the binding time for patterns of sharing. In class systems, an object’s data members and methods are fixed depending on its class even in the case of dynamic OOPLs like Smalltalk (Sutherland 1995) and CLOS (Franz 1995) which allow the types of the objects handled by particular routines to change at runtime. In class systems, once an object is created, its data and behaviour are fixed. On the other hand, in prototyped systems, objects can add, modify and remove both data and methods at runtime, which provides for great flexibility in programming. This flexibility, however, comes at great risk, since changing a prototype can cause undesireable changes to the behaviour of objects that refer to it.
Another difference that involves a compromise between flexibility and correctness is that in a prototyped system the programmer needs to personally keep track of which prototype stores the data and the methods that will be called when a message is sent to an object, whereas in a class system, the compiler takes care of this at compile time.
The third major difference mentioned by Gallagher is that a class system through classification describes the behaviour of entire groups of objects, and by examining a class definition, one has a minimal guarantee of the behaviour of any object in that class. However, in a prototyped system objects may and typically do differ in varying degrees from their prototypes, and although this makes them more flexible, the lack of uniformity can be harmful.
Considering these mixed blessings of both approaches to behaviour sharing, some languages like Hybrid and Exemplars provide support for both classes and prototypes. In Hybrid, every object belongs to a class that describes it minimal behaviour; through prototyping an individual instance of a class can extend its behaviour. In Exemplars, an object can exist apart from a class (Gallagher 1990).
Now, before we leave the subject of behaviour sharing, I will briefly mention another way that this goal is achieved in some OOPLs.
Polymorphism as Behaviour Sharing
“Polymorphism (from the Greek, meaning ‘many forms’) is the quality that allows one name to be used for two or more related but technically different purposes” (Schildt 1994, p.5). Polymorphism is implemented in some programming languages, among them some non‑object‑oriented languages, by function overloading. Function overloading means using the same identifier name to call different procedures or functions, and letting the compiler or system to resolve the correct procedure specification using the function profile or typing information. Some OOPLs use polymorphism to implement a type of behaviour sharing.
It is common that similar or even different objects need to carry out similar processes. For example, if a number of objects model different shapes, such as a circle, a square and a triangle, each object might have a print method to print themselves. It often makes sense, especially in situations when the programmer doesn’t know at design time what type of object might be handled, for the print methods to be called in a uniform manner and to let the specific called object take care of the details of calling the correct method on itself. In such cases, polymorphism can be looked at as a way to let different objects share the interface of a common method, while leaving the implementation details to the object data abstraction.
Static and Dynamic Object‑Oriented Languages
Since the first days of FORTRAN, static typing has been a feature of almost all compiled programming languages, mainly because of the ease of implementation and efficiency of compiled code produced. Moreover, application often need the stability of variables that retain their values throughout the lifetime of the program. However, the need for dynamic typing was soon recognized, mainly because of the need to implement recursion and to conserve memory by reusing memory space when variables are no longer used in the program.
In these days of object‑oriented programming, what used to be ma or advantages of static typing are non longer so compelling. The phenomenal increase in hardware quality accompanied with an equally phenomenal drop in cost has made concerns such as saving memory space and increasing speed a matter of less concern for many application domains, especially in business programming. The primary concerns of today are how to handle the overwhelming complexity of the software systems that modem technology makes possible (Franz 1995). In light of this present condition of the software industry, the argument for dynamic OOPLs is weighty. The most popular of these languages are Smalltalk (Sutherland 1995) and CLOS (Franz 1995). To achieve dynamicity, these languages are interpreted rather than compiled. Franz (1995) contends that since CLOS is a hybrid rather than a pure OOPL like Smalltalk, it achieves performance comparable to that of compiled languages.
There are many other important issues and considerations concerning object‑oriented programming languages that could be discussed, such as concurrent OOPLs, comparison of efficiency and performance to conventional languages, programming environments and tools, including class libraries, and so on. Nonetheless, I have tried to discuss those features of OOPLs that make them distinctively object‑oriented‑‑objects that encapsulate data to implement abstraction and information hiding, and the sharing of behaviour by classification or prototyping. The field of object‑oriented programming is rapidly growing as computer scientists begin to apply object‑oriented principles to almost every aspect of computing. I envision greater refinement of the issues and techniques involved as the paradigm of object‑oriented programming continues to change the way we do computing.
Birtwistle, G., et al. Simula Begin, 2nd Edition. Van Nostrand Reinhold, 1979.
Blair, G., et al. “Introduction”, Object-Oriented Languages, Systems and Applications. Halsted Press, 1990.
Coad, P. and Nicola, J. Object-Oriented Programming. Yourdon Press, 1993.
Cook, S. “Programming Languages Based on Objects”, Object-Oriented Languages, Systems and Applications. Halsted Press, 1990.
Cox, B. “Message/Object Programming: An Evolutionary Change in Programming Technology”, IEEE Software v.1:1 pp.50-61. 1984.
Franz, Inc. “Technology”. (as at December 1996). Franz, Inc., 1995.
Gallagher, J. “Basic Concepts II (Variations on a Theme)”, Object-Oriented Languages, Systems and Applications. Halsted Press, 1990.
Indiana, University of. “Index to Object-Oriented Programming Languages.” (as at December 1996).
ISE, Interactive Software Engineering. “An Invitation to Eiffel.” (as at December 1996). ISE, Inc., 1995.
Lieberman, H. “Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems”, Special Issue of ACM SIGPLAN Notices, v. 21 pp.214-223. 1986.
Paepcke, A. Object-Oriented Programming: The CLOS Perspective. MIT Press, 1993.
Schildt, H. Teach Yourself C++. Osborne McGraw-Hill. 1994.
Snyder, A. “Encapsulation and Inheritance in Object-Oriented Programming Languages”, Special Issue of ACM SIGPLAN Notices, v. 21 pp.38-45. 1986.
Sutherland, J. “Smalltalk, C++, and OO COBOL: The Good, the Bad, and the Ugly.” (as at December 1996). 1995.
Wegner, P. “Classification in Object-Oriented Systems”, ACM SIGPLAN Notices, v. 21:10 pp. 173-182. 198
Wyant, G. “Introducing Modula-3”, ftp://ftp.gte.com/pub/m3/linux-journal.html (as at December 1996). 1995.