CS 302 Programming Assignment 3: blather

Due on Friday, August 10 at 11:59 PM (CSL time)

Announcements | Overview | Topics engaged | Description | Hints | Handin

Last modified: Tuesday, July 31


Announcements

Includes:  Additions, Revisions, and FAQs (Frequently Asked Questions).
Please check here frequently.

08/08/2007
To use the web view, please remove blather.jar from your project and instead add blather-web.jar. Then you will be able to instantiate and use a blather.view.web.WebView object.
08/08/2007
To run tests on your model classes, please add junit.jar and blather-tests.jar to your project. Then run blather.tests.RunModelTests as an application.
08/02/2007
You can download the provided files here. I will release the web view and some other helpful information soon. However, you should get started on implementing the model classes now.


Overview

For this assignment, you will develop social software -- specifically, you will be developing the code for blather, a website that allows people to post short updates about their lives and follow happenings in the lives of their friends. You will use test-driven development to ensure that your code meets certain requirements as you are developing it. Finally, you will organize your code in a certain way -- a way that programmers call the model-view-controller pattern -- that enables code reuse and modular development. You will exploit the modular design by developing an alternate interface for the blather application.

Note: you may use ArrayList and friends on this project, as long as you clear it with me (over email) first.


Topics engaged


Description

What you’ll be doing

You will be developing classes for blather, a social networking application a website that allows people to post short updates about their lives and follow happenings in the lives of their friends. (blather is similar in spirit to the popular twitter web site.) We have provided a design for you that uses the model-view-controller pattern; you will be implementing

  1. model classes, which represent application-domain objects,
  2. view classes, which provide user interface elements, and
  3. controller classes, which implement “application logic” and mediate interactions between the user interface classes and the model classes.
By “application-domain classes”, we simply mean instantiable classes that model objects that occur in the problem domain. In this case, these objects include users, blabs, email addresses, and dates.

We will provide one set of view classes that implement a web-based interface to the blather application; you will be implementing view classes that implement a text-based, menu-driven interface for blather.

We also provide a class to make it easy to load and save program state to a file. Your controller class will use this class to This way, you can stop running the program and restart it with the same user database as before.

Before you start implementing this code, though, you should write test harnesses that exercise every method in the public interfaces of every model class. Writing test cases before you write the instantiable code that you’ll be testing will help you as you develop the code. Recall that we encourage incremental development, or developing and testing one feature at a time. Since you will have the tests before you will develop any of the actual application-domain classes, you'll be able to test individual features as you implement them.

Introduction to patterns and MVC

Introduction to patterns

Design patterns are general solutions to common design problems. Patterns will typically describe relationships between classes or objects in an abstract way, so that the solution described by the pattern is really a template that is applicable to a wide range of software design situations.

As an example, you’ve already seen one of the simplest example patterns in CS 302: the Immutable Object pattern. If a design features the Immutable Object pattern, then that design contains classes that have no mutator methods. Such classes are useful in many cases: it can greatly simplify the task of understanding code if you can be sure that an instance of a particular class is unmodifiable. (In the Java library, classes such as String, Integer, and Double exemplify the Immutable Object pattern.)

Of course, you probably could have come up with the idea for an object with no mutator methods on your own, but design patterns provide not only a template solution but also the vocabulary to discuss such a solution. Many design patterns illustrate excellent object-oriented solutions; however, design patterns are even more useful because they enhance your capacity to communicate with other programmers. Since a big part of a working programmer’s job involves communication (both in documentation and in meetings), solid knowledge of design patterns is one of the hallmarks of an excellent object-oriented programmer.

It’s much easier, once you’re familiar with the pattern vocabulary, to say:

I used the Command pattern to eliminate an unwieldy switch statement.

than it is to say:

I had a big switch statement in my code, and every case called a different method depending on a parameter p passed in to that method. I wanted to get rid of the switch statement by writing a method that took another method as a parameter, but Java won’t let me do that. I wound up writing an interface that had an “execute()” method, implementing it for every case: then I could just pass in an object that instantiated that interface that was appropriate for the value of p when I called the method, and eliminate the unwieldy switch statement.

