Data abstractions: ADTs Invariants, Abstraction function Lecture 4: OOP, autumn 2003
Limits of procedural abstractions Isolate implementation from specification Dependency on the types of parameters representation (implementation) interpretation Local changes of a type => global changes in all procedures Hard to modify/maintain software Example: representation change: list -> binary tree interpretation change: sort order Need to isolate representation from specification
Data abstractions abstract from implementation abstract data type (ADT) = <objects, operations> storage structures - type representation applicable operations - behavior access the representation through operations independence from representation locality of change kinds: create, modify, inspect defer choice of representation built-in vs. user-defined types (primitive vs. classes)
Data abstraction specifications define behavior more important than representation specification => what the operations do structure: overview description through known concepts (math) constructors - initialize methods - access mutable vs. immutable data types abstract data type in Java => class: name, constructors, methods (public) belong to objects (class instances) this implicit argument for all methods
List class example: /** * A mutable ordered collection of objects L (sequence), * for example L = <O 1,,O n >. * A list L is a mapping: L : {O 1, O n } -> {0,,n-1}, where * the i-th element is denoted as elem(i) */ public class List { public List (); public void insert (int i, Object e); public void set(int i, Object e); public void remove(int i); public int size(); public Object get(int i); public List sublist(int i, int j); }
Classes of operations creators: create new objects of a type (some constructors) producers: create new objects from existing objects (constructors, methods - e.g. concat, substring) mutators: modify objects (methods) observers: provide information about objects (methods) in summary (where: t other types, T current ADT) : creator: t -> T producer: T -> T mutator: T, t -> void observer: T, t -> t
List class example: public void get(int i) throws IndexOutOfBoundsException; // effects: throws IndexOutOfBoundsException, if i < 0, i >= n // else returns i-th element of this public void insert(int i, Object e) throws IndexOutOfBoundsException; // modifies: this // effects: throws IndexOutOfBoundsException if i < 0, i > n, // else changes this to <O 1,,e i,,o n > public void set(int i, Object e) throws IndexOutOfBoundsException; // modifies: this // effects: throws IndexOutOfBoundsException if i < 0, i >= n // else elem(i) = e public List sublist(int i, int j) throws IndexOutOfBoundsException; // effects: throws IndexOutOfBoundsException if i < 0, i > j, j >= n
Immutable: class typename { 1. overview 2. creators 3. observers 4. producers } Mutable: class typename { 1. overview 2. creators 3. observers 4. mutators } Designing abstract data types Mutability modeled concept (integers vs. sets) safety, sharing, and performance Operations - simple building blocks, easy to combine Adequacy - sufficient set of operations depends on potential uses fully populated types - obtain all possible states minimal set of operations
Implementing data types choice of representation, e.g. linked list - faster insert array - faster get, set representation independence, errors: exposing the representation return mutable objects use mutable objects In Java instance variables (mutable vs. immutable) internal methods records - collections of fields (no abstraction, no special operations) language support for independence
Language support visibility private - local to class package - local to package (protected - later) interfaces - declare only operations public interface List { void add (int i, Object e); void set (int i, Object e); void remove (int i); int size (); Object get (int i); } public class LinkedList implements List {...} public class ArrayList implements List {...} List lst = new LinkedList();
Standard operations Inherited from Object equals() - behavioral equivalence clone() independent copies copy object: o.clone.equals(o) == false shallow vs. deep tostring() - represent current object state as text hashcode() map objects to integers used in hash tables May have to redesign for new classes
Object identity Compare by reference or by state? Mutable objects - == Immutable objects - equals Object default - by reference Wrong for immutable types - need deep equals Properties of equals symmetric: a.equals(b) => b.equals(a) reflexive: a.equals(a) == true transitive: a.equals(b) && b.equals(c) => that a.equals(c) null preserving: if a!= null a.equals(null) is false consistent: if a.equals(b) now a.equals(b) later if neither has been modified
Understanding and reasoning about data abstractions Not always clear what is represented Not all values for a representation are reasonable abstraction function (AF) specify the interpretation of objects mapping: AF : Object -> AbstractObject rep invariant (RI) characterize well-formed instances mathematical formula: RI : Object -> boolean
Abstraction function Sets Lists many-to-one implemented as tostring
Representation invariant Integral part of representation Formal or informal description Can be violated inside methods LinkedList example: Add size field size == n Express all constraints on which methods depend Can be violated inside methods Implemented as a method repok Can be used in assertions
Reasoning about data types Modular reasoning Inductive reasoning - ensure that constructors produce valid objects => mutators and producers preserve invariants Assume input is valid