Review ====== In the last class we saw two principles of class design: * The open-closed Principle (OPC): a module should be closed for use, and open for extension. - The dangers: - Run-time type interrogation. - Public properties. - Global variables. - The examples: - Closing the payroll system for use. - Closing against ordering: the Comparable interface. * The Liskov Substitution Principle (LSP): we can use the subclass in a situation in which we expect the superclass. - The dangers: - Inheritance that does not obey the contract. - Overwritten method expects more. - Overwritten method promises less. - Heuristics: - Using the type system to enforce the contract. - Examples: - The Rectangle/Square dilemma. - The Collection/Set dilemma. - The problem with persistent collections. Two More Principles =================== * The Dependency Inversion Principle (DIP): depend on abstractions, not on implementations. 1) Write the 'cp' Unix command. 2) What is a good test case for this problem? public final void testCopy() throws Exception { byte[] inData = {70,101,114,110,97,110,100,111}; String inputFileName = "test0.data"; String outputFileName = "test1.data"; // Create the file: FileOutputStream srcFile = new FileOutputStream(new File(inputFileName)); srcFile.write(inData); // Do the copy: FileCopier fc = new FileCopier(); fc.cp(inputFileName, outputFileName); // Check if the output file has the same contents of the input file. FileInputStream dstFile = new FileInputStream(new File(outputFileName)); byte[] outData = new byte[inData.length]; dstFile.read(outData); for (int i = 0; i < inData.length; i++) { assertEquals(outData[i], inData[i]); } } 3) What is a reasonable solution? public final void cp(final String srcFileName, final String dstFileName) throws IOException { FileOutputStream dstFile = new FileOutputStream(new File(dstFileName)); FileInputStream srcFile = new FileInputStream(new File(srcFileName)); for (int data = srcFile.read(); data > 0; data = srcFile.read()) { dstFile.write(data); } dstFile.close(); srcFile.close(); } 4) Make it into an executable and test it: public static void main(final String[] args) { if (args.length != 2) { System.err.println("Syntaxe: java Cp src_file dst_file"); System.exit(1); } else { FileCopier fc = new FileCopier(); try { fc.cp(args[0], args[1]); } catch (IOException e) { e.printStackTrace(); } } } 5) Is this a good solution? 5.1) Is is fragile? 5.2) Is is rigid? 5.3) Is is hard to reuse? 6) Implement a 'wc' UNIX like utility. 7) Can you reuse anything from the previous application? 8) What do these two applications have in common? 9) What is the algorithm implemented by FileCopier? while has data: read data from input; send data to output. 9.1) Does wc follow this model? 10) What is the interface of the output? public interface OutChannel { void write(int data) throws Exception; void done() throws Exception; } 11) How to implement this interface to do a 'cp'? import java.io.*; public class FileWriter implements OutChannel { private FileOutputStream dstFile; public FileWriter(final String fileName) throws FileNotFoundException { dstFile = new FileOutputStream(new File(fileName)); } public final void write(final int data) throws IOException { dstFile.write(data); } public final void done() throws IOException { dstFile.close(); } @Override public final void finalize() { try { done(); } catch (IOException e) { e.printStackTrace(); } } } 12) Why the finalize is important? 13) How is the 'wc' app divided? Which tasks does it execute? 14) How is an OutChannel that counts the number of characters in the input file? public class CharacterCounter implements OutChannel { private int numCharacters = 0; public void done() throws Exception { System.out.println("Number of characters = " + numCharacters); } public void write(int data) throws Exception { numCharacters++; } public int getNumCharacters() { return numCharacters; } } 14) How is an OutChannel that counts the number of words in the input file? public class WordCounter implements OutChannel { private int numWords = 0; private boolean wasReadingWord; public void done() throws Exception { System.out.println("Number of characters = " + numWords); } public void write(int data) throws Exception { if (Character.isWhitespace(data)) { if (wasReadingWord) { wasReadingWord = false; } } else if (!wasReadingWord) { numWords++; wasReadingWord = true; } } public int getNumWords() { return numWords; } } 15) And how is the line counter? public class LineCounter implements OutChannel { private int numLines = 1; public void done() throws Exception { System.out.println("Number of lines = " + numLines); } public void write(int data) throws Exception { if (data == '\n') { numLines++; } } public int getNumLines() { return numLines; } } 16) Ok, now, put everything into an executable an give me 'wc': public static void main(final String[] args) { if (args.length != 1) { System.err.println("Syntaxe: java Wc file"); System.exit(1); } else { FileCopier1 fc = new FileCopier1(); try { CharacterCounter cc = new CharacterCounter(); fc.cp(args[0], cc); WordCounter wc = new WordCounter(); fc.cp(args[0], wc); LineCounter lc = new LineCounter(); fc.cp(args[0], lc); System.out.println("\t" + lc.getNumLines() + "\t" + wc.getNumWords() + "\t" + cc.getNumCharacters() + "\t" + args[0]); } catch (Exception e) { e.printStackTrace(); } } } 17) Is there any situation in which we might wish to generalize the input channel? 18) What if I want to count the number of characters in a collection of strings? 19) How would be a InChannel interface like? 19.1) Can we use generics? import java.io.IOException; public interface InChannel { boolean hasData(); E read() throws IOException; } 20) What about the implementation of a FileReader? import java.io.*; public class FileReader implements InChannel { private int nextData = 0; private FileInputStream srcFile; public FileReader(final String fileName) throws FileNotFoundException { srcFile = new FileInputStream(new File(fileName)); try { nextData = srcFile.read(); } catch (IOException e) { e.printStackTrace(); } } public final Integer read() throws IOException { if (hasData()) { int i = nextData; nextData = srcFile.read(); return i; } else { throw new IOException("Trying to read empty input channel."); } } public final boolean hasData() { return nextData != -1; } @Override public final void finalize() { try { srcFile.close(); } catch (IOException e) { e.printStackTrace(); } } } 21) And the FileCopier, how would it look like? public class FileCopier2 { public final void cp(final InChannel in, final OutChannel out) throws Exception { while (in.hasData()) { out.write(in.read()); } } } 22) And the main Wc program? FileCopier2 fc = new FileCopier2(); try { CharacterCounter cc = new CharacterCounter(); fc.cp(new FileReader(args[0]), cc); WordCounter wc = new WordCounter(); fc.cp(new FileReader(args[0]), wc); LineCounter lc = new LineCounter(); fc.cp(new FileReader(args[0]), lc); System.out.println("\t" + lc.getNumLines() + "\t" + wc.getNumWords() + "\t" + cc.getNumCharacters() + "\t" + args[0]); } catch (Exception e) { e.printStackTrace(); } 23) But, going back to counting the number of characters in a collection of strings... import java.io.IOException; import java.util.Collection; import java.util.Iterator; public class StringStream implements InChannel { private int nextData = 0; private String data; public StringStream(final Collection inputData) { data = ""; Iterator i = inputData.iterator(); while (i.hasNext()) { data += i.next(); } } public final Integer read() throws IOException { if (hasData()) { int i = data.charAt(nextData); nextData++; return i; } else { throw new IOException("Trying to read empty input channel."); } } public final boolean hasData() { return nextData < data.length(); } } 24) Can you use StringStream to code a program that counts the number of characters in the command line? import java.util.Collection; import java.util.LinkedList; public class Wc2 { public static void main(final String[] args) { FileCopier2 fc = new FileCopier2(); try { CharacterCounter cc = new CharacterCounter(); Collection cl = new LinkedList(); for (String s : args) { cl.add(s); } fc.cp(new StringStream(cl), cc); System.out.println("\t" + cc.getNumCharacters() + "\t" + args[0]); } catch (Exception e) { e.printStackTrace(); } } } 25) Why have we been programming to Collection's instead of TreeSet's, and List's instead of LinkedList? 26) What are good heuristics to see if our programs adhere to the DIP? - Few classes on the left side of expressions. - Few classes as formal parameters. * The Interface Segregation Principle (ISP): give to each client only the interface that the client needs. 27) Why Java has so many ways to deal with files? How does it compared to, say, C or C++? 28) What would be a good interface for a door? public interface Door { void open(); void close(); boolean isOpen(); } 29) Now, let's assume that a door will trigger an alarm if it stays open for too long. How to write this application? 29.1) Remember the DIP: program for the interfaces, not for the implementations. public interface Door { void open(); void close(); boolean isOpen(); void registerListener(TimeListener listener); } - and - public interface TimeListener { void start(); } 30) What would be a good test for this application? public static void main(final String[] args) { Door d = new DoorImpl(); TimeListener t1 = new TimeListenerImpl(d, "The door is open."); TimeListener t2 = new TimeListenerImpl(d, "Help!!!"); d.registerListener(t1); d.registerListener(t2); d.open(); d.open(); } 31) How is the implementation of a TimeListener? Which informations does it need? What does it do when the time elapses? public class TimeListenerImpl implements TimeListener { private Door door; private String msg; public TimeListenerImpl(final Door doorp, final String msgp) { door = doorp; msg = msgp; } public final void start() { if (door.isOpen()) { System.out.println(msg); } } } 32) How can we time the door? Can you sketch a design in UML? Door ____________ Thread ^ / \ ^ . v | | DoorImpl<>-->* TimeListener<----DoorImpl.Timer 33) Looks hard... How to implement this? 34) How would be the implementation of the door? import java.util.Collection; import java.util.LinkedList; public class DoorImpl implements Door { private boolean isOpen = false; private Collection listeners; private final long TIME_TO_TRIGGER = 500; private class Timer extends Thread { private long time = 0; private TimeListener listener; public Timer(long time, TimeListener listener) { this.time = time; this.listener = listener; } public void run() { try { sleep(time); listener.start(); } catch (InterruptedException e) { e.printStackTrace(); } } } public DoorImpl() { this.listeners = new LinkedList(); isOpen = true; } public void close() { isOpen = false; } public boolean isOpen() { return isOpen; } public void open() { for (TimeListener t : listeners) { Timer timer = new Timer(TIME_TO_TRIGGER, t); timer.start(); } } public void registerListener(TimeListener listener) { listeners.add(listener); } } 35) What do you think about this design. Any problem? 36) Is it rigid? What would happen if we change the signature of the method to add a time listener to the door (registerListener)? Who would have to be re-compiled? 37) Is it easy to reuse? Can you think on any situation where you would be able to reuse this design? 38) What about adapting doors to ssh connections? I.e, if the connection remains open for too long, then we must close it. 39) What about adapting the time alarm to an assembly line? Every time a product is ready, we call the start of the TimeListener, that will carry on some action. 40) Can we use the time alarm in the context of a window system. I.e, when the user clicks a button, we notify an action? 41) Does our time alarm depends on this concept of time in any way? Door EventNotifier Thread +open +registerListener ^ +close ^ | +isOpen . Listener | ^ . +notifyEvent | . . . . . ^ | . . . | TimedDoor<>--------->* TimeListener<>--> DoorImpl.Timer "The Interface Segregation Principle (ISP): give to each client only the interface that the client needs." 42) Notice that our main method is using the TimedDoor class directly. It is a class, and after the DIP, is this a problem? public static void main(final String[] args) { TimedDoor d = new TimedDoor(); Listener t1 = new TimeListenerImpl(d, "The door is open."); Listener t2 = new TimeListenerImpl(d, "Help!!!"); d.registerListener(t1); d.registerListener(t2); d.open(); d.open(); } 43) Returning to a previous question: why was the first design rigid? Which forces could cause changes into clients of Door? 44) What is a change in this case? - Recompilation? - Re-distribution? 45) What is the core idea behind the interface segregation principle? "Clients should not be forced to depend upon interfaces that they do not use." 46) Which problems are revealed by the non-conformance to this priciple? Rigidity, fragility, mobility? Which one? 47) Consider a bank system. How would you go about design it? 47.1) What does a bank system do? What does it know? - Deposit - Withdraw - Transfer 47.2) Are there situations in which only part of these functions would be necessary? - Through the internet we can transfer. - On the ATM we can withdraw and transfer. - On the teller we can do everything. 47.3) How to separate these concerns?