Object Oriented Programming and Object Oriented Design Programming Languages Robert Dewar
Object Oriented Programming OOP provides three fundamental functionalities: OOP provides three fundamental functionalities: Type extension Type extension Inheritance Inheritance Dynamic Polymorphism Dynamic Polymorphism
Type Extension The problem, given an existing type, add additional capabilities retaining the original The problem, given an existing type, add additional capabilities retaining the original Make the extended type look as much like the original as possible Make the extended type look as much like the original as possible Typical implementation, add fields or components to an existing record type. Typical implementation, add fields or components to an existing record type.
Faking Type Extension Could make a new record Could make a new record With new components With new components And retaining the old type as one component And retaining the old type as one component type Ext is record Parent : Old; Newf : Integer; end record; type Ext is record Parent : Old; Newf : Integer; end record; But that would not look much like original But that would not look much like original If Newobj is of type Ext If Newobj is of type Ext Cannot say Ext.Value Cannot say Ext.Value Must say Ext.Parent.Value Must say Ext.Parent.Value
Inheritance Goes along with type extension Goes along with type extension When a type is extended When a type is extended New type has all the operations of the old type, with the same meaning. New type has all the operations of the old type, with the same meaning. Of course they do not know about the new fields, and do not reference them Of course they do not know about the new fields, and do not reference them But most code that worked for the base type works for the extended type without change But most code that worked for the base type works for the extended type without change
More on Inheritance Cannot always use operations of base type Cannot always use operations of base type May need to mess with new fields May need to mess with new fields In particular constructors must do so In particular constructors must do so May have different semantics from new field May have different semantics from new field Should be able to “override” inherited operations when type is extended Should be able to “override” inherited operations when type is extended Must override constructors Must override constructors Can also add new operations for new extended type Can also add new operations for new extended type
Faking Inheritance If you fake type extension If you fake type extension You could add definitions for every operation. Most would look like You could add definitions for every operation. Most would look like procedure Operate (X : Ext) is begin Operate (X.Parent); end Operate; procedure Operate (X : Ext) is begin Operate (X.Parent); end Operate; That’s rather annoying That’s rather annoying And generates a lot of junk And generates a lot of junk
Ad Hoc Polymorphism More familiar term is overloading More familiar term is overloading Applies in this situation as follows Applies in this situation as follows If we have several types derived from a common parent with an operation Op If we have several types derived from a common parent with an operation Op Suppose Op is overridden for some types Suppose Op is overridden for some types If a variable has a particular type, then the compiler can figure out what Op you mean from the type of the variable If a variable has a particular type, then the compiler can figure out what Op you mean from the type of the variable
Dynamic Polymorphism Also called dynamic dispatching Also called dynamic dispatching Addresses problems where you have data structures that are heterogenous and can contain different kinds of data. Addresses problems where you have data structures that are heterogenous and can contain different kinds of data. The data items are similar (e.g. obtained by type extension from a common base). The data items are similar (e.g. obtained by type extension from a common base). And therefore have a similar set of operations. And therefore have a similar set of operations.
More on Dynamic Dispatching Now suppose we apply Op to a variable which at run time can have more than one possible “type”. Now suppose we apply Op to a variable which at run time can have more than one possible “type”. What we want is that at runtime, the proper Op is picked, based on the current type of the object in the variable What we want is that at runtime, the proper Op is picked, based on the current type of the object in the variable This is called dynamic dispatching This is called dynamic dispatching
Faking Dynamic Dispatching We could have record fields that contained pointers to the function to be called. We could have record fields that contained pointers to the function to be called. Actually that’s how dynamic dispatching is usually implemented: Actually that’s how dynamic dispatching is usually implemented: Record contains a pointer to a table Record contains a pointer to a table Table has (at fixed offsets for any given operation), address of function to be called. Table has (at fixed offsets for any given operation), address of function to be called. Different types have different tables Different types have different tables
An Approach to Compare The issue is that we want a variable that can have several different forms at runtime. The issue is that we want a variable that can have several different forms at runtime. And we have some code that depends on which particular form it has. And we have some code that depends on which particular form it has. And some code that is the same for all types. And some code that is the same for all types.
Using Variant Records Instead of N types extended from a given base type, use a variant record with N different possibilities. Instead of N types extended from a given base type, use a variant record with N different possibilities. Fields of parent correspond to common fields in the variant record Fields of parent correspond to common fields in the variant record Code that does not depend on type just accesses these common fields Code that does not depend on type just accesses these common fields
Using Variant Records (cont) Code that is not common has a case statement: Code that is not common has a case statement: case Object.DiscrimValue is when val1 => … when val2 => … … end case; case Object.DiscrimValue is when val1 => … when val2 => … … end case;
Comparing the Approaches Consider that you have N operations and T types, then potentially you have N*T different functions, but in practice many of the functions are the same for many types. Consider that you have N operations and T types, then potentially you have N*T different functions, but in practice many of the functions are the same for many types. Case statement means you have one unit per operation, using case to select Case statement means you have one unit per operation, using case to select Dynamic dispatching means you have one unit per type, with overridden operations. Dynamic dispatching means you have one unit per type, with overridden operations.
Comparing the Approaches (cont.) If you often add operations, the case statement approach is easier, add one new unit for new operation providing base code with case statements as required If you often add operations, the case statement approach is easier, add one new unit for new operation providing base code with case statements as required If you often add types, the dynamic dispatching approach is easier, add one new unit for new type overriding any operations where base code is wrong. If you often add types, the dynamic dispatching approach is easier, add one new unit for new type overriding any operations where base code is wrong.
OOP and Reuse By making your types extendible By making your types extendible You increase reuse capabilities You increase reuse capabilities Instead of editing your code in places where it does not apply Instead of editing your code in places where it does not apply A client extends your types, and overrides your code where it does not apply A client extends your types, and overrides your code where it does not apply
Object Oriented Design Has nothing to do with OOP per se Has nothing to do with OOP per se Relates not to language features but to the design approach Relates not to language features but to the design approach It may be that OOP features are useful for object oriented design (OOD). It may be that OOP features are useful for object oriented design (OOD).
OOD – The Basic Idea The problem is modeled as a set of objects, preferably related to the structure of the problem, that represent real objects in the world. These objects have state. The problem is modeled as a set of objects, preferably related to the structure of the problem, that represent real objects in the world. These objects have state. Computation proceeds by passing messages (requests, signals, commands, reports) between objects. Computation proceeds by passing messages (requests, signals, commands, reports) between objects.
How does OOD relate to OOP In the real world, objects are built by specializing more general notions In the real world, objects are built by specializing more general notions A Toyota Previa is an instance of Car with extra info, which is an instance of Vehicle with extra information, etc. A Toyota Previa is an instance of Car with extra info, which is an instance of Vehicle with extra information, etc. Type extension Type extension All cars work mostly the same All cars work mostly the same Inheritance Inheritance But for some features, cars differ But for some features, cars differ Dynamic dispatching Dynamic dispatching
OOP Features in Ada 83 Ada 83 provides features for Ada 83 provides features for Inheritance Inheritance But does not provide But does not provide Type extension Type extension Dynamic dispatching Dynamic dispatching Inheritance is provided via derived types Inheritance is provided via derived types Other OOP features deliberately omitted Other OOP features deliberately omitted Designers were very familiar with Simula-67 Designers were very familiar with Simula-67 But felt that genericity was a better approach But felt that genericity was a better approach
Derived Types Declare a type and some operations on it Declare a type and some operations on it type Base is …. procedure Print (Arg : Base); function New_Base return Base; type Base is …. procedure Print (Arg : Base); function New_Base return Base; Now derive a new type Now derive a new type type Mybase is new Base; type Mybase is new Base; All operations are available on Mybase Including for example Print and New_Base All operations are available on Mybase Including for example Print and New_Base But you can redefine (override) any inherited operations. But you can redefine (override) any inherited operations.
OOP In Ada 95 Genericity is not enough Genericity is not enough Market demands OOP features Market demands OOP features So in Ada 95 features are added for So in Ada 95 features are added for Type Extension Type Extension Dynamic Dispatching Dynamic Dispatching But multiple inheritance is deliberately omitted But multiple inheritance is deliberately omitted
Tagged Types in Ada 95 A tagged type has a dynamic tag showing what type the object is. Otherwise it looks like a record A tagged type has a dynamic tag showing what type the object is. Otherwise it looks like a record type Base is tagged record X : Integer; Y : Float; end record; type Base is tagged record X : Integer; Y : Float; end record; Can also have tagged private types Can also have tagged private types type Base is tagged private; type Base is tagged private; Completion must be tagged record Completion must be tagged record
Type Extension A tagged type can be extended A tagged type can be extended Using an extension of derived type idea Using an extension of derived type idea type Mybase is new Base with record B : Boolean; X : Duration; end record; type Mybase is new Base with record B : Boolean; X : Duration; end record; All operations are inherited All operations are inherited Except for constructors (functions returning values of type Base) Except for constructors (functions returning values of type Base) Constructors must be overridden Constructors must be overridden Since they need to know about the new fields Since they need to know about the new fields
How type Extension Works New fields are added at the end of the record, with original fields at the start. New fields are added at the end of the record, with original fields at the start. A subprogram that is only referencing the original fields can do this on the base type or any type derived from it. A subprogram that is only referencing the original fields can do this on the base type or any type derived from it. Because the original fields are always at the same offset from the start of the record. Because the original fields are always at the same offset from the start of the record. This model does not extend well to the case of multiple inheritance. This model does not extend well to the case of multiple inheritance.
Type extension and overloading Suppose a client has Suppose a client has B : Base; B : Base; M : Mybase; M : Mybase; And there is an operation D that was not overridden: And there is an operation D that was not overridden: D (B); D (M); D (B); D (M); Correct proc called, but in fact does same thing Correct proc called, but in fact does same thing And there was an overridden operation O And there was an overridden operation O O (B); O (M); O (B); O (M); Correct proc called (static overloading) Correct proc called (static overloading)
Converting Among Types Suppose we have an operation Q that is defined for Base and was not inherited Suppose we have an operation Q that is defined for Base and was not inherited Because it was not derived in original package Because it was not derived in original package And now we have a Mybase: And now we have a Mybase: M : Mybase; M : Mybase; And we want to call Q on M: And we want to call Q on M: Q (M); -- no good, wrong type Q (M); -- no good, wrong type Q (Base (M)) -- that’s ok, a view conversion Q (Base (M)) -- that’s ok, a view conversion
Using conversion when Overriding Suppose we have a procedure Dump defined on Base Suppose we have a procedure Dump defined on Base For Mybase we want to dump the new fields and then call the original dump For Mybase we want to dump the new fields and then call the original dump procedure Dump (X : Mybase) is begin … dump new fields Dump (Base (X)); -- calls original Dump end Dump; procedure Dump (X : Mybase) is begin … dump new fields Dump (Base (X)); -- calls original Dump end Dump;
Converting the Other Way: NOT Suppose we have an operation M that is defined for Mybase, and we have an object of type Base: Suppose we have an operation M that is defined for Mybase, and we have an object of type Base: B : Base; B : Base; And we want to apply M to B And we want to apply M to B You are out of luck, can’t do it You are out of luck, can’t do it After all M might refer to extended fields! After all M might refer to extended fields!
Class Variables Suppose you want a data structure that holds a mixture of objects of type Base and Mybase. Suppose you want a data structure that holds a mixture of objects of type Base and Mybase. The type Base’Class is a type that includes values of tagged type Base and all types derived from Base. The type Base’Class is a type that includes values of tagged type Base and all types derived from Base. type Bptr is access Base’Class; type Bptr is access Base’Class; BC_Ptr : Bptr := new Base’(….); BC_Ptr := new Mybase’(….); BC_Ptr : Bptr := new Base’(….); BC_Ptr := new Mybase’(….);
Dynamic Dispatching If an subprogram, say Draw is defined as a primitive operation of type Base (defined along with type Base) If an subprogram, say Draw is defined as a primitive operation of type Base (defined along with type Base) Then not only is it inherited by any type derived from Base Then not only is it inherited by any type derived from Base But it is also defined on Base’Class But it is also defined on Base’Class
Special Treatment of Base’Class The subprogram The subprogram procedure Draw (Arg : Base’Class); procedure Draw (Arg : Base’Class); That is derived automatically That is derived automatically Has special semantics Has special semantics It is called with an object of the appropriate type (e.g. BC_Ptr.all) It is called with an object of the appropriate type (e.g. BC_Ptr.all) The result is to call the version of Draw that is appropriate to the actual run-time type of the argument (looks at the tag) The result is to call the version of Draw that is appropriate to the actual run-time type of the argument (looks at the tag)
How Dynamic Dispatching Works Tag is actually a pointer to a table Tag is actually a pointer to a table One table for each type One table for each type In our example, two tables In our example, two tables One table for Base One table for Base Different table for Mybase Different table for Mybase Table contains pointers to subprograms Table contains pointers to subprograms Put new ones at end Put new ones at end First entries in Mybase table are a copy of the entries in the Base table unless overridden. First entries in Mybase table are a copy of the entries in the Base table unless overridden.
Object-Oriented programming in C++ Classes as units of encapsulation Classes as units of encapsulation Information Hiding Information Hiding Inheritance Inheritance polymorphism and dynamic dispatching polymorphism and dynamic dispatching Storage management Storage management multiple inheritance multiple inheritance
Classes Encapsulation of type and related operations Encapsulation of type and related operations class point { double x,y; // private data members double x,y; // private data members public: public: point (int x0, int y0); // public methods point (int x0, int y0); // public methods point () { x = 0; y = 0;}; // a constructor point () { x = 0; y = 0;}; // a constructor void move (int dx, int dy); void move (int dx, int dy); void rotate (double alpha); void rotate (double alpha); int distance (point p); int distance (point p);}
A class is a type : objects are instances point p1 (10, 20); // call constructor with given arguments point p1 (10, 20); // call constructor with given arguments point p2; // call default constructor point p2; // call default constructor Methods are functions with an implicit argument p1.move (1, -1); // special syntax to indicate object p1.move (1, -1); // special syntax to indicate object // in other languages might write move (p1, 1, -1) // in other languages might write move (p1, 1, -1) // special syntax inspired by message-passing metaphor: // special syntax inspired by message-passing metaphor: // objects are autonomous entities that exchange messages. // objects are autonomous entities that exchange messages.
Implementing methods No equivalent of a body: each method can be defined separately No equivalent of a body: each method can be defined separately void point::rotate (double alpha) { void point::rotate (double alpha) { x = x * cos (alpha) - y * sin (alpha); x = x * cos (alpha) - y * sin (alpha); y = y * cos (alpha) + x * cos (alpha); y = y * cos (alpha) + x * cos (alpha); }; }; // x and y are the data members of the object on which the // x and y are the data members of the object on which the // method is being called. // method is being called. // if method is defined in class declaration, it is inlined. // if method is defined in class declaration, it is inlined.
Constructors One of the best innovations of C++ One of the best innovations of C++ special method (s) invoked automatically when an object of the class is declared special method (s) invoked automatically when an object of the class is declared point (int x1, int x2); point (int x1, int x2); point (); point (); point (double alpha; double r); point (double alpha; double r); point p1 (10,10), p2; p3 (pi / 4, 2.5); point p1 (10,10), p2; p3 (pi / 4, 2.5); Name of method is name of class Name of method is name of class Declaration has no return type. Declaration has no return type.
The target of an operation The implicit parameter in a method call can be retrieved through this: The implicit parameter in a method call can be retrieved through this: class Collection { class Collection { Collection& insert (thing x) { // return reference Collection& insert (thing x) { // return reference … modify data structure … modify data structure return *this; // to modified object return *this; // to modified object }; }; my_collection.insert (x1).insert (x2); my_collection.insert (x1).insert (x2);
Static members Need to have computable attributes for class itself, independent of specific object; e.g. number of objects created. Need to have computable attributes for class itself, independent of specific object; e.g. number of objects created. Static qualifier indicates that entity is unique for the class Static qualifier indicates that entity is unique for the class static int num_objects = 0; static int num_objects = 0; point () { num_objects++;}; // ditto for other constructors point () { num_objects++;}; // ditto for other constructors Can access static data using class name or object name: Can access static data using class name or object name: if (point.num_objects != p1.num_objects) error (); if (point.num_objects != p1.num_objects) error ();
Classes and private types If all data members are private, class is identical to a private type: visible methods, including assignment. If all data members are private, class is identical to a private type: visible methods, including assignment. A struct is a class with all public members A struct is a class with all public members How much to reveal is up to programmer How much to reveal is up to programmer define functions to retrieve (not modify) private data define functions to retrieve (not modify) private data int xcoord () { return x;}; int xcoord () { return x;}; int ycoord () { return y;}; int ycoord () { return y;}; p2.x = 15; // error, data member x is private p2.x = 15; // error, data member x is private
Destructors If constructor allocates dynamic storage, need to reclaim it If constructor allocates dynamic storage, need to reclaim it class stack { class stack { int* contents; int sz; int* contents; int sz; public: public: stack (int size) { contents = new int [ sz = size];}; stack (int size) { contents = new int [ sz = size];}; void push (); void push (); int pop (); int pop (); int size () { return sz;}; } int size () { return sz;}; } stack my_stack (100); // allocate storage dynamically stack my_stack (100); // allocate storage dynamically // when is my_stack.contents released? // when is my_stack.contents released?
If constructor uses resources, class needs a destructor User cannot deallocate data because data member is private: system must do it User cannot deallocate data because data member is private: system must do it ~stack ( ) {delete[ ] contents;}; ~stack ( ) {delete[ ] contents;}; inventive syntax: negation of constructor inventive syntax: negation of constructor Called automatically when object goes out of scope Called automatically when object goes out of scope Almost never called explicitly Almost never called explicitly
Copy and assignment point p3 (10,20); point p3 (10,20); point p5 = p3; // componentwise copy point p5 = p3; // componentwise copy This can lead to unwanted sharing: This can lead to unwanted sharing: stack stack1 (200); stack stack1 (200); stack stack2 = stack1; // stack1.contents shared stack stack2 = stack1; // stack1.contents shared stack2.push (15); // stack1 is modified stack2.push (15); // stack1 is modified Need to redefine assignment and copy Need to redefine assignment and copy
Copy constructor stack (const stack& s) { // reference to existing object stack (const stack& s) { // reference to existing object contents = new int [ sz = s.size()]; contents = new int [ sz = s.size()]; for (int I = 0; I <sz; I++) contents [I] = s.contents [I]; for (int I = 0; I <sz; I++) contents [I] = s.contents [I]; } stack s1 (100); stack s1 (100); … stack s2 = s1; // invokes copy constructor stack s2 = s1; // invokes copy constructor
Redefining assignment Assignment can also be redefined to avoid unwanted sharing Assignment can also be redefined to avoid unwanted sharing Operator returns a reference, so it can be used efficiently in chained assignments: one = two = three; Operator returns a reference, so it can be used efficiently in chained assignments: one = two = three; stack & operator= (const stack& s) { stack & operator= (const stack& s) { if (this != &s) { // beware of self-assignment if (this != &s) { // beware of self-assignment delete [] contents; // discard old value delete [] contents; // discard old value contents = new int [sz = s.size ()]; contents = new int [sz = s.size ()]; for (int I = 0; I <sz; I++) contents [I] = s.contents [I]; for (int I = 0; I <sz; I++) contents [I] = s.contents [I]; } return *this; } return *this; } stack s1 (100), s2 (200); … s1 = s2; // transfer contents stack s1 (100), s2 (200); … s1 = s2; // transfer contents
Differences Between Ada and C++ C++ model much more specialized to the notion of OOD C++ model much more specialized to the notion of OOD Distinguished first parameter is object involved Distinguished first parameter is object involved No easy way of defining binary operators No easy way of defining binary operators Prefix notation nice for objects but awkard for values Prefix notation nice for objects but awkard for values C++ allows multiple inheritance C++ allows multiple inheritance
Doing Multiple Inheritance in Ada We can have one field that we add be an instance of some other base type. We can have one field that we add be an instance of some other base type. We can use generics to parametrize this additional type We can use generics to parametrize this additional type Worked out examples in Ada 95 Rationale Worked out examples in Ada 95 Rationale Which you can find at Which you can find at
OOP in Java To be supplied! To be supplied!