Object oriented programming is a keyword used in numerous contexts. It may represent a set of similar but competing design methodologies, complete with philosophical and almost religious aspects. Nonetheless, this section attempts to identify and describe a number of well defined programming constructs present in most programming languages labeled as object oriented. Thus, the emplasis is on technical aspects rather than on terminology.
Most classical programming languages have data types composed of a number of fields and often called structure or record types. These are now called object types or classes; the individual records of a record type are thus called the objects, or instances, of the corresponding class.
Sometimes, a data type C will contain all the same fields as another one A plus a few additional ones. In that case, it is said that class C inherits from class A or that C is a child and A an ancestor. This serves two purposes. First, the declaration of C is simpler since it is sufficient to declare that it inherits the fields from A instead of repeating the fields in A in the declaration of C. Secondly, an instance of class C is also a valid object of class A since C it is a superset of A. Thus, when a procedure expects an object of class A, an object of class C or of any other child class of A can be sent; this is called polymorphism.
In most implementations, like in C++ or Modula-3, the fields common to A and C will be at the same offset within objects of type A and C such that a procedure compiled to operate on objects of type A will still work correctly on objects of type C. A class may have several child classes inheriting from it. Child classes may in turn have childs. This produces an inheritance tree.
Some languages allow multiple inheritance. In that case, a class may inherit from two or more classes. The result is an acyclic directed inheritance graph instead of a tree. While this may lead to overly complex relationships between classes, there are cases where multiple inheritance is interesting. It poses, however, a number of implementation problems. The most significant problem may be the offsets of fields that now may vary between an ancestor and its childs.
Indeed, if class C inherits from A and then from B. The fields in A are also present in C and at the same offset in the record. The fields of B, however, can only be placed in C after the fields inherited from A and will thus be at different offsets. A number of implementation techniques have been developed to cope with this problem but each carry an overhead in record size, access time and implementation complexity.
Some, mostly interpreted, languages may store fields in a table of name-content pairs. There is obviously a time penalty in replacing the access through a fixed offset by a table search. In such cases, interestingly, offsets are never used and multiple inheritance poses fewer implementation problems. Moreover, such languages may also enable adding and removing fields dynamically, while the program is running. When the list of fields changes dynamically, it can only be known at run time if a given field exists or not. Typing errors in a field name may thus go undetected until the statement containing the error is executed.
Each instance of a class contains a number of fields, just like ordinary records. These are called instance variables. Class variables are global variables that are associated with a class. They can be accessed by specifying the class and variable name or by specifying an object of that class and the variable name.
Class variables are mostly a convenience. The syntax to access class variables may be the same as the syntax to access instance variables (fields in records). It also divides the name space for global variables along the classes. If no support for class variables exists in a language, one gets the same result by naming adequately the variables by prepending the class name to the variable name. Modular languages, with variables names scoped by module, obtain the same benefits through a more general mechanism.
The following example illustrates class variables in C++.
class A { public: int field1; /* instance variable */ int field2; /* instance variable */ static int global_count; /* static indicates class variable */ }; A obj1, obj2; /* class variables can be accessed as a global variable */ A::global_count = 0; obj1.field1 = 0; obj1.field2 = 0; obj2.field1 = 0; obj2.field2 = 0; /* obj1.global_count and obj2.global_count are A::global_count */ obj1.field1++; obj1.field2++; obj1.global_count++; obj2.field1++; obj2.field2++; obj2.global_count++; /* Thus, A::global_count is now equal to 2 */
Class variables are inherited but the child class may use the parent variable or have its own. In C++, for example, if class A has a class variable v, a child class C will also have a class variable named v; however, A::v and C::v will be distinct variables. In other languages, such as CLOS, it is possible to specify if A::v and C::v are the same or are distinct.
Encapsulation and data hiding are often mentioned in connection with object oriented programming. This is nothing else than keeping the internal details private to a module while only exporting in the interface what the clients need to know. In some languages, like Modula-3, it is possible to place in the interface only the desired subset of the type declarations, variables and procedures. Other languages, like C and C++, because of implementation limitations, require the full declaration of every exported type to appear in the interface. However, in C++, it is possible to flag portions of the type declarations as private; these still appear in the interface but the compiler ensures that the private portions are not used by clients.
Procedure names used to be unique. There are cases however where several related procedures perform the same function but on arguments of different type. For instance, there could be print_integer, print_boolean... Some languages, like C++, allow several procedures to have the same name. The compiler then determines the desired procedure by looking at the argument types.
The downside of this is that if the wrong argument is specified, there may be another procedure of the same name matching the type of the wrong argument, leaving the error undetected at compile time. The advantages are shorter names for procedures and the possibility to simply change the type of a variable and have the compiler automatically find the appropriate procedures to call, given the new type.
The examples below illustrate the impact of allowing several procedures to have the same name.
int identity, fame; char *name; ... /* if identity changes type, this statement needs no change */ print(identity); /* if the programmer types fame instead of name by mistake it will go undetected at compile time. */ print(name); ...
int identity, fame; char *name; ... /* if identity changes type, this statement needs change */ print_text(identity); /* if the programmer types fame instead of name by mistake the compiler will catch it. */ print_int(name); ...
Most programming tools, in particular linkers, are based on the assumption that procedure names are unique. Thus, C++ compilers must often generate unique external names for the different procedures that have the same name but different arguments. This may simply mean using the concatenation of the name and of the arguments type names as a unique procedure name supplied to the linker; this process is called name mangling. Other programming tools, such as the debugger, must understand mangled names and translate them to a more readable form for the programmer.
Most programming languages support a number of arithmetic operators. The evaluation order within an arithmetic expression may or not be specified in the language. If the evaluation order changes, from one compiler to another for instance, rounding errors may propagate differently and produce slightly different results.
Apart from the evaluation order which may change, there is little difference between an operator which takes two arguments and produces a result and an equivalent procedure. A number of languages, striving for syntax regularity, like LISP, only support procedures. Arithmetic operations are simply part of the built-in procedures.
Another approach is to support user defined operators. Letting a program define new operators or change the number of arguments for operators would require a dynamically changing lexical analyzer; few languages allow that. It is possible, however, in C++ to associate procedures to operators based on the type of the associated arguments.
This is simply a syntaxic convenience. Indeed, a + b is simply replaced by the procedure call operator+(a,b). Programmers reading a program must then check carefully the declarations in order to know what procedure is associated with each operator in the program.
A number of procedures are often associated with a class. The methods of the class are fields that contain pointers to these procedures. Naturally, one of their arguments will be an instance of that class. Often, the argument of that class will be the first argument and , will be called the receiving object. The receiving object is thus used both to access the method and as argument to the procedure. This may look as following in a non object oriented language:
TYPE A = RECORD field1, field2: INTEGER; method1: PROCEDURE(arg1: A; arg2: REAL); method2: PROCEDURE(arg1: A; arg2, arg3: TEXT); END; PROCEDURE MyProcedure(arg1: A; arg2: REAL) = BEGIN ... END MyProcedure; VAR obj: A; ... BEGIN obj.field1 := 5; obj.method1 := MyProcedure; obj.method1(obj,22.0); ... END.
The advantage of storing a pointer to a procedure in each object is that each object may react in its own way when the method is activated. Indeed, the method must contain a pointer to a procedure of the declared type but each object may be associated with a different one, upon need. For example, each object may represent a printer model, and a PaperEmpty method may contain a procedure that can communicate with the printer and determine the number of pages left in the tray. Calling a method is commonly named invoquing a method on an object or sending a message to the object.
Very often, all the objects of a given class would use the same procedures as methods. Thus, having identical methods stored in every object of a class is wasteful. Instead, all the methods may be stored in a table and only a pointer to that shared table is stored in each object.
TYPE TableForA = RECORD method1: PROCEDURE(arg1: A; arg2: REAL); method2: PROCEDURE(arg1: A; arg2, arg3: TEXT); END; A = RECORD table: TableForA; field1, field2: INTEGER; END; PROCEDURE MyProcedure(arg1: A; arg2: REAL) = BEGIN ... END MyProcedure; VAR obj1, obj2: A; tableForA: TableForA; ... BEGIN tableForA.method1 := MyProcedure; tableForA.method2 := OtherProc; obj1.table := tableForA; obj2.table := tableForA; obj1.field1 := 5; (* obj1 is used to find the method and is also the first argument *) obj1.table.method1(obj1,22.0); ... END.
Since methods are a common construct, many languages automate the construction of the table. A table and a field pointing to it is automatically allocated for each class. Furthermore, since the object used to find the method is also the first argument to the method, it is automatically supplied. Thus, with this work handled automatically, the above example becomes:
TYPE A = OBJECT field1, field2: INTEGER; METHODS (* implicitly there is a first argument of type A *) method1(arg2: REAL) := MyProcedure; method2(arg2, arg3: TEXT) := OtherProc; END; PROCEDURE MyProcedure(arg1: A; arg2: REAL) = BEGIN ... END MyProcedure; VAR obj1, obj2: A; ... BEGIN obj1.field1 := 5; (* obj1 is also supplied as argument automatically *) obj1.method1(22.0); ... END.
Some interpreted languages store the fields and methods of each instance in a table of name-content pairs. In that case, the method invocation mechanism may be more elaborate than simply retrieving a pointer to procedure, from a record. Indeed, the method is first searched in the instance. If not found, the class provides a default value. This way, a default method may be specified for all instances of a class but may be superseeded by another method in the instances where this is desired.
There is another dimension to method invocation, inheritance. Indeed, a procedure that expects an instance of class A as first argument (receiving object) may operate as well on an instance of its child class C since C has a superset of the fields defined for A. Therefore, all the methods defined for class A are applicable to class C and need only be redefined when a different behavior is desired for C.
In most compiled object oriented languages like C++ or Modula-3, all the methods defined for class A (stored in its table) are used to initialize the methods for class C. However, C may override some of the methods inherited from A (store a different procedure at the corresponding location in the table) or may have additional methods (entries added at the end of the table).
TYPE A = OBJECT field1, field2: INTEGER; METHODS (* implicitly there is a first argument of type A *) method1(arg2: REAL) := MyProcedure; method2(arg2, arg3: TEXT) := OtherProc; END; C = A OBJECT (* inherit from A *) METHODS addMethod(arg2: INTEGER); OVERRIDES method1 := MyProcForC; END; PROCEDURE MyProcedure(arg1: A; arg2: REAL) = BEGIN ... END MyProcedure; PROCEDURE MyProcForC(arg1: C; arg2: REAL) = BEGIN ... END MyProcedure; ... END.
In some interpreted languages, where fields are stored in a table of name-content pairs, the method invocation looks if the method is defined in the instance, then in the corresponding class and then in the ancestor classes. Several table searches may be involved before finding the procedure to execute. If methods may be added and removed dynamically, it may also happen that the method cannot be found. The following example illustrates how this search may proceed.
TYPE Class = REF RECORD parents: REF ARRAY OF Class; methods: ProcTable; (* class methods *) name: TEXT; END; Obj = OBJECT class: Class; methods: ProcTable; (* instance methods *) END; A = Obj OBJECT field1, field2: INTEGER; END; ... PROCEDURE InvoqueMethod(receiver: Obj; methodName: TEXT; arg: ArgList) = VAR proc: Proc := NIL; i := 0; BEGIN proc := Find(methodName,receiver.methods); IF proc = NIL THEN proc := FindMethod(methodName,receiver.class); END; IF proc = NIL THEN Wr.PutText(Stdio.stderr,"Method " & methodName & " not found in object of type " & receiver.class.name & "\n"); RAISE MethodNotFound; END; proc(receiver,arg); END InvoqueMethod; PROCEDURE FindMethod(name: TEXT; class: Class): Proc = VAR proc: Proc := NIL; BEGIN proc := Find(name,class.methods); IF proc # NIL THEN RETURN proc; END; FOR i := 0 TO LAST(class.parents^) DO proc := FindMethod(name,class.parents[i]); IF proc # NIL THEN RETURN proc; END; END; RETURN proc; END FindMethod;
Often, a child class overrides a method because it needs additional work to be performed, on top of what the ancestor method does. While it is possible to simply lazily copy the ancestor method and modify it to perform the added work, this creates maintenance problems. Indeed, when the ancestor method is modified, the portion of the child method copied from the ancestor will probably need to be modified in the same way.
Some languages allow the definition of procedures to be executed before or after a method is called. The same result, however, is obtained by overriding the parent method with a method that does the additional work and calls the parent method.
Sometimes, the additional work needs to be done in the middle of what the parent method does. In that case, the content of the parent method should be put in two procedures. These two procedures will be called in the parent method. They will also be called in the child method, but with some code between the two calls.
In classical programming languages, there are no parent and child versions of a procedure. One procedure does it all and tests are inserted to adapt to the needs of different types. The resulting code is full of IF/THEN/ELSE and hard to read but there are fewer incentives to lazily copy code.
In the example below, a parent method and a good and bad version of the corresponding method for the child is shown. The Child method needs only do more work before and after the parent method.
TYPE A = OBJECT METHODS method() := MethodInAncestor; END; C = A OBJECT METHODS OVERRIDES method := GoodMethodInChild; END; PROCEDURE MethodInAncestor(self: A) = BEGIN do step i... do step ii... do step iii... do step iv... END MethodInAncestor; PROCEDURE BadMethodInChild(self: C) = BEGIN do something before... do step i... do step ii... do step iii... do step iv... do something after... END BadMethodInChild; PROCEDURE GoodMethodInChild(self: C) = BEGIN do something before... A.method(self); (* call the parent method *) do something after... END GoodMethodInChild;
In the example below, the child method needs to do something in between the steps performed by the parent method.
PROCEDURE Step1And2() = BEGIN do step i... do step ii... END Step1And2; PROCEDURE Step3And4() = BEGIN do step iii... do step iv... END Step3And4; PROCEDURE MethodInAncestor(self: A) = BEGIN Step1And2(); Step3And4(); END MethodInAncestor; PROCEDURE GoodMethodInChild(self: C) = BEGIN do something before... Step1And2(); do something in middle Step3And4(); do something after... END GoodMethodInChild;
In most object oriented languages, one argument is the receiving object and its type determines the method to execute, through its method table. In the CLOS language, the method selection process has been generalized to account for the type of each of the arguments.
Many versions of a given procedure may be defined, all with the same name but each with different types for the arguments, as a generic procedure. When a procedure call with that name is issued, the actual type of each argument is verified and the suitable version of the generic procedure is found. If the argument supplied in position 1 is of type C, it is suitable for a version of the procedure that expects an argument of its ancestor type A at the same position. However, if there is a version of the procedure defined for an argument of type C in that position, it is a better match since it was defined specifically for that type.
There are cases, however, where the best match is not obvious. For instance, if arguments in position 1 and 2 are of type C, it is difficult to select between a version that expects types A and C in position 1 and 2 and a version that expects types C and A.
This flexibility and expressive power comes at the cost of searching the best match. To alleviate that cost, a cache may be attached to each generic procedure. This cache is a hash table that associates types of arguments to the best matching version of the generic procedure. When a new version of the generic procedure is added, which is infrequent, the cache content is emptied. When a match is found, the type of the arguments is used as key to insert the matched version in the cache. The next time a call is made with the same argument types, the matching version is readily found in the cache instead of searching through all the available versions. The cost of searching in the cache, especially given the length of the key formed by all the arguments type names, is still much higher that accessing a method in a method table.
In C++, many procedures may have the same name. The suitable procedure to call is selected based on the arguments type, but at compile time. The declared argument type is thus used instead of the actual argument type. Indeed, a variable of declared type A may hold an object of type A or of any of its descendents.
class A {...} *pa1, *pa2; class C : A {...} c1, c2; /* In C++ this version will get called */ void TheFunction(A arg1, A arg2) { do something general... } /* In CLOS this version would get called */ void TheFunction(C arg1, C arg2) { do something more appropriate... } main() { pa1 = &c1; pa2 = &c2; TheFunction(pa1,pa2); }
In C++, only the methods declared as virtual are effectively placed in the method table. The other non virtual methods are simply ordinary procedures using the methods syntax. While the version of a virtual method called depends on the actual type of the receiving object, the version of a non virtual method depends on its declared type. It is thus mostly a syntactic convenience.
class A { public: void theMethod(); virtual void theVirtualMethod(); } *pa1; class C : A { public: void theMethod(); virtual void theVirtualMethod(); } c1; main() { pa1 = &c1; /* The version from A is executed */ pa1->theMethod(); /* The version from C is executed */ pa1->theVirtualMethod(); c1.theMethod(); c1.theVirtualMethod(); }
Because virtual methods are selected through the method table at run time, this is often called dynamic binding. Non virtual methods are selected at compile time and thus represent static binding. It is virtual methods that enable objects of different types, stored in a list of some common ancestor type, to produce different results. For example, a list of GraphicPrimitive objects could store Circles, Rectangles and Polygons, each having an appropriate draw method. This is called polymorphism.
In some languages, like C++, it is possible to define a method called the constructor that will automatically be called each time a new object of the corresponding type is created. When an object is created, the constructors of its type and of all its ancestor types are called. The order in which all these constructors are called and mechanisms to pass arguments to all these constructors need to specified appropriately. In C++, new objects are created for global and local variables, when dynamically allocated, or when arguments and return values are passed by value (copied). In languages without constructors, the same effect is obtained through initialization methods called explicitly when new objects are created. The constructors initialize the objects, setting default values and allocating internal structures.
Destructors are methods automatically called each time an object ceases to exist. In C++, local variables and temporary objects cease to exist when they fall out of scope, global variables cease to exist when the program ends and dynamically allocated objects cease to exist when free is called. The destructors usually deallocate internal structures and may write permanent state to disk.
The notion of destructor does not work well in a garbage collected environment. Indeed, the garbage collector, once in a while, finds unreachable objects and deallocates them. Calling a destructor method may change the reachability of the object (store a pointer to the object in a global variable somewhere), in which case the object should not be collected now and the destructor should not have been called.
In garbage collected environments, objects may be registered for finalization. The registered procedure is eventually called when the object is unreachable. If the registered procedure makes the object reachable again, it will not be garbage collected and may be registered again for finalization if needed.
In the example below, both constructors and explicit initializations are illustrated. Constructors need to relay some of the received arguments to the constructors of their parent classes while no special mechanism is required in explicit initialization procedures.
class employee { char *name; Address home; public: employee(char *n, Address adr) : name(n), home(adr) {} } class manager: public employee { Group group; public manager(char *n, Address adr, Group grp): employee(n,adr), group(grp) {} } manager boss("John Smith","1 Main Street CA",Finance);
TYPE Employee = OBJECT name: TEXT; home: Address; METHODS init(name: TEXT; home: Address) := InitE; END; Manager = Employee OBJECT group: Group; METHODS init(name: TEXT; home: Address; group: Group) := InitM; END; PROCEDURE InitE(self: Employee; name: TEXT; address: Address) = BEGIN self.name := name; self.address := address; END InitE; PROCEDURE InitM(self: Manager; name: TEXT; address: Address; group: Group) = BEGIN Employee.init(self,name,address); self.group := group; END InitM; VAR boss := NEW(Manager).init("John Smith","1 Main Street CA",Finance);
It is often useful to obtain information about a class at run time. This information may include the class name, the list of fields, with their name and type, and the ancestor type. The class of objects that contain information about classes is sometimes called metaclass. These objects may contain the class methods and class fields, common to all objects of the corresponding class.
Adding a method to a class thus becomes calling the addMethod method upon the corresponding metaclass object.
Many library functions return an error code whenever something goes wrong. The calling function may be able to deal with the error or may need to propagate the error code to its calling function. The result is that eventually almost every function call is followed by a test and a return. This affects the program readability by adding a lot of code only useful in exceptional cases.
Exceptions are a mechanism to automatically return from procedures, recursively, until the error condition can be handled appropriately. The number of places where exceptional conditions must be checked and handled is thus greatly reduced. The downside is that procedures may be interrupted abruptly by exceptions at almost every nested procedure call. The programmer must insure that the data structures are in a consistent state at these points. Alternatively he may intercept exceptions and put the structures in a consistent state before sending the exception to its parent.
Exceptions are best used for really exceptional, unpredictable, conditions such as disk full, no memory left or syntax errors.
EXCEPTION BadFormat(TEXT); PROCEDURE WriteInfo(msg: TEXT) RAISES{BadFormat,IOError} = BEGIN TRY OpenMessageFile(); IF BadMsg(msg) THEN RAISE BadFormat(msg); END; WriteMessage(msg); END; END WriteInfo; PROCEDURE CallWrite() RAISES ANY = BEGIN TRY WriteInfo("Is this message well formatted?"); EXCEPT | IOError => ...; | BadFormat(m) => Wr.PutText(Stdio.stderr,m); ELSE Wr.PutText(Stdio.stderr,"Unknown Error"); END; END CallWrite;
There are cases where a similar functionality is needed in several different contexts. In some cases this can be achieved through objects and methods. In other cases, it is not practical for simplicity or performance reasons. A typical example is container classes (lists, stacks, trees...). It is possible to have a ListAny type which accepts any type (an ancestor of all other types). Tests, however, will be required when taking out objects to verify that they are of the expected type. A single implementation would suffice but it would be less efficient and more cumbersome to use. Moreover, in many programming languages, there is no type which can accept every type, including characters, integers and floating point values. In such cases it may be more convenient and efficient. to have distinct IntegerList, GeometryList, BooleanList types.
Another example is when some functionality should be added to several otherwise unrelated classes. A field and associated methods to store property lists may be wanted. While the same functionality is required in each case, the offset in the structure where the field will be added and other similar parameters may change from one class involved to another.
While distinct executable code will be required in most cases to handle these variants (the size of contained objects changes from GeometryList to BooleanList), a single copy of the source code should exist for maintainability purposes. This generic module is then parameterized. Each set of parameters defines a context in which the generic module can be compiled. Below is a simple generic interface and module followed by an instantiation for integer elements.
GENERIC INTERFACE ArrayOps(Element); PROCEDURE Max(READONLY anArray:ARRAY OF Element.T): Element.T; END ArrayOps; GENERIC MODULE ArrayOps(Element); PROCEDURE Max(READONLY anArray:ARRAY OF Element.T): Element.T = VAR largest := Element.First(); BEGIN FOR i:= FIRST(anArray) TO LAST(anArray) DO IF Element.Greater(anArray[i],largest) THEN largest := anArray[i]; END; END; RETURN largest; END Max; END ArrayOps;
INTERFACE IntegerElement; TYPE T = INTEGER; PROCEDURE Greater(a,b:T):BOOLEAN; PROCEDURE First():T; END IntegerElement; INTERFACE IntegerArrayOps = ArrayOps(IntegerElement) END IntegerArrayOps MODULE IntegerArrayOps = ArrayOps(IntegerElement) END IntegerArrayOps
Multiple inheritance is an interesting mechanism that comes at a relatively high cost in conceptual and implementation complexity. It also incurs a performance penalty. Its benefits are, in some cases, easier code reuse.
For example, a number of classes may need to store properties. A Propertyclass may provide a data structure and access methods to store and access properties. Setting the Property class as a common ancestor, using single inheritance, may not be feasible if other classes with common ancestors have no need for properties. While multiple inheritance solves the problem, other approaches may be used to obtain the same result without too much difficulty.
/* Class C contains the fields and methods of A plus those of PropertyClass plus some of its own. */ class C : A PropertyClass { ... } /* The methods of PropertyClass, when inherited from C, need to know the offset of the PropertyClass fields within C. This involves an overhead in space and execution time. */ C objc; main() { ... objc.addProperty("name",value); ... }
The first way is through aggregation. A PropertyClass object is allocated in each object of classes that need to store properties.
(* Class Child contains the fields and methods of Ancestor.T plus a field of type Property.T plus others as needed. *) IMPORT Ancestor, Property; TYPE Child = Ancestor.T OBJECT properties: Property.T; ... METHODS ... END; VAR objc := NEW(Child).init(); (* The same result is achieved simply through accessing the "properties" field. A PropertyClass object is contained instead of inherited. *) BEGIN ... objc.properties.addProperty("name",value); ...
A second way is to provide a template that adds a property list to a class, producing a child class with properties. In that case, a copy of the methods required to process properties is created in the context of class Child. These copied methods use some memory but avoid the space and time overhead associated with multiple inheritance.
GENERIC INTERFACE Property(BaseType); TYPE Child = BaseType.T OBJECT ...fields required to store properties... METHODS addProperty(name: TEXT; value: INTEGER); ... END; END Property. MODULE DefineChild = Property(Ancestor); END DefineChild; ... VAR objc: NEW(DefineChild.Child).init(); BEGIN ... objc.addProperty("name",value); ...
In classical structured programs, each procedure must account for the differences in data types it must handle. For exemple, the draw procedure would need to test and execute different sections to handle Circles and Polygons. In object oriented languages, the common fields and methods are stored in parent classes while the specifics of the different types handled are defined in their respective class.
When the way some operation proceeds is modified, the changes are localized in the corresponding procedure for classical structured programs. In an object oriented program, the corresponding method, in all the types that override it, must be examined.
Upon adding a new type to handle, all the procedures must be modified in a classical structured program. In an object oriented program, a new class is simply added and the methods specific to that class are defined.
Thus, object oriented programming often requires more time to set up properly the inheritance hierarchy and insure that the common parts are implemented in the parent types. However, adding new types is much easier thereafter.
Interpreted object oriented languages allow more features than compiled ones. Indeed, most interpreted languages will allow dynamically adding new types or adding and removing fields and methods of existing types. Some even allow dynamically changing the type of an existing object or the ancestors of a class. On the other hand, interpreted languages usually execute much slower (2 to 20 times) and cannot determine before the program is executed if a non-existent method will be called or if the arguments types are incorrect. Therefore, interpreted languages are often more interesting for rapid prototyping while compiled languages are better for large, efficient and robust programs.
Encapsulation often means hiding some operations is methods calls. Similarly, decomposing a method in a common part, implemented in the ancestor type, and a specific part, implemented in the child type may also cause extra methods calls. Furthermore, some compilers cannot optimize as much programs when procedures are called through tables, as are methods. For all these reasons, some object oriented programs may run slower than their classical counterpart. However, a quicker development may justify this overhead. It may even allow spending more time to find more efficient algorithms.
The overhead of these extra calls belongs to the low level, constant factor, optimizations. Once a program runs well, it is often easy to identify the component which uses up most of the execution time. Then, some methods calls in this small component may be replaced by non object oriented procedure calls.