Document 7904631
Download
Report
Transcript Document 7904631
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}
mutators
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.
}
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.
Immutable: Safe, but may be inefficient
Create and discard many intermediate objects before
completing computation
This allows more sharing of subparts
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
Use static fields to store information that applies to all
objects of that class
There is a separate set of them for each object
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 new NegativeExponentException(“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; }
private Poly (int n)
//methods
{ trms = new int[n+1]; deg = n;
}
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
Alternative
Very inefficient
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
Often not correct for the object we’re dealing with
Creates a new object of the same type, copies each instance field
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