Consider Bloch's
InstrumentedHashSet
,
ForwardingSet
,
and
InstrumentedSet
examples:
// Broken: Inheritance violates encapsulation!
// Approach is not general; 1) limited to specific class, 2) only works for mutable types
public class InstrumentedHashSet<E> extends HashSet<E>{
private int addCount = 0;
public InstrumentedHashSet() {}
@Override public boolean add(E e){
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c){
// What to do with addCount?
return super.addAll(c);
}
public int getAddCount(){ return addCount; }
}
// Re-usable wrapper uses composition instead of inheritance
// Note power of the implemented interface
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s){ this.s = s; }
@Override public boolean add(E e) { return s.add(e); }
@Override public boolean remove(Object o){ return s.remove(o); }
@Override public boolean equals(Object o){ return s.equals(o); }
@Override public int hashCode() { return s.hashCode(); }
@Override public String toString() { return s.toString(); }
// Other forwarded methods from Set interface omitted
}
// Implementation details in underlying set are encapsulated again
public class InstrumentedSet<E> extends ForwardingSet<E>{
private int addCount = 0;
public InstrumentedSet(Set<E> s){ super(s); }
@Override public boolean add(E e){ addCount++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size(); return super.addAll(c);
}
public int getAddCount(){ return addCount; }
}
Consider also the following client code:
Set<String> r = new HashSet<String>();
r.add("ant"); r.add("bee");
Set<String> sh = new InstrumentedHashSet<String>();
sh.addAll(r);
Set<String> s = new InstrumentedSet<String>(r);
s.add("ant"); s.add("cat");
Set<String> t = new InstrumentedSet<String>(s);
t.add("dog");
r.remove("bee"); s.remove("ant");
-
How do you think the
addCount
variable
should be updated in the
addAll()
method in InstrumentedHashSet
?
-
Why is this an unpleasant question? Is the answer documented anywhere? Should it be?
-
What does the answer say about inheritance?
-
Does
equals()
behave correctly
in InstrumentedHashSet
?
-
Given your previous answer, what is the value of
sh.addCount
at the end of the computation?
-
Consider the
InstrumentedSet
solution.
Besides being correct (always a plus!) why is it more general than the
InstrumentedHashSet
solution?
-
At the end of the computation,
what are the values of:
r
, s
, and t
?
- What would a call to
s.getAddCount()
return at the end of the computation?
-
At the end of the computation,
what are the values of:
r.equals(s)
, s.equals(t)
, and t.equals(s)
?
- Are there any problems with the
equals()
contract?
- Would this still work if you globally replaced sets with lists?
- Would this still work if you globally replaced sets with collections?
Note: There is a lot going on in this example.
I highly recommend that you play with the code until you understand it.