
CS 430 Spring 2020 Mike Lam, Professor Abstraction and Object-Oriented Programming Abstraction ● Abstraction is a fundamental concept in CS ● Textbook definition: "a view or representation of an entity that includes only the most significant attributes" ● Mathematical notion: "equivalence classes" ● Practical reality: the first line of defense against software complexity! ● Key: finding the most appropriate level of abstraction Types of abstraction ● Process abstraction – Structured (block) syntax – Subprograms and modules ● Data abstraction – Abstract data types and interfaces ● Polymorphism and generics – Encapsulation and information hiding ● Classes and objects ● Inheritance Abstract data types ● Abstract data type (ADT) – Set of values (carrier set) – List of supported operations ● Common operations: constructor, accessors, iterators, destructors – Not specified: underlying representation ● Exists purely as a mathematical construct ● Examples – List: append(value), get(index), remove(index) – Stack: push(value), pop – Set: add(value), isMember(value), union(otherSet) – Map: store(key, value), lookup(key) – Floating-point: add, sub, mul, div, sqrt Abstract data types ● Concrete data type – Implementation of an ADT on a computer – Specifies value size and format – Often supports only a subset of values from the ADT – Most languages support user-defined concrete data types ● Examples (in Java) – List: ArrayList, LinkedList Set ADT – Set: HashSet, TreeSet – Floating-point: float, double HashSet TreeSet Abstract data types ● Abstract data types can be implemented in some programming languages as data types – Easier w/ encapsulation mechanisms – Even easier w/ information hiding mechanisms – Information hiding implies encapsulation (but not converse) Design issues ● Encapsulation: how is related code and data grouped? – Header files, namespaces, packages, modules, etc. – Structs, unions, classes, interfaces – Modularity and readability; extensibility and maintainability ● Information hiding: should underlying data be exposed? – Levels: public, private, protected – Public fields vs. getters and setters – Convenience/writability vs. safety and extensibility ● Polymorphism: is parameterization possible? – Specifying parameters – Specifying restrictions on the parameters – Power/expressivity vs. readability Encapsulation ● Advantages – Organization – Separate compilation – Avoiding name collisions ● Physical vs. logical encapsulation – Contiguous vs. non-contiguous code ● Naming vs. non-naming encapsulation – Referenced by name vs. not ● Grouping-only vs. information hiding encapsulation – Everything public vs some things non-public Encapsulation Physical Logical Naming Java Class Ruby Class Java Package Ada Package C++ Namespace Ruby Module Non-naming .c, .cpp, or .h file Grouping only .h file Ruby Module C++ Namespace Information hiding .c or .cpp file Ruby Class Java Class Ada Package Java Package Original table courtesy of Dr. Chris Fox Object-oriented programming ● Inheritance – Original motivation: code re-use – Parent/superclass vs. child/derived/subclass – “Pure” vs. hybrid – Overriding methods – Single vs. multiple inheritance (simplicity vs. power) – Static vs. dynamic dispatch (speed vs. power) – Abstract methods and classes – Non-overridable methods: "final" methods in Java Dispatch class DispatchTest1 { void foo(Object o) { System.out.println("foo(Object)"); } void foo(String s) { System.out.println("foo(String)"); } void bar(Object o) { foo(o); } public static void main(String[] args) { (new DispatchTest1()).bar("What gets run?"); } } What will this program print? Dispatch class DispatchTest1 { void foo(Object o) { System.out.println("foo(Object)"); } void foo(String s) { System.out.println("foo(String)"); } void bar(Object o) { foo(o); } public static void main(String[] args) { (new DispatchTest1()).bar("What gets run?"); } } class DispatchTest2 { static class A { void foo() { System.out.println("A.foo()"); } } static class B extends A { void foo() { System.out.println("B.foo()"); } } void bar(A a) { a.foo(); } public static void main(String[] args) { (new DispatchTest2()).bar(new B()); } } What about this one? Dispatch class DispatchTest1 { void foo(Object o) { System.out.println("foo(Object)"); } void foo(String s) { System.out.println("foo(String)"); } void bar(Object o) { foo(o); } public static void main(String[] args) { (new DispatchTest1()).bar("What gets run?"); } } class DispatchTest3 { static class A { static void foo() { System.out.println("A.foo()"); } } static class B extends A { static void foo() { System.out.println("B.foo()"); } } void bar(A a) { a.foo(); } public static void main(String[] args) { (new DispatchTest3()).bar(new B()); } } How about now? Object-oriented implementation ● Dispatch – Static dispatch: all method calls can be resolved at compile time – Dynamic dispatch: polymorphic method calls resolved at run time – Single vs. multiple dispatch (one object’s type vs. multiple objects’ type) ● Class instance record – List of member variables for objects w/ vtable pointer – Subclass CIR is a copy of the parents' with (potentially) added fields ● Virtual method table (vtable) – List of methods w/ pointers to implementations Heap Static Code Section vtable ptr foo Parent::foo x : int bar class instance record virtual method table Child::bar Object-oriented implementation public class A { public class B extends A { public int x, y; public int z; public void draw() { … } public void draw() { … } public int area() { … } public void sift() { … } } } a = new A(); b = new B(); Heap Static Code Section A a vtable ptr draw A::draw x : int area A::area y : int virtual method table class instance record B B::draw b vtable ptr draw B::sift x : int area y : int sift z : int Dynamic dispatch! Multiple inheritance class A { public: class C : public A, public B { int x; public: virtual void init() { … } int z; virtual void foo() { … } virtual void foo() { … } } virtual void baz() { … } class B { } public: int y; virtual void bar { … } c = new C(); } Heap Static Code Section C (C & A part) c vtable ptr init A::init x : int foo A::foo vtable ptr baz y : int C (B part) B::bar z : int bar C::foo C::baz Multiple inheritance ● Diamond problem – If D inherits from B and C with common ancestor A, and all except D implement method “foo,” which is called? class D : public B, C { public: A void bar() { foo(); // which foo? foo() } } B C C++ solution: use ordering from definition foo() foo() (so B’s foo here) Java solution #1: only inherit multiple interfaces D (so no foo here) bar() Java solution #2: compiler error (Java 8 adds default methods for interfaces) Inheritance and the stack ● How to handle class instance records on stack in case of copying to a superclass variable? – No space for subclass data – Object slicing: remove subclass data – Causes a loss of information! class A { int x; } class B inherits A { int y; } B b = new B(); A a = b; // copy; A’s CIR is smaller // b.y is lost! // no way to cast a back Templates vs. generics ● Templates (C++) – Compiles different versions w/ mangled names ● Generics (Java) – Type erasure: compiler changes generic type to Object and inserts runtime casts (expensive!) – No runtime difference between HashSet<String> and HashSet<Integer> ● Example: no arrays of generics (array members must be type-checked at runtime) – Only one set of static member data template <class T> class Foo<T> { class Foo { T data; T data; void bar(T x) { public: this.data = x; void bar(T x) { } this.data = x; } } } Templates in C++ Generics in Java Reflection ● A language with reflection provides runtime access to type and structure metadata – Sometimes with the ability to modify the structure – Often incurs a severe runtime penalty because of data structures required ● Examples: – Ruby: methods and send – Java: java.lang.Class and java.lang.reflect.Method try { "Hello".send( System.out.println("str".getClass() .getMethod("toUpperCase") "str".methods .invoke("Hello")); .grep(/upcase/)[0]) } catch (NoSuchMethodException ex) {} catch (IllegalAccessException ex) {} catch (InvocationTargetException ex) {} Reflection in Ruby Reflection in Java History of OOP ● Simula (1967): data abstractions for simulation and modeling ● Smalltalk (1980): objects and messages ● C++ (1985): originally “C with classes” ● Java (1995) and C# (2000): goal was “C++ but better” ● Ruby (1996): pure, dynamic OOP language ● Most modern languages have some form of OOP – Abstract data types – Inheritance – Dynamic binding Abstraction in C ● Structs for encapsulation ● void* pointers for polymorphism and inheritance ● Stack or heap allocation (manual management) ● Header file and implementation file ● Function pointers for dynamic dispatch ● No reflection by default https://www.cs.rit.edu/~ats/books/ooc.pdf Abstraction in C++ ● Classes and structs ● Stack or heap allocation ● Manual memory management: constructors and destructors ● Header file and implementation file ● Visibility: public (default for structs) or private (default for classes) – "Friend" functions for private access outside class ● All forms of polymorphism (parametric via templates) ● Static dispatch by default (override via “virtual” keyword) ● Multiple inheritance w/ resolution via inheritance order ● Namespaces for naming and encapsulation ● No reflection by default Abstraction in Java ● Classes similar to C++ ● Single inheritance tree (rooted at Object) ● No stack allocation (everything on heap) ● Automatic memory management ● Visibility modifiers required (public,
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages26 Page
-
File Size-