Chapter 12. Analysis and Design: Seeking the Objects

In this chapter, we will present the barest outline of a software development methodology. For some readers, this will be simplistic and unsuitable. In our experience, however, there are many businesses out there with very small development teams that have very little software engineering experience, even though they have considerable technical and programming skill. Our goal here is to present a bare minimum of analysis and design method, so that we can be sure we have a common basis for discussing the issues of object-oriented analysis and design.

What You Will Learn

In this chapter you will learn a very simple method for object discovery and a simple method of documenting this process.

Facing the Blank Page

So, you have some requirements. Maybe you even have some UI prototypes. How do you turn that into an object-oriented design for Java classes? How do you confront the paralyzing blank white of your whiteboard, terminal session, or easel?

The simplest way is to start with real-world objects. Stop thinking about everything you have read about object-oriented programming. Instead, ask yourself, “What are the real objects involved in this problem?”

In our case, the more you look at it, the simpler it gets. For the moment, the only real objects we have are people—the users—and accounts, that is, named pools of money. We know that users get accounts from “above,” and that they may break those pools down into subaccounts, which they may own or delegate to other users.

At the broadest level, then, we seem to have two “classes” or types of real-world objects: Accounts and Users.

Using CRC Cards

So, we need two classes. But what goes into those classes? How do we go about putting the substance into this simplistic framework?

In their now (semi)famous paper presented at the object-oriented programming conference OOPSLA in 1989, Kent Beck and Ward Cunningham introduced a simple, practical design tool for object-oriented design based on a simple, practical 3x5 file card. The CRC cards for our classes are shown in Figures 12.1 and 12.2.

Account CRC card

Figure 12.1. Account CRC card

User CRC card

Figure 12.2. User CRC card

But we are getting a bit ahead of ourselves. These CRC cards are an end product of analysis. They are the starting point for coding. Let’s talk a little bit about what is on these cards and how we came to that content.

Finding the Objects

The basic technique for doing OOA[1] with CRC cards is to start with a stack of blank cards. Assemble a design team (this may be one person, or this may be dozens).[2] The first step should always be the nomination of the real-world objects. Don’t edit or critique at this point. If someone says “computer” as an object, write “Computer” on the top of a card and put it on the table. If someone says “Manager” write it on a card and put it on the table.

To take our example, suppose we have the following list of CRC cards after such an open brainstorming session:

  • Database

  • Capital Account

  • Current Account

  • CEO

  • Computer

  • CFO

  • Director

  • Keyboard

  • Manager

Where do you go from here? Let’s articulate a general principle.

The first principle. If we could teach a programmer only one thing about software design, it would be this idea: less is more. Or, to quote Antoine de Saint-Exupéry: “Perfection is achieved not when nothing can be added, but when nothing can be taken away.” Or, to put it yet another way, always use the KISS[3] principle. The best object design is the smallest possible number of classes that model the real objects and meet all the requirements.

You are seeking simplifying abstractions.

First of all, all the objects that represent technologies or implementation details should be removed. In our list, this would include “Database,” “Computer,” and “Keyboard.” While it is likely that all three will be involved in the final product, they are not objects in the problem space. There is no theoretical reason why an OOA session cannot produce a manual, noncomputer solution. It is a common tendency to leap from problem analysis directly to technical solutions. “We can write that in Java,” “We can store those in Oracle,” “That could be an XML file.” Statements like these are to be avoided at this stage. Those are details about the implementation. You haven’t got a design to implement yet!

As we said, you are seeking simplifying abstractions. The next step, after culling cards that do not represent real objects in the problem space, is to group together the cards that have any attributes in common. If we look at our remaining cards, we can quickly see that we have two cards that are accounts: “Capital Account” and “Current Account.” These are both pools of money. Put them on top of one another on the table. Likewise, it is fairly obvious that “CEO,” “CFO,” “Director,” and “Manager” are all people. Put them together on the table.

Remember that we are looking for simplifying abstractions. The grouped cards should all be obviously variant types of a generic class of objects. In our example, the one is a stack of Accounts, and the other is a stack of People, or, as we will call them, Users. Create new cards for these generic classes. Make a card with “Account” at the top and put it above the first stack. Make another card with “User” at the top and put it above the second stack.

There are two ways that this might simplify your design. For now, all cards below the abstract cards are “on probation.” We are going to move on to define the attributes (data) and methods (behavior) of our abstract classes. If the abstract class can handle all use cases without having to treat any of the more specific classes differently, then the specific cards are discarded. If not, then all functionality that is common across the more specific types will be put on the abstract class card, and only those data and behaviors that are different will be put on the more detailed cards.

