670 likes | 823 Views
Object Orientation. James Brucker. History Questions. OO concepts of encapsulation, inheritance, and polymorphism are based on what language developed in the 1960's in Norway? What OO language introduced the notion of "message passing" between object?
E N D
Object Orientation James Brucker
History Questions • OO concepts of encapsulation, inheritance, and polymorphism are based on what language developed in the 1960's in Norway? • What OO language introduced the notion of "message passing" between object? • What modern OO language uses message passing? • you can try to invoke any method of any object at runtime. If the method is "private" you get a runtime exception. • Hint: used in mobile phones
Questions 2 • In Java, what is escape analysis and what is its purpose? • What is the difference between a value type and a reference type? • In C#, how are value types implemented? • In C#, can you add a method to a class that already exists? How?
Objectives • Motivation for O-O Languages • Key Characteristics • Important Differences • Focus on C++, C#, Java, and JavaScript • Implementation of O-O Features • Organization of Data Members • Virtual Method Table (VMT) • Dynamic Binding
Stack push( ): void pop( ): value isEmpty( ): bool isFull( ): bool Designing a Stack • A basic data structure. What behavior is needed? push( value ) Put a new value on top of stack. pop( ) Pop and return the top of stack isEmpty( ) Test if stack is empty. isFull( ) Test if stack is full. Pretty simple, huh? Let's see how C handles this.
Stack in C (1): imperative style Can you name some deficiencies in this code? (find 4) void push( int value, int *stack, int *top, int size ) { if ( *top >= size ) perror("Stack overflow"); stack[(*top)++] = value; } int pop( int *stack, int *top, int size ) { if ( *top <= 0 ) perror("Stack underflow"); return stack[--(*top)]; } int isEmpty( int *stack, int *top, int size) { return (*top <= 0); }
Stack in C (2): simplify the interface int SIZE = 0; int *stack; int top = 0; int initStack( int size ) { stack = (int *)malloc( size * sizeof(int) ); if ( stack == null ) perror("InitStack failed"); SIZE = size; top = 0; } void push( int value ) { if ( top >= SIZE ) perror("Stack overflow"); stack[top++] = value; } int pop( ) { if ( top <= 0 ) perror("Stack underflow"); return stack[--top]; } int isEmpty( ) { return (top <= 0); } What are the problems with this implementation? Global variable for stack properties.
Stack in C (3): hide data members static int SIZE = 0; static int *stack; static int top = 0; int initStack( int size ) { stack = (int *)malloc(size*sizeof(int)); if ( stack == null ) perror("InitStack failed"); SIZE = size; top = 0; } void push( int value ) { if ( top >= SIZE ) perror("Stack overflow"); stack[top++] = value; } int pop( ) { if ( top <= 0 ) perror("Stack underflow"); return stack[--top]; } int isEmpty( ) { return (top <= 0); } What are the problems with this implementation? Data Hiding: "static" variables have file scope.
Stack in Modula-2: abstraction TYPE element = integer; MODULE Stack; IMPORT element; EXPORT push, pop, isFull, isEmpty; CONST size = 100; VAR data : array [1..size] of element; top : integer; PROCEDURE push( value : element ); BEGIN if top = size THEN error("Stack overflow"); else begin top := top + 1; data[top] := value; end; END push; Module limits scope/accessibility of its members. Import/export external names. (* Stack usage *) push( data1 ); push( data2 ); y := pop( ); Better: abstract data type, data hiding, less name space pollution. Problems?
Stack Factory in Modula-2 MODULE StackFactory; IMPORT element; EXPORT stack, push, pop, isFull, isEmpty; CONST size = 100; TYPE stack = RECORD data : array [1..size] of element; top : integer; END; PROCEDURE init_stack(var stk : stack); BEGIN stk.top := 1; END init_stack; PROCEDURE push(var stk: stack; item: element); BEGIN if stk.top >= size then error( ); else stk.data[stk.top] := item; stk.top := stk.top + 1; END push; (* Stack is abstract type *) var A, B: stack; init_stack( A ); init_stack( B ); push( A, data1 ); push( B, data2 );
Three Pillars of O-O Programming • Encapsulation • objects contain both data and methods that operate on the data • control over visibility (and access) to members • Inheritance • one class can extend another class, inheriting its attributes and methods. Promotes code re-use. • Polymorphism • the same behavior can be defined for different classes • a reference variable can refer to different classes • using a reference, a program can transparently invoke a named behavior from different classes.
Other benefits of object-orientation • Reuse code using well-designed classes and inheritance. • Limit complexity by dividing big project into classes. • Better control over object creation (constructor) and destruction (finalizer or destructor). class DancingButton extends JButton { public DancingButton( ImageIcon image ) { ___________( image ); // call parent constructor this.image = image; } public void paint( Graphics g ) { _______________( g ); // call parent's paint( ) dancer.dance( ); }
Stack class in C++ (1) using namespace std; class Stack { private: int *stack; int size; int top; public: Stack( int asize ) : size(asize) { // constructor stack = new int[asize]; top = 0; } ~Stack( ) { // destructor delete [ ] stack; } void push(int value); // method prototype int pop( ); }; semi-colon ends class definition.
Stack class in C++ (2) class Stack { public: ... void push(int value); int pop( ); bool isEmpty( ) { return top <= 0; } bool isFull( ) { return top >= size; } }; void Stack::push(int value) { if ( top >= size ) perror("Stack overflow"); stack[top++] = value; } int Stack::pop( ) { if ( top <= 0 ) perror("Stack underflow"); return stack[--top]; } Method prototype inside of class. Methods defined outside of class.
Stack class in C++: header file #ifndef _STACK_H_ #define _STACK_H_ class Stack { private: int *stack; int size; int top; public: Stack(int size) { ... } void push(int value); int pop( ); bool isEmpty( ) { return top <= 0; } bool isFull( ) { return top >= size; } }; #endif stack.h
Stack class in C++: method definition #include "stack.h" void Stack::push(int value) { if ( top >= size ) perror("Stack overflow"); stack[top++] = value; } int Stack::pop( ) { if ( top <= 0 ) perror("Stack underflow"); return stack[--top]; } stack.cc
Why 2 Files? #include "stack.h" void Stack::push(int value) { if ( top >= size ) perror("Stack overflow"); stack[top++] = value; } ... class Stack { public: Stack(int size) { ... } void push(int value); int pop( ); bool isEmpty( ) { return top <= 0; } bool isFull( ) { return top >= size; } }; stack.h stack.cc
What belongs in stack.h? #ifndef _STACK_H_ #define _STACK_H_ class Stack { private: int *stack; int size; int top; public: Stack(int size) { ... } void push(int value); int pop( ); bool isEmpty( ) { return top <= 0; } bool isFull( ) { return top >= size; } }; #endif why code here?
Stack class in C++: application #include "stack.h" int main( ) { Stack mystack(20); // creates a Stack object mystack.push(1); mystack.push(15); ... } application.cc
Encapsulation (1) C++: extends C, functions and variables can be global, file, or class scope. C# and Java: all functions and variables must be part of a class. int MAXSIZE = 1024; char buf[MAXSIZE]; static int size = 0; int fun( ) { ... } class Stack { private: int size; int top; public: void push(int x); ... }; public class Stack { private int top; private int size; private int *stack; public void push(int x) { if (top>=size) ...; stack[top++] = x; } public bool isEmpty( ) { return top <= 0; } }
Encapsulation (2) C++: members can be public, protected, or private, but not classes. C#: class can be public, private, or internal. class Stack { private: int top; int size; protected: int *stack; public: void push(int x); bool isEmpty( ) { return top <= 0; } }; public class Stack { private int top; private int size; protected int *stack; public void push(int x) { if (top>=size) ...; stack[top++] = x; } public bool isEmpty( ) { return top <= 0; } }
Scope of Names C++ and C#: namespace and "using namespace" control name visibility. Java: package and "import". using System; using System.Collections; namespace MyCollection { class Stack { ... } } package mycollection; import java.io.File; import java.util.*; class Stack { ... } /* C++ */ using namespace System; using namespace System::Collections;
Scope Resolution Operator C++ uses "::" but "." for elements of a class. C# uses "." Java uses "." /* C# */ class Stack : System.Collections.IEnumerator { void read( ) { Console.ReadLine( ); } } • /* Java */ • class Stack implements • java.util.Iterator • { • java.util.Scanner in = • new java.util.Scanner(); • void read( ) { • in.nextLine( ); • } • }
Creating Objects C++, C#, and Java all have constructors. • A class may have multiple constructors. In C++ declaring an object reference constructs it! In C# and Java, must use "new" to create the object. /* C++ */ Person p; // invokes default constructor Person p("Joe"); // invokes Person::Person(string) Person& q = p; // create object reference q /* C# and Java */ Person p; // create object reference only p = new Person("Joe");// invokes Person(String)
Inheritance C++: multiple inheritance no universal base class C#: single inheritance all classes are subclass of System.Object a class can implement unlimited number of interfaces Java: single inheritance all classes are subclass of java.lang.Object a class can implement unlimited number of interfaces
Inheritance and Access Control • Class members can be public, private, or protected. • Child class inherits everything but can only access public and protected members. /* C++ */ class Stack { protected: int top; int size; int *stack; public: void push(int x); bool isEmpty( ) { return top <= 0; } }; /* C# */ public class Stack { protected int top; protected int size; protected int *stack; public void push(int x) { ... } public bool isEmpty( ) { return top <= 0; }
Access Control in C++ • "Friend" classes can access protected and private members. • Child class can reduce visibility of inherited parts. class CircularStack : private Stack { ... friend class StackManager; friend int max( const Stack& ); };
Multiple inheritance in C++ • Multiple inheritance allows subclasses to "mix in" the properties of several parent classes. ostream istream iostream class iostream: public istream, public ostream { public: iostream( streambuf* ); virtual ~iostream(); protected: iostream( ); }
Multiple inheritance in C++ • If a subclass has multiple paths to a superclass, it may inherit more than one copy of superclass properties! • This is called repeated inheritance. • This can cause unwanted, inconsistent behavior. class A { protected string name; ...};class B : public A {...};class C : public A {...};class D : public B, public C {...}; ... D dog(); // dog has 2 copies of A::name dog.setName("woof"); // set which name?
Shared inheritance in C++ • Shared inheritance: instances of a derived class that have multiple access routes to superclass share the same instance of a superclass object: • class A { protected string name; ...};class B : virtualpublic A {...};class C : virtualpublic A {...};class D : public B, public C {...}; • ... • D dog( ); // dog has only one A::name member
Polymorphism Polymorphism enables flexible, extensible applications. Container -components: Collection +add( JComponent) +paint(Graphics g) JComponent paint(Graphics g) setsize( Dimension ) setPosition(Dimension) ... 1 * JButton button = new JButton(...); JTextField text = new JTextField(...); JSlider slider = new JSlider(...); container.add( button ); container.add( text ); container.add( slider ); JTextField JButton JSlider JPanel
Polymorphism (2) Container can send same message to any JComponent. Container -components: Collection +add( JComponent) +paint(Graphics g) JComponent paint(Graphics g) setsize( Dimension ) setPosition(Dimension) ... 1 * /* tell all components to paint themselves */ for( JComponent comp : components ) comp.paint( Graphics g ); JavaVM will call the paint(Graphics) method of each object, not the paint method of the superclass (JComponent).
Liskov Substitution Principle If a program uses an object from class A, then the program should still work correctly if you substitute an object from any subclass of A. • For this to work, the behavior of each public method in a class needs to be clearly defined. • This means documentation. • Java and C# include documentation right in the code.
Polymorphism in Java • In Java, instance methods use dynamic binding so polymorphism is automatic. class Person { protected String name; ... public String toString( ){ return name; } } class Student extends Person { private String studentID; ... public String toString( ) { return studentID; } } Person p = new Student("Bill Gates", "11111111"); System.out.println( "Welcome to KU " + p.toString() );
Static Binding in Java Static binding used for things that are never overridden: • static and final methods • private methods • constructors. class Person { private static int nextID; public Person( ) { ... } final void setName( String newname ) { ... } private void initialize( ) { ... } public static getNextID( ) { return nextID; } }
Polymorphism in C++ • All methods are statically bound unless virtual. class Person { protected: string name; public: Person(string aname) : { name = aname; } string toString( ) { return name; } } class Student : Person { /* Student extends Person */ protected: string id; public: Student(string name, string id) : Person(name), id(id) { } string toString( ) { return id; } } Student bill("Bill Gates", "11111111"); Person &p = bill; cout << Welcome to KU " << p.toString() << endl;
Polymorphism in C++ • virtual methods are dynamically bound. • virtual can applied to classes, methods, and abstract methods. class Person { protected: string name; public: Person(string aname) : name(aname) { } virtual string toString( ) { return name; } } class Student : Person { /* Student extends Person */ protected: string id; public: Student(string name, string id) : Person(name), id(id) { } string toString( ) { return id; } }
Polymorphism in C# Static binding (default) Use "new" to override. Dynamic binding: use virtual and override. class Person { public string who( ) { ... } } class Student : Person { new public string who() { ... } } class Person { public virtual string who( ) { ... } } class Student : Person { public override string who( ) { ... } } Example: Person p = new Student("Bill Gates", "11111111"); p.who( );
Why static binding? • Polymorphism is key to writing extensible, reusable O-O programs. • So, why don't C# and C++ always use dynamic binding? • don't require us to request it using "virtual" • Why does Java have "final" methods? • eliminates future opportunity for overriding
Polymorphism and Run-time Binding A key to polymorphism is that names are bound to methods dynamically. How does the run-time environment know which method should be used? public class Circle extends JComponent { void paint(Graphics g) { /* draw circle */ } ... } public class Square extends JComponent { void paint(Graphics g) { /* draw square */ } ... } public redraw( Graphics g ) { JComponent [ ] parts = { new Square(), ... }; parts[k].paint(g); // determined at run-time
Memory for Object Data Members class Person { String name; Date birthday; char sex; ... // methods String getName() ... } void test( ) { Person you = new Person(...); you.name = "Mr. Java"; you.getName( ); } memory Virtual Method Table for Person you VMT ptr name birthday 'M' a String object a Date object
Memory for Object Data Members • The compiler uses known offsets from the start of object for each data member. For example: you.name is (you)+4 you.sex is (you)+12 • The VMT is for accessing methods (later). memory for Person Virtual Method Table for Person you VMT ptr name birthday 'M' a String object a Date object
Memory Layout with Inheritance class Person { String name; Date birthday; private char sex; // methods String toString( )... } class Student extends Person { String studentID; Course [] courses; void addCourse(...) } s = new Student(...); memory for Student VMT for Student s VMT ptr name birthday sex String Person Date studentID courses String Array
Virtual Method Table for Object • A Virtual Method Table is a table of addresses of the methods of a class. For the Object class... clone equals finalize getClass ... toString address of Object clone method address of Object equals method Methods from Object's VMT address of Object finalize method • The compiler knows which method it wants based on signature. It can specify as offset in runtime VMT.
Virtual Method Table for Person • A subclass can add methods or replace methods in its VMT. • Only 1 VMT for each class (all objects share it). clone equals finalize getClass ... toString getName setName address of Object clone method Methods from Object's VMT (maybe changed by Person class) address of Person equals method address of Object finalize method address of Person toString method Methods added by Person class
Object Reference and Inheritance • The information about an object is available through the object reference. Personp = new Person(...); memory for Person object VMT for Person class p VMT ptr name birthday sex clone equals finalize getClass ... toString getName setName String Person Date
Object Reference and Inheritance • A subclass can override fields/methods or add new fields and methods. Student s = new Student(...); memory for Student object VMT for Student class s VMT ptr name birthday sex clone equals finalize getClass ... toString getName setName String Person Date added attributes studentID courses String Array added methods getCourse addCourse
Object Reference and Inheritance • If we assign an object to reference of superclass type, the extra information is still there, but not used by superclass reference. Person p2 = new Student(); p2 VMT ptr name birthday sex clone equals finalize getClass ... toString getName setName String Date studentID courses getCourse addCourse ((Student)p2).getCourse();
Interfaces • Separate the specification of behavior from the implementation of the behavior. • Interfaces can often replace multiple inheritance. • Interfaces can extend other interfaces in Java and C# Example: IComparable specifies "CompareTo" behavior. /** C# IComparable interface **/ namespace System { public interface IComparable { int CompareTo( object other ) ; } } methods are automatically "public" method signature only
Interface Example /** Person class implements IComparable **/ using namespace System; public class Person : IComparable { private string lastName; private string firstName; ... int CompareTo( object other ) { if ( other is Person ) { Person p = (Person) other; return lastName.CompareTo( p.lastName ); } else throw new ArgumentException( "CompareTo argument is not a Person"); } } } Whyimplement interface? What is the benefit?