C SC 225 Lecture 3: Designing a Class
[ previous
| schedule
| next ]
Alternate Implementations
- Textbook presents 3 different implementations of Day class
- Compare them with respect to
- Big-O complexity, method by method
- understandability of code
- space requirements
- time-space tradeoff
- All implementations hidden from client
- Through information hiding and encapsulation, implementation can be changed from one release to next
Defining an ordering over a class
- Will you or a client need to sort objects of this class?
- If so, you need to define an ordering
- A partial ordering covers some but not all possible values
- A total ordering applies to all possible values
- For instance, the Integer class defines a total ordering over all 32 bit integer values
- The String class defines a total ordering over all possible strings
- Relation R is a total ordering if, for all values x, y, and z:
- x R x (reflexive)
- x R y R z => x R z (transitive)
- x R y R x => x = y (antisymmetric)
- We've seen the Comparable and Comparator classes before and will revisit them in greater detail later
Non-public Classes
- If class is defined only to help implement another class, it should not be public
- The helper class should be defined in the same file as the class it helps -- keep 'em close for better encapsulation
- File is limited to one public class (same name as file), but may also contain any number of non-public classes
- Non-public does not mean it has to be inner or nested or local or anonymous class, although it can be
- nested class: any class defined inside another class
- inner class: a non-static nested class (i.e. class not declared static)
- local class: inner class defined within a method
- anonymous class: local inner class with no name
- An inner class has access to all members of its enclosing class
- Static nested class has access only to static members of its enclosing class
- An instance of an inner class can exist only in within an instance of its enclosing class
- Class defined within a method has access to "final" parameters and locals of that method (more below)
- You've probably encountered anonymous classes when writing action listeners.
Jia on organizing class members
- See schedule page for full reference to Jia's textbook
- Suggests that class definition groups members in this order:
- public constructors
- public accessors
- public mutators
- non-public fields
- auxiliary methods and nested classes
- To me, it is more important that:
- like members are grouped together
- consistent ordering is used across classes
- all fields are declared together
- Overriding concern: separate interface from implementation
Jia on Canonical form of a class
- See schedule page for full reference to Jia's textbook
- canonical: standard or authoritative method (derived from "canon" the laws of the church)
- Jia's canonical form:
- Override the default public no-arg constructor
- Override the default equals() and hashCode() methods (remember them?)
- Override the default toString() method
- Implement Cloneable and override the default clone() method
- If the object may be written to and read from a file or network, implement Serializable and implement private readObject() and writeObject() methods
- "Don't attempt this at home": The methods for cloning and serialization are difficult to implement correctly. Be sure to have a good reference.
- We will re-visit selected of these topics in Horstmann chapter 7.
Public Fields
- terminology: field is Java-speak for instance or class (static) variable
- Normally, fields are private
- Why avoid public fields?
- What about static final? (technically not a variable)
Private Methods
- Normally, methods are public or protected
- private methods a.k.a. helper methods
- Why have private methods?
- Their specification may be implementation-specific
- Why is it hard to change a public method to a private one?
- Why is it not recommended to change a private method to a public one?
Deprecated Methods
- Find an example in Java standard API
- What does this mean?
- What caused it to become deprecated?
- Why does it remain in the class?
Immutable Classes
- What does immutable mean?
- What is an immutable class?
- String is prominent example of immutable class
- ArrayList is prominent example of mutable class
- Object of immutable class can be safely shared
- Relate this to accessor and mutator methods
- Does absence of mutator methods render the class immutable?
- What if accessor method returns mutable field?
- You may not have control over design of your fields' classes
- E.g., if field is of mutable class, you may not be able to make it immutable
- Is it safe to return field that has primitive type?
- Are the wrapper classes for the primitives (e.g. Integer) immutable?
- You can always protect your field by having accessor return a clone
- How do static methods relate to immutability?
- How does the final modifier relate to immutability?
- How do you feel about using your mutable fields as arguments in method calls?
final in Java
- final class - cannot be extended
- final method - cannot be overridden
- final field - can only be assigned once (NOTE: if field is reference, the reference is protected but not the object it references unless that object is immutable)
- final parameter - identifier cannot be assigned inside the method. (NOTE: if
parameter is reference, the reference is protected but not the object it references
unless that object is immutable). From the client's viewpoint, a final parameter is meaningless because it affords
no additional protection under Java's "pass by value". From the method's viewpoint, however, a final parameter permits
an inner class defined within that method to access the parameter.
- final local variable - can only be assigned once. Also becomes accessible to inner class defined within method.
const in C++
- Not the same as final in Java
- const in C++ has different interpretations depending on context of its use
- Different C++ declarations for a String:
- Thing const * me = new Thing(); (me can be altered but not the object itself)
- Thing * const me = new Thing(); (me cannot be altered but the object itself can)
- const Thing * const me = new Thing(); (neither me nor the object can be altered)
- Java declaration: final Thing me = new Thing(); is equivalent to second C++ declaration
- Different interpretations are possible in C++ because the pointer and object are both explicit
whereas in Java the pointer (reference) is not.
- Here's a worst case C++ example method declaration and explanation from
http://duramecho.com/ComputerInformation/WhyHowCppConst.html
- const int*const Method3(const int*const &)const;
- The 5 uses of const from left to right, respectively mean that
- the variable pointed to by the returned pointer won't be alterable
- the returned pointer itself won’t be alterable
- the method does not alter the variable pointed to by the given parameter pointer
- the method does not alter the parameter pointer itself
- the method does not alter the object through which it is called
- You get multiple levels of protection but is confusing to use it correctly.
- Usage #4 in the list is equivalent to Java final parameter
Separate roles of Accessors and Mutators
- Accessor method should not change object state - if you call it twice in
a row it should return same value both times.
- Mutator method should not return anything, specifically object state
- Both are guidelines and not laws; there are situations that justify violation:
- Iterator has next() method suggests both accessing a
value and modifying the iterator. However, most implementations of this
interface do not use it as a pure accessor since they access the associated
(but external) Iterable, not their own state.
- Scanner, which implements Iterator, represents an internal iterator (the iterator
is part of the thing it is iterating over) and its next() accessor method also mutates.
- The Stack class's pop() method both modifies the stack and returns the
popped value. Justifiable? Would it be preferable to replace this with a pair of methods, one
a pure accessor and the other a pure mutator? (what about peek()?)
- Related issue: methods with side effects
- side effect: observable data modification
- Example: The assignment operator (= in C/C++/Java) both produces a value, the value assigned, and has a
side effect, storing that value into the variable. If you access the variable both before and after the assignment, its value may be different.
- purely functional languages do not allow this (they calculate and return a value)
- Most functional languages have been extended to permit it
- Empirical (e.g. C and Fortran) and OO languages allow this because they
are abstraction of the computer
- How does this relate to immutability?
- Some side effects are pleasant; others unpleasant and unexpected.
- Side effects inhibit ability to reason about a program or prove its correctness
- Possible side effects of a method:
- Modifies state of reference parameter
- Modifies state of object through which it was called (Horstmann calls this "implicit parameter")
- Modifies value of static variable in its class
- Modifies state of a different object (none of the above) that it obtains internally.
- Note that changes to local variables do not count because they exist only during the call
- How would you rank those 4 side effects on a scale from most to least acceptable?
- Related to "Law of Demeter" (see next topic)
Law of Demeter
- Pronounced dih-MEE-tur
- a.k.a. Principle of Least Knowledge
- a.k.a. "Only talk to your immediate friends"
- Developed at Northeastern University (Boston) in 1987 (reference)
- Demeter website: http://www.ccs.neu.edu/research/demeter/
- Relates to Aspect-Oriented Programming (AOP) as well as to OO
- AOP concept originated at Xerox PARC
- Software consists of loosely coupled "crosscutting" concerns
- A cross-cutting concern is one that is used in many classes (and therefore breaks encapsulation)
- In a banking system, "funds transfer" is an example of a cross-cutting concern
- major language is AspectJ, an extension of Java
- Law of Demeter says that methods should only use:
- instance fields of its class (if field is collection, this includes collection elements)
- its parameters
- objects that it constructs
- By implication, methods should not:
- use objects that it gets from another object
- return a reference to an object that it contains (flip side of previous item)
- use global objects
- Use of "objects it gets from other objects" can be avoided by delegating responsibility
to the other objects
- Again, these are guidelines, despite use of the word "law"
- Following these guidelines reduces coupling, improves maintainability and adaptability of the software
Class quality from client's viewpoint
- Client is anyone who uses the class that you've designed/programmed
- Who are clients of the Java J2SE API?
- Class specification is the client interface to the class.
- Unless clients have both access to the source code AND motivation, all they will know is its specification (write good javadoc!)
- What does class specification consist of?
- Horstmann presents "5 C's" of specification
- cohesion: Methods should support a coherent purpose (that of the class :)
- completeness: Methods should be provided for the kinds of operations that clients associate with the class' abstraction
- convenience: Borrowed guideline from user interface design: "frequent tasks should be easy, infrequent
tasks should be possible." The former relates to convenience, the latter to completeness.
- clarity: Specification should not cause confusion, which leads
to inadvertent misuse (and therefore reduced productivity). Clarity relates
to:
- Clear explanation through comments.
- Ease of method use, particularly with respect to parameters.
- Clarity of method purpose.
- Class/method behavior that accurately reflects the client's mental
model of the abstraction.
Text example of ListIterator and mental model of word processor:
the add() method inserts new element before the "current cursor
position", just like typing text, but the remove() method does
not model the action of the the backspace key (which would remove the
element preceding the cursor). To quote from the API: "Note that the
remove() and set(Object) methods are not defined in terms of the
cursor position; they are defined to operate on the last element returned
by a call to next() or previous()."
- consistency: Consistent behaviors, naming, parameter usage and terminology, across the methods. This reduces mental load on client.
- Horstmann notes that some of the 5 C's conflict with others. Example? Completeness and convenience?
- All of these are important to client's "readiness-to-hand" (term
coined by the philosopher Heidigger and applied to UI design by Terry Winograd).
Ability to focus on accomplishing a task without thought of the tool you are
using. Classic example: The task of hammering a nail requires a hammer. While
hammering you focus on the task not the hammer; readiness-at-hand is achieved.
But if you hammer your thumb, readiness-at-hand is broken (any maybe your thumb also)!
- True readiness-at-hand is difficult to achieve when using a class (the hammer) to accomplish
the task at hand. But it can happen.
Contract between class designer/implementer and its client
- A.k.a., programming by contract
- We touched on this, if indirectly, in CSC 120/160/205 (probably method pre-conditions)
- The major concepts, as applied to a method, are:
- preconditions: conditions that client must assure are met before calling the method.
- postconditions: conditions that implementer must assure are met upon method return, assuming preconditions were met.
- class invariants: conditions that implementer must assure will hold for all objects of a class before and after each method call.
- Note the responsibilities of the client and of the implementer. That's the contract.
- Is the implementer responsible for checking preconditions or taking a particular action if they are not met?
- Is the implementer responsible for providing the client a way (e.g. public methods) to check preconditions before calling the method?
- Suppose you (designer/implementer) want to check preconditions. How?
- if-then statement
- assert statement (Java)
- assert condition ; // If condition is false, will throw new AssertionError()
- assert condition : details ; // If condition is false, will throw new AssertionError(details)
- In the second form, details can be any value and will be converted to a String
- Assertions can be enabled/disabled using compile command switch (which
means they can be enabled for some classes and not others)
- Note: the class is called AssertionError and not AssertionException because it
is not a subclass of Exception. It does however implement Throwable.
- Suppose you check preconditions. What should you do when violation occurs?
- Act as if you never checked and let the chips fall where they may. But then why check?
- Return a special or weird value ;-)
- Throw an exception. The assert statement will do this.
You can throw an IllegalArgumentException if that applies. Or use another exception
class or roll your own if appropriate.
- If you take any action (return a special value or throw an exception) in
response to a precondition violation, this needs to be documented in the specification
(contract).
- Note that Javadoc does not define @precondition or @postcondition tags. You can
specify special tags using a javadoc command switch, or format your comments directly using HTML tags
- Note the performance penalty associated with checking preconditions.
- Note that class invariants need to be checked only at the end of constructors
and mutators, and serve only as a self-check by the implementer.
- Include in the contract any invariants affecting the public part of the class
- What to do if class invariant fails?
Testing your class
- Testing in general is a recurring topic in our curriculum
- Purpose of testing is to reveal bugs
- Testing can confirm the presence of bugs but not the absence.
- There are many kinds of testing (more details in my CSC 325 notes)
- Focus on testing the design/implementation of a single class in isolation
- walkthrough: a form of static testing, in which the designer/programmer
explains the class design/code step by step to development team members while all are looking at the documents.
Can catch many bugs this way.
- unit test: a form of dynamic testing, in which the class code under test is executed with
predefined test cases and expected results.
- Both well suited to agile development processes and extreme programming with programming pairs
- JUnit (www.junit.org) is a popular tool for Java unit testing
- JUnit is an open source regression testing framework developed by Erich Gamma and Kent Beck.
- Regression testing is retesting previously-working software after it has been modified.
- Erich Gamma is one of the "Gang of Four" who created Design Patterns
- Kent Beck created Extreme Programming
- In its raw form, JUnit is a bit clunky to use. Friendlier version available as Eclipse plug-in.
- We'll do some lab exercises with it.
[ C
SC 225 | Peter
Sanderson | Math Sciences server
| Math Sciences home page
| Otterbein ]
Last updated:
Peter Sanderson (PSanderson@otterbein.edu)