In the first case, the simplification is a reduction of several potential classes to a single class. This is always a good thing, when it is possible. In the second case, you are identifying potential inheritance relationships. [4]

Finding the Methods and Attributes

The next step is to start identifying the data and behavior that characterize your classes. Always put such items on the most abstract class first. The only time to add an attribute or method to a more specific class is when it applies to that class and only that class—in other words, only when it represents a difference between the general case and the specific case. [5]

Essential and Nonessential

So far, we have walked you through a very simple example, and we have made sound choices at every step. In more complex cases, even the best of us will make mistakes. We will head down blind alleys. We will group things together that might belong in separate abstract categories, but should, perhaps, share an interface. These are not so much errors as judgment calls, and skill at recognizing them and making the correct decisions comes only with experience.

For now, the most important questions to ask include:

  • Do I need this class?

    We are often tempted to create too many inherited classes. When we seek more generic, higher level abstractions, it is often possible to use only the more abstract class. Of course, it is possible to carry that tendency too far. If your methods contain a lot of “if’s” to handle various subtypes, that might be a case where you should inherit and overload the method.

  • Should I get functionality by inheritance or composition?

    Inheritance should be reserved only for cases where a class is a more specific variety of the base class. For example, you might have a Person class, and then you might have Employee and Customer classes inherit common attributes and methods from Person. This is frequently called an “is-a” relationship, as in “A User is a Person.” If your proposed inheritance relationship makes sense phrased that way, it might well be a good candidate for inheritance.

    Composition is when you use a class as an attribute. To extend our example, you might have an Address class. You might be tempted to have Person inherit from Address. But a Person is not an Address. Try it: “A Person is an Address.” Nope. Instead, you should just have an instance of the Address class as an attribute of Person. Such a relationship is often called a “has-a” relationship, as in “A Person has an Address.” If the relationship makes sense phrased that way, it is a good candidate for composition. Another way to recognize that you’ve wrongly used inheritance is if you end up having a radically different class inherit from the same base class. For example, suppose you have a class, Building. Would it make sense for Building and Person to inherit from Address? Are Buildings and Persons more specific instances of the same general type of thing? No, they are not. Building and Person should get Address functionality by composition.

  • Does this attribute or method belong here?

    If you find yourself specifying nearly identical methods in more than one class, this should make you ask if the classes should have a common base class from which they should inherit, or if there should be a new unrelated class that they all share by composition.

    If the functionality is the same for a set of classes, and the classes are specific instances of a more general type, the method should be on the general class. For example, a changeName() method should probably be on Person, not on Employee or Customer, because the functionality is the same for all three classes. By contrast, a changeEmployeeNumber() method should be only on Employee. It should not be on Person, because not all Persons are Employees. There may also be methods that are common to both Employee and Customer types, but are radically different in implementation. For example, a changePassword() method might change a password in a system-wide LDAP server for an Employee, but might just change a record in a Web site database for a Customer. This is easily done by writing separate methods in each class.

    But should you add a changePassword() method on Person? If you want to be able to call the method when treating either a Customer or an Employee as a Person, then you should. But you don’t have to implement the method on Person. You can declare Person.changePassword as abstract, and then, if you call the method on a Person, it will call the correct method based on what type of Person (Employee or Customer) the Person is. Note that if a class contains any abstract methods, the class itself must be declared abstract and it cannot then be instantiated. Also note that this is often best accomplished not through abstract classes, but through interfaces (see Eckel, pp. 321–322).

These are by no means the only considerations that come to bear on what classes to create and how to arrange and implement them, but they do represent a good start. They are a foundation on which you can build best practices out of your own experience and environment.

Whole books have been written on the topics of object-oriented analysis and object-oriented design. CRC cards are only one part of an array of techniques that can be applied to OOA/OOD. The Unified Modeling Language (UML) is popular in many MIS circles. UML consists of a variety of different diagrams which are used to model parts of an object-oriented design. They are:

  • Class Diagram

  • Sequence Diagram

  • Collaboration Diagram

  • Use Case Diagram

  • Activity Diagram

  • Component Diagram

  • Deployment Diagram

Using the simple but effective technique of CRC cards can be a good place to start, but you may soon want to move up the OOA/OOD ladder to use tools like Umbrello[6] to make UML diagrams, and perhaps to use the whole UML toolset.[7] Many organizations that we know of will pick and choose various techniques and tools. No matter how far down the road of formal software engineering you go, you must at least make some effort to have a repeatable process that incorporates continuous improvement.

Analysis Paralysis

The catchy phrase “analysis paralysis” has become a cliché. (And how could it not, being so catchy?) What it refers to, of course, is the tendency to become bogged down in details; or the tendency to refuse to start implementation until you are certain that your design is “right.”

This is where using a “spiral” development model can pay off. By doing frequent small releases, you can expose subtle design flaws at an earlier stage in development. Often, you can (to trot out another trendy term) “refactor” a small part of your design or implementation. If you have clean object interfaces, this can often be done with minimal disruption because a good object model hides implementation details within classes.

In most cases it is best, once you have the use cases and requirements, to proceed to a prototype object model and learn by doing.

Real Software Engineering

Let’s take a moment here and ask a fundamental question. Is this the best way to make software? And there is another fundamental, but subtly and importantly different question: Is this the right way to make software?

There are techniques and methods of Software Engineering that do approach the ideal of “zero defects.” NASA uses such procedures for manned spacecraft. Coders for medical devices do likewise. The outline method we have suggested here doesn’t come close to such methods. So, is what we have described the best way to make software? No, it is not. So why don’t we all use those zero defect methods? That is easy to answer: cost. It is expensive. Virtually no MIS shop on the planet would be willing to pay the price it takes to get that stability and certainty. The price isn’t just dollar cost, either. The Space Shuttle, for example, has computers that still use magnetic core memory, a technology that was old in the 1970s. Why? Because the restrictions imposed by their change control systems would essentially require the entire shuttle to be redesigned and retested if they made such a change. [8]

But this isn’t an either-or. You do not have to apply either a full-fledged software engineering methodology, or use nothing at all. Instead, you have to apply some design, development, and maintenance processes that improve the probability of success and reduce the cost of failure. When we recommend version control, requirements gathering, use cases, and CRC cards, we are giving you a bare-bones set of methods that will help to write fairly successful software at reasonable cost in reasonable amounts of time.

To some of you, this will be old news. If you are at level 2 or above on the Capability Maturity Model (see the sidebar in Section 12.6), then you already have some process. But you would be surprised how many business out there do not even have source code control in place. To some of you, what we suggest here will be primitive compared to processes you already have. The point is, no one’s level of control and process is “right” (to us, that means “cost-justified”) for all cases. But using no method at all is a risk too great for any business.

Core Classes

So, let’s meet our core Java classes. Here they are, in all their glory (Examples 12.1, 12.2).

Review

We have discussed a simple approach to object-oriented analysis and design through the use of CRC cards. The ideal outcome is a design with the smallest possible number of classes that model real-world objects while meeting all the requirements.

What You Still Don’t Know

We could list the names of a number of formal software engineering methodologies, but we won’t bother. If this chapter has served as your only introduction to object-oriented analysis and software engineering, let’s just say you have a lot of reading to do. But beyond that, there is something you need that is much more subtle and difficult to pin down: experience. The only way to get good at analysis and design is to do it. It helps to do it in conjunction with experienced people, because they can save you time and pain in acquiring your experience. This chapter is the simplest of foundations. The books give you knowledge. Experience gives you wisdom.

Resources

Kent Beck and Ward Cunningham, “A Laboratory for Teaching Object-Oriented Thinking”, in OOPSLA’89 Conference Proceedings, New Orleans, Louisiana, October 1–6, 1989. The special issue of SIGPLAN Notices 24, no. 10 (October 1989) is also available online at http://c2.com/doc/oopsla89/paper.html#cards.

More on the Capability Maturity Model can be found at http://www.sei.cmu.edu/cmm/.

Information on the Unified Modeling Language can be found at http://www.uml.org/.

Example 12.1. The Account class

package net.multitool.core;

import net.multitool.util.*;
import java.util.*;
import java.sql.*;

public class
Account
{
  private String name;                      // A name to identify this account
  private User owner;                       // The user assigned to this account
  private SAMoney total;                    // Total amt originally allocated to
                                            //   this account
  private SAMoney balance;                  // amt remaining unallocated to any
                                            //   subaccounts
  private Account parent;                   // The account which contains this
                                            //   account as a child
  private HashMap children;                 // The collection of subaccounts,
                                            //   by name
  private static Connection dbConn = null;  // JDBC connection
  private ArrayList payments;               // TODO: unimplemented
  private SAMoney unspent;                  // TODO: unimplemented

  /**
   * Create an account, with a pool of dollars to budget.
   * Use this constructor to create the master account.
   * Use createSub to create children of this account.
   */
  public
  Account(String name, User owner, String total)
    throws NumberFormatException
  {
    this.name = name;
    this.owner = owner;
    this.total = new SAMoney(Double.valueOf(total).doubleValue());
    this.balance = new SAMoney(Double.valueOf(total).doubleValue());
                                               // N.B. must not be the same object
    this.parent = null;
    this.children = new HashMap();
  }

  // Static that connects to the DB and either returns the top account,
  // or creates it for us.
  public static Account getTopAccount() throws SQLException {
  Account topAccount = null;

  dbConn = DriverManager.getConnection("jdbc:postgresql:budgetPro?user=mschwarz");
  if (dbConn != null) {
    // We have a database connection.
  } else {
    // We don't and we must create a top account.
  }

  return topAccount;
  }

  // Simple getter; returns the name.
  public String
  getName() { return name; }

  // Simple getter; returns the total pool of money that this account represents.
  public SAMoney
  getTotal() { return total; }

  // Simple getter; returns the balance.
  public SAMoney
  getBalance() { return balance; }

  // Simple getter; returns the parent account.
  public Account
  getParent() { return parent; }

  // Simple getter; returns the owner of this account, as a User object.
  public User
  getOwner() { return owner; }

  // Census - how many children.
  public int
  size() { return children.size(); }

  /**
   * Get to all the children, via an iterator.
   */
  public Iterator
  getAllSubs()
  {
    return children.values().iterator();
  }

  /**
   * Create a new subaccount (i.e., child)
   * given a name and an amount.
   * The child is connected to the parent, and
   * the parent's balance is reduced by the amount
   * allocated to the child.
   */
  public Account
  createSub(String name, String amt)
    throws NumberFormatException
  {
    Account acct = new Account(name, owner, amt);

    // Reduce the parent's unallocated funds.
    balance = balance.subtract(acct.getTotal());

    // Connect the accounts to each other.
    acct.parent = this;
    children.put(name, acct);

    return acct;

  } // createSub

  /**
   * Looks up and returns the account with the given name.
   */
  public Account
  getSub(String name)
  {
    return (Account) children.get(name);

  } // getSub

} // class Accoun

The Umbrello UML modeller is an Open Source tool for creating the various UML diagrams. You can find it at http://uml.sourceforge.net/index.php. We also recommend their online documentation as a good brief introduction to UML and to Umbrello. It can be found from the main Umbrello page, or directly at http://docs.kde.org/en/HEAD/kdesdk/umbrello/.

Exercises

1.

Imagine a public library. Carry out the CRC nomination process for a system to track library members and the collection. What list of objects do you come up with? What abstract classes do you find? Which did you discard and why?

2.

Extend the purpose of the library program to include generating mailings to members with overdue materials. Did you add classes? Did you add methods and/or members? To which classes did you add them?

Example 12.2. The User class

package net.multitool.core;

import net.multitool.util.*;
import java.util.*;

public class
User
{
  private String name;
  private Account home;      // TODO: implement

  public
  User(String username)
  {
    name = username;
  }

  public String
  toString()
  {

    return name;
  }

} // class User

3.

A new requirement is added. The system must allow for books, audio recordings, and movies to be checked out for different lengths of time. Did you add classes? Did you add methods and/or members? To which classes did you add them?



[1] (object-oriented analysis)

[2] It is fun to gloss over such a complex topic with a single sentence! Obviously, the composition of a design team is a complicated matter. At the very least, a design team must include a representative from the programming team and a future user of the system. On small, simple projects, that may be all you need. On more complex or mission-critical systems, there will have to be additional representatives, such as people from Operations, Support, Training, Quality Assurance, and so on.

[3] An acronym for: Keep It Simple, Stupid!

[4] We’ll talk more about that later in the book. As it happens, all of our simplifications in this chapter are examples of the first case.

[5] In complex cases, you may find an attribute or method that applies to several, but not all, of the specific cases. In such a case, a new abstract class below the main abstract class, but above all the specific classes that share that attribute or method, may be called for.

[*] The problem that seems to come up with this system is that very bad processes may be very mature and very good processes may be relatively immature. Obviously, however, an Optimizing process must be steadily moving towards the good.

[8] An exaggeration to be sure, though maybe not as much as you might think, but you get our point.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.192.212