SE450: Lecture 5 (Patterns/Canonical form of classes)

Contents [0/22]

Overview of Today's Class [1/22]
Homework Solution - HW3 [2/22]
Homework Solution - HW4 [3/22]
JUnit: Patterns [4/22]
Patterns: Template Method Example (another one) [5/22]
Patterns: The Command Pattern [6/22]
Patterns: Command Example [7/22]
Patterns: The Composite Pattern [8/22]
Patterns: Composite Example [9/22]
Inner Classes: Definitions and Static [10/22]
Inner Classes: Non-static [11/22]
Inner Classes: Local Inner Classes [12/22]
Inner Classes: Anonymous Inner Classes [13/22]
Java classes: The canonical form of classes [14/22]
Java classes: 0-arg constructor [15/22]
Java classes: Equality [16/22]
Java classes: Hash Code [17/22]
Java classes: clone() [18/22]
Java classes: cloning implementation [19/22]
Java classes: toString [20/22]
Java classes: Serialization [21/22]
Quiz Questions? [22/22]

Overview of Today's Class [1/22]

Homework Solutions (HW3 and HW4)

Composite and Command Patterns

Inner Class Idiom and Anonymous Inner Classes

The Canonical form of classes

Assertions in java

Design By Contract

Project Overview

Refactoring

Quiz questions

Homework Solution - HW3 [2/22]

One way to do the homework assignment.

The files are here.

Homework Solution - HW4 [3/22]

One way to do the homework assignment.

The TestCase (I wrote this one first) FlightTest.java.

The implementation. Flight.java.

One note on Comparable.... if compareTo isn't consistent with equals

JUnit: Patterns [4/22]

JUnit is actually a great demonstration of a number of patterns from GoF. We will look at examples of Template Method(325), Composite (163) and the Command (233) patterns. JUnit also uses Adapter (139).

Patterns: Template Method Example (another one) [5/22]

GenericClass: TestCase

ConcreteClass: VectorTest

public abstract class TestCase extends Assert implements Test {
	/**
	 * Runs the bare test sequence.
	 * @exception Throwable if any exception is thrown
	 */
	public void runBare() throws Throwable {
		setUp();
		try {
			runTest();
		}
		finally {
			tearDown();
		}
	}
	/**
	 * Sets up the fixture, for example, open a network connection.
	 * This method is called before a test is executed.
	 */
	protected void setUp() throws Exception {
	}
	/**
	 * Tears down the fixture, for example, close a network connection.
	 * This method is called after a test is executed.
	 */
	protected void tearDown() throws Exception {
	}
}

Patterns: The Command Pattern [6/22]

Patterns: Command Example [7/22]

Participants and Collaborators

public interface Test {

	/**
	 * Runs a test and collects its result in a TestResult instance.
	 */
	public abstract void run(TestResult result);

}        

public abstract class TestCase extends Assert implements Test {
 
       /**
	 * Runs the test case and collects the results in TestResult.
	 */
	public void run(TestResult result) {
		result.run(this);
	}

}

public class TestRunner extends BaseTestRunner {
	public TestResult doRun(Test suite, boolean wait) {
		TestResult result= createTestResult();
		result.addListener(fPrinter);
		long startTime= System.currentTimeMillis();
		suite.run(result);
		long endTime= System.currentTimeMillis();
		long runTime= endTime-startTime;
		fPrinter.print(result, runTime);

		pause(wait);
		return result;
	}
}

Patterns: The Composite Pattern [8/22]

Patterns: Composite Example [9/22]

Participants and Collaborators

public interface Test {
	/**
	 * Runs a test and collects its result in a TestResult instance.
	 */
	public abstract void run(TestResult result);
}

public abstract class TestCase extends Assert implements Test {
	/**
	 * Runs the test case and collects the results in TestResult.
	 */
	public void run(TestResult result) {
		result.run(this);
	}
}

public class TestSuite implements Test {
	/**
	 * Adds a test to the suite.
	 */
	public void addTest(Test test) {
		fTests.addElement(test);
	}
	/**
	 * Runs the tests and collects their result in a TestResult.
	 */
	public void run(TestResult result) {
		for (Enumeration e= tests(); e.hasMoreElements(); ) {
	  		if (result.shouldStop() )
	  			break;
			Test test= (Test)e.nextElement();
			runTest(test, result);
		}
	}
}

Inner Classes: Definitions and Static [10/22]

In Java, classes and interfaces can be members of other classes and interfaces. These are considered nested classes and nested interfaces. They are part of the contract of their enclosing type.

Nested classes and interfaces can be assigned the same access as other members of a class.