Think of learning patterns as building your design vocabulary. (You can learn more about design patterns by visiting Wikipedia or Google.)

The Model-View-Controller pattern

The Model-View-Controller, or “MVC,” pattern provides a template for structuring an entire application by dividing classes into three groups or “layers:”

  1. Model classes represent potentially-persistent, domain-specific entities. The entities are persistent because they may continue to exist after the program has stopped running (whether in a file or in a database); they are domain-specific because they are particular to a certain kind of program. Therefore, a user or string might be a model class, but a pull-down menu is not.
  2. View classes represent user interface objects. Typically, view classes are general-purpose (as opposed to domain-specific model classes). View classes might implement pull-down menus, radio buttons, or dialog boxes. Of course, view classes don’t have to implement graphical user interfaces. View classes could also implement an auditory interface, a console-based text interface, a special hardware interface (like a keypad), or a web interface.
  3. Controller classes provide the application-specific logic. Controller classes decide how model objects are manipulated and provide the “glue” that connects model objects to view objects.

It’s important to note that these groups of classes only interact in well-defined ways. The classes in the controller layer will typically interact with two different sets of interfaces: one to communicate with view classes and one to communicate with model classes. Note that view and model classes do not directly interact with one another.

The illustration above demonstrates the interactions between layers. The “puzzle pieces” that fit together are calls to public interfaces. End-users of the application will interact with view classes, while model classes will represent application-domain objects.

Why use MVC?

There are three major benefits to the model-view-controller pattern:

  1. Better design. Following the MVC pattern can help ensure that unrelated classes aren’t tightly coupled by enforcing separation between layers.
  2. Reusable code. If your view classes aren’t closely coupled to your model classes, then you can re-use view classes in all of your applications. (When you think about it, there’s no reason for a pull-down menu to know about a party!) Perhaps more importantly, model classes are reusable as well: once you’ve written one set of model classes to deal with messages or people, you can reuse those classes for social software that accomplishes different tasks.
  3. Interchangeable components. What if you want to change to a different kind of view? Let’s say you want a web interface for your program, or a text-only interface, or a carrier-pigeon interface. These are all possible with the model-view-controller paradigm. Likewise, since the model layer isolates the rest of your program from persistent storage, your program can change from using one file format to using another file format (or even to using a database) simply by changing the model classes that deal with storage.

Design of the blather application

The blather application consists of several classes that comprise model, view, and controller functionality. To make it easier to deal with these classes, we have divided them into several packages:

blather.controller
Classes implementing the controller layer (i.e. classes that mediate interaction between view and model components)
blather.exceptions
Some specific exception classes that blather components can use to signal errors to each other
blather.model
Model classes, modeling application-domain objects (e.g. users, relationships between users, “blabs“, and dates)
blather.util
Utility classes: a class for sorting arrays of blabs, and a class that enables you to save a program database to a file
blather.view
Classes relating to user interfaces and interaction

As with any class using the model-view-controller pattern, the model classes will represent the application-domain entities involved in our problem domain. The view classes will provide a means for the user to interact with the application, and the controller classes will mediate interactions between the view components and the model components.

A note about packages

You have written code that has used classes from other packages before (e.g. java.util.Scanner.) However, you may not have written code that is organized in packages itself. This section will show you how to do so.

Remember that every Java file may contain a line indicating which package it is in. This should be the very first line of your file, and it will look something like this:

package my.awesome.package;

The preceding line indicates that the class (or classes) that we'll define in this file are from a package called my.awesome.package. The syntax is simple: the package keyword, followed by the package name, followed by a semicolon. If you were interested in declaring the UserDB class, for example, you'd put it in the blather.model package, like this:

package blather.model;

Before you do this, though, you'll want to tell Eclipse about your new packages so that it can create package folders for them. Fortunately, this is pretty simple. First, right-click on your project folder in the navigator or package explorer panes. Then select "New → Package," just like this:

Package

This will give you the following dialog box, in which you can set options for your package:

Package Dialog

Then simply type the name of your package (e.g. blather.model) and press “finish”. If you've created some files already, you can simply drag them to the appropriate package folders and add the appropriate package line to each. It's very easy, and keeps your classes organized!


What blather does

In this section, we'll examine the functionality of the blather application by considering how users may interact with it. Recall that blather is designed to enable users to post short messages and track the messages of their friends. We shall consider each of those tasks.

Welcome and logging in

There are a few different kinds of possible interactions with the blather application: browsing blabs (which anyone can do), and creating blabs and adding friends (which users must register and log in to do). Therefore, blather users are first presented with a screen in which they can decide whether to create a new account, log in, or browse a list of registered blather users without logging in. Logged in users may also log out.

When a user logs in, the blather session database generates a new, unique session ID, which is then passed along to everything else that user does. (When a user logs out, that session ID is deleted from the session database.)

(See AppController)

Browsing users

The blather system can list the names of every user stored in the system. This is useful for finding friends and finding “blabs” (our name for short messages) to read. From this list, blather users may select a user to view.

(See AppController)

User operations

Anyone may view a user's profile, which consists of the user's username, an obscured version of their email address, their ten most recent blabs, and the ten most recent blabs of their friends. (Logged-in users will see the full version of a user's email address.)

Logged-in users may update their information: changing their registered email address, adding a new blab, and adding a new friend. Because blather users are all extremely friendly people who never make mistakes, blather does not support deleting friends or editing blabs. (Note that twitter does not support editing short messages, either.)

(See UserController)


Provided classes and interfaces

Note: This section provides only a brief overview of the classes and interfaces you'll need to know about. Please see the Javadoc for more detailed information.

We have provided some instantiable and utility classes for you to use in your projects. We have also provided several interfaces that your classes should implement. We have done this both to afford you some flexibility (in the case of the view classes) and to help you ensure that you have implemented all of the required instance methods with the correct signatures. If your classes implement the provided interfaces, then the Java compiler will check that you have declared every instance method with the right names and parameter lists. However, since interfaces cannot describe static methods or constructors, you will need to take care to ensure that you have properly implemented these methods exactly as they are documented.

Instantiable and utility classes

blather.model.UserDB
A UserDB provides a mapping between usernames and BlatherUsers.
blather.model.SessionDB
A SessionDB provides a mapping between session identifiers and logged-in BlatherUsers.
blather.util.BlabUtil
BlabUtil provides a utility method for sorting arrays of Blabs.
blather.model.SimpleDate
This class models a date; it also validates whether or not a date corresponds to an actual calendar date.
blather.util.DBPersistence
This class provides static methods for saving a user database to a file and loading it from a file. Implementing database loading and saving will make testing much easier.
blather.util.DBPersistenceException
This class is used to signal some sort of error when loading or saving user data. You will need to handle it in your AppController methods that implement loading and saving.

The blatherView interface

blather.view.BlatherView
This interface defines all of the functionality necessary for a view -- whether a web view, like the one we provide you, or a text-based view, like the one you will develop. A view must be associated with a controller object and then the application can turn over control to the view.

Other useful interfaces

blather.controller.AppControllerInterface
This defines all of the instance methods that your AppController class must support.
blather.controller.UserControllerInterface
This defines all of the instance methods that your UserController class must support.
blather.model.EmailAddressInterface
This defines all of the instance methods that your EmailAddress class must support.
blather.model.BlabInterface
This defines all of the instance methods that your Blab class must support.
blather.model.BlatherUserInterface
This defines all of the instance methods that your BlatherUser class must support.

Classes you will have to implement

Note: This section provides only a brief overview of the classes and interfaces you'll need to know about. Please see the Javadoc for more detailed information.

Tester and stub classes

You will develop tester and stub classes for every model class you develop. (You will also develop a tester class for SimpleDate, even though we have provided that class for you.) We discuss what is involved in producing these classes in greater detail here.

blather application classes

Every blather application class must implement the corresponding interface (e.g. AppController must implement AppControllerInterface). Your model classes must also implement the standard Java interface java.io.Serializable. (You will not have to implement any other methods to implement this interface -- see here for an example.) Your solution may rely on additional classes that you have implemented because you deem them necessary or helpful. (For example, you may wish to make classes that encapsulate Java language arrays.)

blather.controller.AppController
The AppController is the main controller for the blather application. It provides a layer of functionality related to logging in, creating new users, loading and saving databases, and browsing users. The view classes will interact with the AppController after the main application has created user and session databases.
blather.controller.UserController
An UserController mediates interactions between the view layer and a particular BlatherUser.
blather.model.EmailAddress
An EmailAddress models an email address (which may or may not be valid).
blather.view.text.TextView
A TextView provides a console-based, menu-driven interface to the functionality provided in AppController. It must implement blather.view.BlatherView.
blather.exceptions.BogusParameterException
This class, which extends RuntimeException, indicates that a method has been called with an invalid parameter.
blather.Main
blather.Main is your application class. It will contain only a main method. This main method will get user input to determine whether to run the text-based interface or the web-based interface, create empty UserDB and SessionDB instances, create an AppController, create an instance of an appropriate class implementing BlatherView and invoke its run method. The main method will not need to do anything else.

Testing

We have always recommended that you develop your classes incrementally and test each one. For this assignment, we will be using test-driven development to facilitate this process. This means that you will be developing tester classes for each of the model classes before you implement those model classes. Your tester classes will deal with references to classes implementing the interfaces we have provided for you (e.g. EmailAddressInterface, etc.) rather than with references to the classes you will develop (e.g. EmailAddress). However, you will have to develop stub classes so that you can test constructors and static methods. (We'll talk more about stub classes momentarily.)

Our typical goal when developing tests is to try a variety of test parameters so as to provide sufficient code coverage (i.e., try enough different parameters to exercise every statement in every method at least once). Since you don't know how you will implement the methods, this is an example of what your textbook calls black-box testing. You won't be able to consider different paths through each method and guarantee complete code coverage, since the methods won't exist yet! Instead, you will have to be clever about how you design your test cases. Be sure to choose a good variety of positive, negative, and borderline test cases; it may also be helpful to think about how you might implement the methods as you are choosing your test cases.

Stub classes

One downside to using test-driven development with interfaces is that interfaces can't fully describe the objects you’ll be interacting with. Specifically, interfaces don’t describe constructors or static methods. For example, if you were developing a tester class for classes that implement the EmailAddressInterface, you'd want to test the following methods:

package blather.model;

public interface EmailAddressInterface {
    public String toString();
    public void setEmail(String email);
    public String getDisplayEmail();
    public String getEmail();
}

However, you'd also need to test the static method isValid() and the constructor for your EmailAddress class. Unfortunately, you haven't written EmailAddress yet, so tester classes that use constructors or static methods won't compile! To work around this situation, we will develop a stub class for EmailAddress. A stub class contains all of the method declarations that the real class will have, but with the simplest possible method bodies. That is: void methods will return nothing; methods with a return type will return the default value for that type. The stub class will fail almost all of your tests, of course, but you'll be able to fill in the method bodies one by one when the time comes to write your real class.

As an example, let's consider a stub class for EmailAddress:

package blather.model;

/**
 * Stub class for EmailAddress.  Each method does nothing except 
 * return the default value for its return type.
 * @author willb
 */
public class EmailAddress implements EmailAddressInterface, java.io.Serializable {
    public EmailAddress(String email) { /* No body */ }

    public static boolean isValid(String address) {
        return false;
    }
    
    public String toString() {
        return null;
    }
    
    public void setEmail(String email) { /* No body */ }
    
    public String getDisplayEmail() {
        return null;
    }
    
    public String getEmail() {
        return null;
    }
}

Obviously, a stub class doesn't do much, but if you write stub classes for all of the model classes, you'll be able to compile and run all of your tests even before you've written the actual model classes. Then, as you develop and refine your model classes, you'll be able to see more and more tests passing. We can guarantee that it will feel great to see the FAILs become PASSes.

Negative test cases and graceful failure

You may have noticed that some of the methods you are to test have preconditions. You may have also noticed that the documentation for these methods specify that they might throw certain exceptions if the preconditions are violated. When we have a case in which a method can't do anything useful, we'd like for it to fail gracefully rather than crash the program. Since you haven't covered exception handling yet (you will in Chapter 15), we'll show you how to write test cases for the situations in which you expect your code to throw an exception.

Let's consider the following simple Reciprocal class. It has one method, a static method that calculates the reciprocal of an int:

public class Reciprocal {
    /**
     * Returns the reciprocal of its argument.
     * Precondition:  k must be nonzero.  If k is zero, this method 
     * will throw an IllegalArgumentException.
     * @return the reciprocal of <tt>k</tt>
     */
    public static float reciprocal(int k) {
        /* method body goes here */
    }
}

We already know how to write positive tests for this kind of method. We will simply give it parameters and make sure that the answer is what we expect each time. In this case, we can make sure that the result is correct by verifying that multiplying some k by its reciprocal results in 1:

int k = 5;
final float EXPECTED_FOR_TEST_1 = 1.0f;
if (k * Reciprocal.reciprocal(k) == EXPECTED_FOR_TEST_1) {
    System.out.println("Passed test 1.");
} else {
    System.out.println("Failed test 1.");
    System.out.print("  k * Reciprocal.reciprocal(k) was " + k);
    System.out.println("; expected it to be " + EXPECTED_FOR_TEST_1);
}

However, handling negative cases is a little trickier. You've probably seen exceptions before when debugging your Java programs: NullPointerException, ArrayIndexException, and their friends are too familiar to any Java programmer. When one of these shows up in your program, your program will usually crash, showing you a stack trace of where things went wrong. What you may not already know is that it is possible to recover from exceptions without crashing the program. We'll cover this more in our lectures when we get to Chapter 15, but for now, here's a simple explanation:

Again, there's a little more to exceptions than that, but for now, you can think of them as a way to jump from the part of your program that caused an error to the part of your program that can handle that error. So to handle the negative cases, we'll need to ensure three things:

  1. The exception is actually thrown. If a method advertises that it will throw an exception in some case, it should actually do so.
  2. The right kind of exception is actually thrown. There are many different kinds of errors, so there are many different kinds of exceptions. If reciprocal() is supposed to throw an IllegalArgumentException if it is called with a 0 argument. We want to make sure that this happens.
  3. No other kind of exception is thrown. We want to make sure that users of our code can program defensively, so we should ensure that the reciprocal() method won't throw any exceptions that we haven't mentioned in its JavaDoc.

We want to “catch” the exception in our test case; otherwise, our tester class would crash every time — and, in so doing, it would be indicating that we'd passed a negative test! Therefore, we need to know how to catch exceptions. Here's a simple example of a negative test case that deals with exceptions (we've included line numbers to make it easier to discuss):

    1 try {
    2     /* this call should throw an IllegalArgumentException */
    3     Reciprocal.reciprocal(0);
    4     
    5     /* we should never reach this point in the program,
    6        since the preceding statement should throw an exception,
    7        causing us to go on to the catch block */
    8     System.out.println("Failed test 2; didn't throw an exception.");
    9     
   10 } catch (IllegalArgumentException e) {
   11     System.out.println("Passed test 2.");
   12 } catch (Throwable t) {
   13     /* If we're here, it's because the call to reciprocal threw
   14        some different kind of exception or error.  This is a problem,
   15        since the documentation didn't say that was possible. */
   16     System.out.print("Failed test 2; threw a " + t.getClass());
   17     System.out.println(" (expected an IllegalArgumentException)"); 
   18 }
   19 

We first indicate to Java that we are writing code that may deal with exceptions by using a try–catch block, which consists on one try block and one or several catch blocks. try precedes a code block that may throw an exception (see line 1). After the try block, there may be one or several catch blocks to handle specific kinds of exceptions thrown in the try block. In this example, we have one catch block on lines 10–11 that handles IllegalArgumentExceptions and one catch block on lines 12–17 that handles any other possible error. (Again the details aren't important for now — we'll explain this magic when we talk about chapter 15.)

Here's what should happen when we execute this code, if the reciprocal() method is working as advertised:

  1. The call to Reciprocal.reciprocal(0) at line 3 should throw an IllegalArgumentException.
  2. The IllegalArgumentException will cause Java to look for the nearest enclosing try–catch block that can handle it. In this case, that is the block of code on lines 10 and 11. The program will resume executing there, without passing “Go” or collecting $200.
  3. After executing line 11, the program will resume executing after the end of the try–catch block — that is, on line 19.

Here are some things that can go wrong:

  1. If the program gets to the line of code at line 8, then we know that the call at line 3 didn't throw any exception. This is a problem.
  2. If the program gets to the line of code at line 13, then we know that the call at line 3 didn't throw the right kind of exception — and our users won't be able to properly handle the error! This is also a problem.

Feel free to use the test case above as a template for when you're writing tests that involve exceptions.


Questions

You should create a text file named questions.txt with answers to the following questions. Note that these may require more involved answers than questions for previous assignments.

Be sure to include the descriptive information listed below as well as the answers to each question:

Assignment Number:
Assignment Name:
Date Completed:

Partner 1 Name:
Partner 1 Login:
Partner 1 Lecturer's Name:
Partner 1 Lab Section:

Partner 2 Name:
Partner 2 Login:
Partner 2 Lecturer's Name:
Partner 2 Lab Section:
  1. We use a fairly simple method to check whether or not email addresses are valid. One of your enthusiastic colleagues has suggested that it might be better to check over the internet to see if the domain name in the email address is valid, or even to try and send a message to the address and see if it bounces. Do you think this is a good idea, or not? Justify your answer.
  2. Describe two changes you'd like to make the the blather application that are possible because of the model-view-controller design pattern, and two changes that might be difficult even given the model-view-controller design pattern.
  3. Discuss the advantages and disadvantages of test-driven development. Also, discuss some bugs you found due to test-driven development, and how you went about fixing them.
  4. Identify the coupling and dependency in the blather design. Discuss whether or not the coupling is warranted. How would you reduce the coupling and dependency in the design we provided? Is it necessary to do so? Justify your answer.
  5. Discuss the cohesion of the classes in the blather design. How would you increase the cohesion of the provided design? Is it necessary to do so? Justify your answer.

Assignment requirements

This section outlines the major requirements of the assignment. Your solution to the assignment must meet each of these requirements. Be sure to read the announcements frequently and ask questions if you need clarification.

  1. Include name, login, lecture, and lab section information at the top of all assignment files.
  2. Follow the style and commenting standards for CS 302. This includes:
    • including file header comments,
    • including JavaDoc comments for every portion of the public interface of every class,
    • using symbolic names wherever they are appropriate, and
    • using visibility modifiers appropriately; data members should be private and publicly accessible methods should be public.
  3. Follow the assignment specifications exactly, e.g., each method you implement must have the correct spelling, the correct return type, and the correct parameter type(s) and must behave according to the specifications.
  4. Submit your answers to the questions in a text-only file (no Word documents) named questions.txt.
  5. Use the Handin program to hand in all work electronically.

Hints


Handin

Use the same technique as with previous projects to hand in your work. Students working in pairs will only submit one copy of all program files, except the README file described below.

Only hand in the files listed below; do not hand in any other files. These files may be handed in any time prior to the due date. Note: you may hand in your files before the deadline as many times as you wish. We suggest you use your handin directory to keep your most recent working copy as you develop your solution.

Required files:

Also:

Pair or Group Programming students must also submit a README.txt file: All students working in pairs must read the Pair Programming Guidelines and submit a README.txt file. Each student working in a group must hand-in this file to his/her own hand-in directory.


©2007 Will Benton, all rights reserved