Chapter Topics - Designing Effective Classes
- An overview of the Date classes in the Java library (skip)
- Designing a Day class (skip)
- Three implementations of the Day class (skip)
- The importance of encapsulation
- Using encapsulation to maintain implementation independence and
consistent objects
- Mutators: guidelines for mutators
- References: prefer immutable references, sharing mutable references
- Side effects: avoid them
- Law of Demeter
- Analyzing the quality of an interface: The Five (or Six) C's
- Programming by contract
- Unit testing
Encapsulation: Key Feature of OO
- OO is as Easy as PIE
- Polymorphism
- Inheritance
- Encapsulation
The Importance of Encapsulation
- Encapsulation uses the principle of information hiding
- Implementation details are hidden so client cannot access
implementation details
- Client should rely on public interface, not private implementation
- Example: Changing Day class from M/D/Y implementation to Julian
implementation (days since 1/1/4713 BC
- What if client accessed public instance variables
- Can't just use text editor to replace all
d.year
with
d.getYear()
- How about
d.year++?
- Possible soution:
- Don't use public fields, even for "simple" classes
- Even a simple class can benefit from different implementations
- Consequences and Benefits of Encapsulation:
- Users do not depend on implementation
- Allows different implementations
- Can change implementation without changing client!
- Client can not corrupt objects!!
- No direct access to fields helps maintain valid objects
- Maintining valid objects
- Constructors create valid objects
- Mutators maintain valid objects
Accessors and Mutators
- Accessor: Reads object state without changing it
- Mutator: Changes object state
- Class without mutators is immutable
- String is immutable
Don't Supply a Mutator for every Accessor
- Day has getYear, getMonth, getDate
accessors
- Day does not have setYear, setMonth,setDate
mutators
- These mutators would not work well
- Example:
Day deadline = new Day(2001, 1, 31);
deadline.setMonth(2); // ERROR
deadline.setDate(28);
- Maybe we should call setDate first?
Day deadline = new Day(2001, 2, 28);
deadline.setDate(31); // ERROR
deadline.setMonth(3);
- GregorianCalendar implements confusing rollover.
- Silently gets the wrong result instead of error.
- Immutability is useful
Sharing Mutable References
Sharing Mutable References
Sharing Mutable References

Final Instance Fields
- Good idea to mark immutable instance fields as final
private final int day;
- However, a final object
reference can still refer to mutating object
private final ArrayList
elements;
- elements can't refer
to another array list
- The contents of the array list can change
- Must protect by not providing accessor and avoiding mutators that
modify
Separating Accessors and Mutators
Separating Accessors and Mutators
Side Effects
- Accessor: no change to object
- Mutator: changes object state
- Side effect: change to another object
- Parameter variable
- Static object
- Avoid side effects--they confuse users
- Good example, no side effect:
a.addAll(b)
mutates a but not b
Side Effects
- Date formatting (basic):
SimpleDateFormat formatter = . . .;
String dateString = "January 11, 2012";
Date d = formatter.parse(dateString);
- Advanced:
FieldPosition position = . . .;
Date d = formatter.parse(dateString, position);
- Side effect: updates position
- Design could be better: add position to formatter state
Side Effects
Law of Demeter

- Example: Mail system in chapter 2, class Connection
Mailbox currentMailbox = mailSystem.findMailbox(...);
- Breaks encapsulation
- Suppose future version of MailSystem uses a database
- Then it no longer has mailbox objects
- Common in larger systems
- Karl Lieberherr: Demeter Project - Law of Demeter
- Demeter = Greek goddess of agriculture, sister of Zeus
Law of Demeter
- The law: A method should only use objects that are
- instance fields of its class
- parameters
- objects that it constructs with new
- Shouldn't use an object that is returned from a method call
- Remedy in mail system: Delegate mailbox methods to mail system
mailSystem.getCurrentMessage(int mailboxNumber);
mailSystem.addMessage(int mailboxNumber, Message msg);
. . .
- Rule of thumb, not a mathematical law
Quality of Class Interface - The 6 (5) C's
- Customers: Programmers using the class
- Criteria:
- Coupling: Minimize Coupling
- Cohesion
- Completeness
- Convenience
- Clarity
- Consistency
- Engineering activity: make tradeoffs
Cohesion
Completeness
Convenience
- A good interface makes all tasks possible . . . and common tasks
simple
- Bad example: Reading from System.in
- Why doesn't System.in have a readLine method?
- After all, System.out has println.
- Why can't I make a BufferedReader from an input stream?
- After all, I can make a PrintWriter from an output
stream.
Clarity
- Confused programmers write buggy code
- Bad example: Removing elements from LinkedList
- Reminder: Standard linked list class
LinkedList countries = new LinkedList();
countries.add("A");
countries.add("B");
countries.add("C");
- Iterate through list:
ListIterator iterator = countries.listIterator();
while (iterator.hasNext())
System.out.println(iterator.next());
Clarity
Consistency
Consistency
- Bad example: String class
s.equals(t) / s.equalsIgnoreCase(t)
- But
boolean regionMatches(int toffset,
String other, int ooffset, int len)
boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len)
- Why not regionMatchesIgnoreCase?
Programming by Contract
- Bertrand Meyer, Eiffel
- Spell out responsibilities
- of client of a class (preconditions)
- of implementor of a class (postconditions)
- Increase reliability
- Increase efficiency
Preconditions
- What if caller attempts to remove message from
empty MessageQueue
- MessageQueue can be written to either
- test for and handle this error in some way (eg throw exception,
return dummy value)
- ignore this possibility (with unpredictable results if it occurs)
- What is better?
Preconditions
- Excessive error by object checking is costly
- Returning dummy values can complicate testing
- Contract metaphor
- Service provider specifies preconditions that specify what is
valid data
- If precondition is fulfilled, service provider must work
correctly
- Otherwise, service provider can do anything
- client is responsible for providing valid data
- When precondition fails, service provider may do anything:
- throw exception
- return false answer
- corrupt data
Precondition Example
/**
Remove message at head
@return the message at the head
@precondition size() > 0
*/
Message removeFirst()
{
return (Message)elements.remove(0);
}
- What happens if precondition not fulfilled?
- IndexOutOfBoundsException
- Other implementation may have different behavior
Circular Array Implementation
- Efficient implementation of bounded queue
- Avoids inefficient shifting of elements
- Circular: head, tail indexes wrap around
- Ch3/queue/MessageQueue.java
Inefficient Shifting of Elements

