280 likes | 385 Views
Comp 302: Software Engineering. Data Abstractions. Data Abstraction. data abstraction = <objects,operations> Why data abstractions? When the implementation of the abstraction changes, programs that use it don’t have to change. Only access the object through methods it provides
E N D
Comp 302: Software Engineering Data Abstractions
Data Abstraction data abstraction = <objects,operations> Why data abstractions? • When the implementation of the abstraction changes, programs that use it don’t have to change. • Only access the object through methods it provides • Can avoid making implementation decisions too early • Avoid inefficiencies and massive re-implementation • Can first define the abstract type with its operations • Can then work on using modules • Make implementation decisions later
Outline • How to specify data abstractions? • How to implement data abstractions?
Specifications for Data Abstractions visibility class dname { // OVERVİEW: A brief description of the // behaviour of the type’s objects goes here //constructors //specs for constructors go here //methods //specs for methods go here }
Specification of IntSet (code filled in later) public class IntSet { //OVERVİEW: IntSets are mutable, unbounded sets of integers //A typical IntSet is {x1,...,xn}. //constructors public IntSet ( ) //EFFECTS: Initialize this to be empty. (no need for MODIFIES clause) //methods public void insert (int x) //MODIFIES: this //EFFECTS: Adds x to the element of this, // i.e., this_post = this + {x} public void remove (int x) //MODIFIES: this //EFFECTS: Removes x from this, i.e., this_post = this – {x} public boolean isIn (int x) //EFFECTS: If x is in this returns true else returns false public int size () //EFFECTS: Returns the cordinality of this public int choose () throws EmptyException //EFFECTS: If this is empty, throws EmptyException // else returns an arbitrary element of this. } mutators observers
Mutability • States of immutable objects never change • They are created and they stay that way until destroyed • Example: Strings • Huh? What about String myFirstName = “Serdar”; String myLastName = “Tasiran”; String myFullName = myFirstName + “ “ + myLastName; • A new String object is created. The String with “Serdar” in it is never changed. • Mutable objects: • Example: Arrays. • a[i] = 5; • If a mutable object is shared, a modification of one modifies the other.
public class Poly { //OVERVIEW: Polys are immutable polynomials with integer coefficients. //A typical Poly is c0 + c1x + c2x2+ ... + cnxn //constructors public Poly () //EFFECTS: Initializes this to be zero polynomial public Poly (int c, int n) throws NegativeExponentException //EFFECTS: If n<0 throws NegativeExponentException else initalizes this// to be the Poly cxn. //methods public int degree () //EFFECTS: Returns the degree of this, i.e., the largest exponent with a //non-zero coefficient. Returns 0 if this is zero Poly. public int coeff (int d) //EFFECTS: Returns the coefficient of the term of this whose exponent is //d. public Poly add (Poly q) throws NullPointerException //EFFECTS: If q is null throws NullPointerException else returns the Poly //this +q. public Poly mul (Poly q) throws NullPointerException //EFFECTS: If q is null throws NullPointerException else returns the Poly //*q. public Poly sub (Poly q) Throws NullPointerException //EFFECTS: If q is null throws NullPointerException else returns the Poly //this –q. public Poly minus () //EFFECTS: Returns the Poly – this. }
Design Issues: Mutability • When to make a data type mutable, when not to. • Type should be immutable when its elements would naturally have unchanging values • We’ll talk more on this later, but in general, when modeling real-world objects, types should be mutable • Mostly mathematical or other symbolic objects are not mutable. • This allows more sharing of subparts • Immutable: Safe, but may be inefficient • Create and discard many intermediate objects before completing computation • Lots of garbage collection • Mutable: Less garbage collection, less safe.
Using Data Abstractions public static Poly diff (Poly p) throws NullPointerExcepyion { //EFFECTS: If p is null throws NullPointerException //else returns the Poly obtained by differentiating p. Poly q = new Poly (); for (int i = 1; i <= p.degree(); i++) q = q.add(new Poly(p.coeff(i)*i, i - 1)); return q; } public static IntSet getElements (int[] a) throws NullPointerException { //EFFECTS: If a is null throws NullPointerException //else returns a set containing an entry for each //distinct element of a. IntSet s = new IntSet(); for (int i = 0; ,< a.length; i++) s.insert(a[i]); return s; }
Implementing Data Abstractions • Must select a representation (rep). Examples: • A Vector (from java.util) of Integer objects is a possible rep for IntSet • Reps must • Support all operations in a simple way • Provide efficient implementations • Searching an entry should not require looking at all entries, ideally.
A Rep for IntSet • Should we allow each element to occur more than once or not • If we do, insertion is simple: Just add it at the end of the Vector • But remove and isIn take a long time • isIn is likely to be called a lot • Forbid duplicates in Vector
Implementing Data Abstractions • A representation typically has several components • Correspond to (non-static) fields in the class definitions • These are also called instance variables • There is a separate set of them for each object • Use static fields to store information that applies to all objects of that class • Example: The number of instances created. • Instance variables must not be visible to users, other classes • Make them private • Provide methods to access and modify them
public class IntSet { //OVERVIEW: IntSets are unbounded, mutable sets of integers. private Vector els; // the rep //constructors public IntSet () { //EFFECTS: Initializes this to be empty els = new Vector(); } //methods public void insert (int x) { //MODIFIES: this //EFFECTS: Adds x to the elements of this. Integer y = new Integer(x); if (getIndex(y) < 0) els.add(y); } public void remove (int x) { //MODIFIES: this //EFFECTS: Removes x from this. int i = getIndex(new Integer(x)); if (i < 0) return; els.set(i, els.lastElement()); els.remove(els.size() - 1); } public boolean isIn (int x) { //EFFECTS: Returns true if x is in this else returns false. return getIndex(new Integer(x)) } (Continued)
private int getIndex (Integer x) { //EFFECTS: If x is in this returns index where //x appears else returns -1. for (int i = 0; i < els.size(); i++) if (x.equals(els.get(i))) return i; return -1; } public int size () { //EFFECTS: Returns the cardinality of this. return els.size(); } public int choose () throws EmptyException { //EFFECTS: If this is empty throws EmptyException else //returns an arbitrary element of this. if(els.size() == 0) throw new EmptyException(“IntSet.choose”); return els.lastElement(); } }
public class Poly { //OVERVIEW: ... private int[] trms; private int deg; //constructors public Poly () { //EFFECTS: Initilizes this to be the zero polynomial. trms = new int[1]; deg = 0; } public Poly (int c, int n) throws NegativeExponentException { //EFFECTS: If n < 0 throws NegativeExponentException // else initializes this to be the Poly cxn. if (n < 0) throw newNegativeExponentException(“Poly(int,int) constructor”); if (c == 0) { trms = new int[n+1]; deg = n; } trms = new int [n+1]; for (int i = 0; i < n; i++) trms[i] = 0; trms[n] = c; deg = n; }
privatePoly (int n) { trms = new int[n+1]; deg = n; } //methods public int degree () { //EFFECTS: Returns the degree of this, i.e., // the largest exponent with a non-zero coefficient. // Returns 0 if this is the zero Poly. return deg; } public int coeff (int d) { //EFFETCS: Returns the coefficient of the // term of this whose exponent is d. if (d < 0 || d > deg) return 0; elsereturn trms[d]; } public Poly sub (Poly q) throws NullPointerException { //EFFECTS: If q is null throws // NullPointerException else returns add (q.minus()); } public Poly minus () { //EFFECTS: Returns the Poly –this. Poly r = new Poly(deg); for (int i = 0; i < deg; i++) r.trms[i] = - trms[i]; return r; }
public Poly add (Poly q) throws NullPointerException { //EFFECTS: If q is null throws NullPointerException // else returns this +q. Poly la, sm; if (deg < q.deg) {la = this; sm = q;} else {la = q; sm = this;} int newdeg = la.deg; //new degree is the larger degree if (deg == q.deg) //unless there are trailing zeros for (int k = deg; k > 0; k--) if (trms[k] + q.trms[k] != 0) break; else newdeg--; Poly r = new Poly(newdeg); //get a new Poly int i; for (i = 0; i <= sm.deg && i <=newdeg; i++) r.trms[i] = sm.trms[i] + la.trms[i]; for (int j = i; j <= newdeg; j++) r.trms[j] = la.trms[j]; return r; }
public Poly mul (Poly q) throws NullPointerException { //EFFECTS: If q is null throws NullPointerException // else returns the Poly this *q. if ((q.deg == 0 && q.trms[0] == 0) || (deg == 0 && trms[0] == 0)) return new Poly(); Poly r = new Poly(deg+q.deg); r.trms[deg+q.deg] = 0; //prepare to compute coeffs for (int i = 0; i <= deg; i++) for (int j = 0; j <= q.deg; j++) r.trms[i+j] = r.trms[i+j] + trms[i] * q.trms[j]; return r; }
Implementation Decisions • Suppose our array is sparse • 2x1001 – x2 + 3x– 1 • We would have an array with 999 zeros • Very inefficient • Alternative • Store only non-zero coefficients and their associated exponents • private Vector coeffs;private Vector exps; • Problem: Must keep the two vectors lined up. • Better to have one Vector, with “records” representing (coeff, exp) pairs. class Pair { //OVERVIEW: A record type int coeff; int exp; Pair(int c, int n) { coeff = c; exp = n; } } // No spec needed: Package accessible fields.
Records Instead of public int coeff (int x) { for (int i = 0; i < exps.size(); i++) if (((Integer) exps.get(i)).intValue() == x) return ((Integer) coeff.get(i)).intValue(); return 0; } We now have private Vector trms; // the terms with non zero coefficients public int coeff (int x) { for (int i = 0; i < trms.size(); i++) { Pair p = (Pair) trms.get(i); if (p.exp == x) return p.coeff; } return 0; }
Methods Inherited from Object • All classes are subclasses of Object • They • either provide implementations for all of Object’s methods • equals, clone, toString, … • or inherit Object’s version of these methods • Two objects should be equals • if they are behaviorally equivalent, i.e., • cannot distinguish them using the object’s own methods • Distinct mutable objects are always distinguishable IntSet s = new IntSet(); IntSet t = new IntSet(); if (s.equals(t)) ...; else ...
cloneing • Makes a copy of its object • The copy should have the same state • Object defines a default implementation • Creates a new object of the same type, copies each instance field • Often not correct for the object we’re dealing with • For IntSet, the two copies would share the els Vector. • If one is modified, the other will be also • Must generate independent copy • For immutable objects, it is OK to share fields • Fields never get changed • In general, immutable objects should inherit from Object, mutable ones should provide their own implementation. • Usage: • visibility class Classname implements Cloneable { }
public class Poly implements Cloneable { //as given before, plus public boolean equals (Poly q) { if (q == null || deg != q.trms.length) return false; for (int i = 0; i <= deg; i++) if (trms[i] != q.trms[i]) return false; return true; } public boolean equals (Object z) { if(!(z instanceof Poly)) return false; return equals((Poly) z); } } public class IntSet { //as given before, plus private IntSet (Vector v) { els = v; } public Object clone () { return new IntSet((Vector) els.clone()); } } • If you invoke clone() on an object that doesn’t implement Cloneable, the clone() method inherited from Object throws the CloneNotSupportedException.
Typecasting Clones IntSet t = (IntSet) s.clone();
The toString method • toString() returns a String that represents the state of the object. • The default method provided by Object prints the type name and hash code. • Not very useful • Must provide our own toString() implementation • Examples: IntSet: {1, 7, 3} Poly: 2 + 3x + 5x^2 • IntSet implementation: public String toString () { if (els.size() == 0) return “IntSet:{}”; String s = “IntSet: {“ els.elementAt(0).toString(); for (int i = 1; i < els.size(); i++) s = s + “ , “ + els.elementAt(i).toString(); return s + “}”; }
Unknowingly Exposing the Rep: BAD!!! public Vector allEls() //EFFECTS: Returns a vector containing the //elements of this, each exactly once, in //arbitrary order { return els; } public IntSet (Vector elms) throws NullPointerException//EFFECTS: If elms is null throws //NullPointerException else initializes this to //contain as elements all the ints in elms. { if (elms == null) throw new NullPointerException (“IntSet 1 argument constructor”); els = elms; }
Operation Categories • Creators: • Create an object from scratch • A constructor with no arguments • Producers: • Take object(s) of your own type • Generate a new one • Examples: Constructors with arguments of same type, mul method of Poly (returns Poly) • Mutators: • Modifies objects of its type : insert, delete for IntSet • Observers: • Reports something about its current state
Adequacy • Provide enough operations • Everything that the user of your class wants can be done • Simply: a few method calls at most • Efficiently: doesn’t take a long time to complete • Type must be fully populated • Must be possible to obtain all possible abstract states using methods • Example: Must be able to get any IntSet • But don’t provide too many operations • Complicated to understand • Difficult to maintain • If rep changes, you have to fix a lot of methods