The simplest nested class to talk about is a static nested class - technically called a top-level nested class (but this is a terribly confusing name). Static nested classes aren't inner classes.


public class BankAccount {
  private long number;
  private long balance; 

  public static class Permissions {
    public boolean canDeposit,
                   canWithdraw,
		   canClose;
  }
  // ...
}

The full name of this class is BankAccount.Permissions.

static inner classes can be intantiated without creating an instance of the outer class. They make sense when used as a namespace mechanism more than anything. They can have any level of accessibility

Inner Classes: Non-static [11/22]

When talking about non-static nested classes, we refer to them as inner classes. You need an instance of the outer class to instantiate the inner class.

For example:

public class BankAccount {
  private long number;
  private long balance;
  private Action lastAct;

  public class Action {
    private String act;
    private long amount;
    Action(String act, long amount) {
      this.act = act;
      this.amount = amount;
    }
    public String toString() {
      return number + ": " + act + " " + amount;
    }
  }

  public void deposit(long amount) {
    balance += amount;
    lastAct = new Action("deposit", amount);
  }  

  public void withdraw(long amount) {
    balance -= amount;
    lastAct = new Action("withdraw", amount);
  }  
  // ...
}

the creation of the Action could be written more explicitly as lastAct = this.new Action("deposit", amount); . Outside the BankAccount class, this would look like this:

BankAccount ba = new BankAccount();
Action act = ba.new Action("deposit", 4);

A nested class can use other members of the enclosing class without qualification (including private fields). A static nested class (last slide) can directly access only static members of the enclosing class.

The toString() method above could be written as return BankAccount.this.number + ": " + act + " " + amount; This is the way that you access the outer class from the inner class if you need to explicitly accesss an enclosing class's members.

It is recommended that you only nest classes one deep, but any level is allowed.

Inner classes can't have static members.

Inner Classes: Local Inner Classes [12/22]

Inner classes can be created in code blocks, such as method bodies. These classes are not members of the class, so they can't be private, protected, public, static or final. They have access to the final variables of the code block as well as other members of the class, static and not-static. (think scope as to why this is the case)

Consider the Iterator interface (we talked about it last week and will look at it again tonight).

public interface Iterator {
  public boolean hasNext();
  public Object next();
  public void remove();
}
This interface is a great candidate for a local inner class.
public static Iterator walthThrough(final Object[] objs) {

  // local inner class
  class Iter implements Iterator {
    private int pos = 0;
    public boolean hasNext() {
      return (pos < objs.length);
    }
    public Object next() throws NoSuchElementException {
      if(pos < objs.length)
        throw new NoSuchElementException();
      return objs[pos++];
    }
  }

  return new Iter();
}

Inner Classes: Anonymous Inner Classes [13/22]

Anonymous classes can be written that implement an interface or extend another class. These classes are just defined at the same time they are created with new. They are perfect for times when you don't want the weight of a full class in your code

For example:

public static Iterator walthThrough(final Object[] objs) {

  // anonymous class
  return new Iterator() {
    private int pos = 0;
    public boolean hasNext() {
      return (pos < objs.length);
    }
    public Object next() throws NoSuchElementException {
      if(pos <= objs.length)
        throw new NoSuchElementException();
      return ojs[pos++];
    }
  };
}
or:
Attr name = new Attr("Name") {
   public Object setValue(Object nv) {
     System.out.println("name set to " + nv);
     return super.setValue(nv);
   }
};
or:
goButton.addActionListener
(
  new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      someMethod();
    }
  }
);

Java classes: The canonical form of classes [14/22]

In order to behave properly in the Java environment, you need to be aware of some issues when you construct your classes.

Many of these issues will come up as you move into larger projects and programs

We use the term the canonical form to describe classes that will be well behaved when manipulated by the java run-time environment and other classes, such as the collections framework

When you design classes for general use, you should consider the following:

Java classes: 0-arg constructor [15/22]

A zero arg constructor is needed by any code that calls the new operator on your class.

It is also needed by code that dynamically instantiates your class. This can be done as follows:

        Class yourClass = Class.forName("se450.mwright1.hw4.Student");

        Student student = (Student)yourClass.newInstance();
	  
        

This kind of code is used in various application servers as well

JavaBeans are classes that need a 0-arg constructor to work properly by other Java classes as well

Java classes: Equality [16/22]

A good equals means the following is true. It is:

  1. Reflexive
  2. Symmetric
  3. Transitive
  4. Consistent
  5. Non-null

Java classes: Hash Code [17/22]

The Hashcode is used in collections classes such as HashSet and HashMap. If the equals method is overridden, it is necessary to override the hashCode method.

In general, it can be done as follows:

  1. Compute a hash code for each significant field. A field is considered significant if it is part of the equals implementation. For primitive types, the hashcode is the type as an integer. For a reference type, it is the hashCode (if it is not null)
  2. Combine the fields into the hash code.

The combination can be done as follows:

  1. Bitwise-or hash = hash << n | c where n is an arbitrary integer, say 8.
  2. Addition hash = hash * p + c where p is a prime number, say 37

The key is to make sure that the hashcode is always the same for equal values. If it isn't, a value can be put in a hashtable, and then never be able to be retrieved

Java classes: clone() [18/22]

We have looked at the equals, toString, and hashCode methods that are all inherited from Object. We now need to look at the clone method.

It is analogous to the Copy constructor in C++

The intent is that


x.clone() != x

x.clone().getClass() == x.getClass()

x.clone().equals(x)

The last intent is not a requirement, but is generally true.

By convention, you obtain the returned object by calling super.clone(). If all superclasses do this, then x.clone().getClass() == x.getClass() will be true.

Object doesn't implement Cloneable itself

The Cloneable interface is meant to be a mixin interface, however it doesn't do a very good job of this. The Cloneable interface doesn't contain the clone method, so classes can't be guaranteed to call this method (it may not have the right visibility). But since it is in pretty wide use, we should understand it. We will look at a design pattern, the Prototype in a few weeks that is a better way to do this.

However, you need to be concerned about the deep vs. shallow copy problem. If a there are mutable objects that are members of the object being cloned, they may need to be copied during the clone operation, and references to the objects changed to point to the new objects.

To clone you need:

You can:

Java classes: cloning implementation [19/22]

Simple:


public class MyClass extends HerClass implements Clonable {
  public Object clone throws CloneNotSupportedException {
    return super.clone();
  }
  // ...
}
But what about

public class IntegerStack implements Clonable {
  private int[] buffer;
  private int top;
  
  public IntegerStack(int maxContents) {
   buffer = new int[maxContents];
   top = -1;
  }
  
  public void push(int val) {
    buffer[++top] = val;
  }

  public int pop() {
    return buffer[top--];
  }

}

What does the default clone method do?

Fix this by overriding clone


public Object clone() {
  try {
    IntegerStack nObj = (IntegerStack)super.clone();
    nObj.buffer = (int[])buffer.clone();
    return nObj;
  } catch (CloneNotSupportedException e) {
    // cannot happen since we and arrays support clone
    throw new Error(e.toString());
  }
}

To disallow cloning:


public final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

There are alternatives:

Java classes: toString [20/22]

the toString method is invoked whenever you use the object in System.out.println. A good String representation of your object makes debugging it much easier than the standard String representation. A good rule of thumb is to include significant fields of an object (like those in the equals method in your toString method.

Avoid this, of course, if they will make it confusing or violate encapsulation. Never put data in your toString method that users may come to depend on because you don't provide an accessor to it.

Java classes: Serialization [21/22]

Java supports serialization for any object that implements the java.io.Serializable interface.

// write.java
import java.io.*;
public class write {
  public static void main(String args[])
  {
    try {
      FileOutputStream fos =
        new FileOutputStream("file.out");
      ObjectOutputStream oos =
        new ObjectOutputStream(fos);
      oos.writeObject(new Test("testing", 37));
      oos.flush();
      fos.close();
    }
    catch (Throwable e) {
      System.err.println(e);
    }
  }
}

// read.java
import java.io.*;
public class read {
  public static void main(String args[])
  {
    Test testobj = null;
    try {
      FileInputStream fis =
        new FileInputStream("file.out");
      ObjectInputStream ois =
        new ObjectInputStream(fis);
      testobj = (Test)ois.readObject();
      fis.close();
    }
    catch (Throwable e) {
      System.err.println(e);
    }
    System.out.println(testobj.str);
    System.out.println(testobj.ivalue);
  }
}

// Test.java
public class Test implements java.io.Serializable {
  public String str;
  public transient int ivalue;
  public Test(String s, int i)
  {
    str = s;
    ivalue = i;
  }

Serialization is used by many Java APIs, especially RMI and many J2EE technologies

You can control serialization with more detail using the java.io.Externalizable interface.

package java.io;
public interface Externalizable extends Serializable {
    public void writeExternal(ObjectOutput out) throws IOException;
    public void readExternal(ObjectInput in) throws IOException, 
                             java.lang.ClassNotFoundException;
}

Control whether fields get persisted using transient. You must be aware that an object that is restored from persistence will need its transient fields initialized.

You may find it very useful in your project

Quiz Questions? [22/22]

Any questions about the quiz?


Revised: 2003/2/3