A Circular Array

Wrapping around the End

Preconditions Must be Checkable
- In circular array implementation, failure of remove
precondition corrupts queue!
- Bounded queue needs precondition for add
- Naive approach:
@precondition size() < elements.length
- Precondition should be checkable by caller
- Better:
@precondition size() < getCapacity()
Assertions
Assertions Example
public Message removeFirst()
{
assert count > 0 : "violated precondition size() > 0";
Message r = elements[head];
. . .
}
Exceptions in the Contract
/**
. . .
@throws IllegalArgumentException if queue is empty
*/
public Message removeFirst()
{
if (count == 0)
throw new IllegalArgumentException();
Message r = elements[head];
. . .
}
- This method has no precondition
- Behavior when queue is empty (ie throw) specified as part of
contract
- Exception throw part of the contract
- Exception throw not result of precondition violation
Postconditions
Class Invariants
- Condition that is
- true after every constructor
- preserved by every method
(if it's true before the call, it's again true afterwards)
- Useful for checking validity of operations
Class Invariants
Class Invariants
- Example: Queue with array list
elements only contains objects of type Message
- Ch2/mail/MessageQueue.java
- It's true for constructor.
elements is initially empty
- Check mutators. Start with add
Parameter type is Message
- What's the use? Casts are correct!
return (Message)elements.remove(0);
Unit Testing
- Unit test = test of a single class
- Design test cases during implementation
- Run tests after every implementation change
- When you find a bug, add a test case that catches it
- JUnit: automates unit testing, free (junit.org), Java based
JUnit

JUnit
- Test class name = tested class name + Test
- Test methods start with test
import junit.framework.*;
public class DayTest extends TestCase
{
public void testAdd() { ... }
public void testDaysBetween() { ... }
. . .
}
JUnit
- Each test case ends with assertion
- Test framework catches assertion failures
public void testAdd()
{
Day d1 = new Day(1970, 1, 1);
int n = 1000;
Day d2 = d1.addDays(n);
assert d2.daysFrom(d1) == n;
}
Example: