At last year's JavaOne conference, I attended a session in which the speaker talked about Sun's plan for the Java virtual machine (JVM). In this talk, the speaker stated that Sun planned, among other things, to clear up current performance bottlenecks in its virtual machine, such as the slowness of synchronized methods and the performance costs of garbage collection. The speaker stated Sun's goal: With the improvements to the JVM, programmers would not need to think about avoiding virtual machine bottlenecks when they designed their programs; they would only need to think of creating "good object-oriented, thread-safe designs."
The speaker did not, however, elaborate on what actually constitutes a good object-oriented, thread-safe design. That is the aim of this new column. Through the articles of the Design Techniques column, I hope to answer the question: What is a good Java program design, and how do you create one?
The column's focus
My focus in this column will be to provide practical design techniques that you can put to use in your everyday programming tasks. I'll assume you are familiar with the Java language and APIs. I plan to discuss techniques, ideas, and guidelines that will help you use the language and APIs in your real-world programs.
To give you an idea of what to expect in this column, here is a list of the kinds of topics I plan to write about:
- Ways to improve the design of your objects
- Building class hierarchies
- What are interfaces for?
- What is the point of polymorphism?
- Choosing between composition and inheritance
- Designing for thread safety
- Designing for thread cooperation
- The Model/Controller/View architecture used by the JFC classes
- Design patterns
Much of the material that has already been written about software design can be applied to Java. There are many full-featured design methodologies and thick textbooks that describe them. In this column I won't promote one methodology over another. Nor will I promote a new methodology of my own invention. Rather, I will draw on and combine insights that I've gained from several existing methodologies and found to be useful in my own programming practice.
The approach to design that I will recommend in these articles arises out of my experiences over the years in the cubicle: designing new software, enhancing old software, maintaining software written by others, maintaining software written by myself, working with various languages, tools, computers, and other programmable machines. My design philosophy will be very "cubicle-oriented": based on, and geared toward, real-world commercial programming.
This month: Process described, "design" defined
In this initial article of the Design Techniques column, I will provide a detailed account of the concept of software design based on my own experience as a developer. In the remainder of this article, I'll discuss the process of software development and explain what I mean by the term "design."
The software development process
In my experience, the process of software development tends to be rather chaotic. Team members come and go, requirements change, schedules change, entire projects get canceled, entire companies go out of business, and so on. The programmer's job is to successfully navigate this chaos and in the end produce a "quality" product in a "timely" manner.
Besides being chaotic, the software development process also tends to be rather iterative. As a software product is developed, it continuously evolves based on feedback from many parties. This iterative process works from release to release (each release is one iteration) and within the development cycle of a single release. From release to release, for example, the feedback of customers with the current version indicates which bug-fixes and enhancements are most important to make in the next version. Within the development cycle of a single release, the vision of the end target is continuously adjusted by forces within the company as development progresses.
Despite the chaos and iteration, however, I have found that most development teams try to enforce some structure on their development efforts. For the purposes of this column, I'll loosely divide the software development process of a single release cycle into these four phases:
- Integration and test
With these four phases I intend to capture a structure that I have observed in most software development projects. Because each company is different, each team is different, and each project is different, these four phases form only a rough outline of a typical development cycle. In practice, some phases may be skipped or may happen in a different order. And because the iterative nature of software development tends to bubble up through any imposed structure, these phases may to some extent overlap or bleed into one another.
When I talk about design in the Design Techniques column, I am talking about the activities that take place during step two of the above list. To give you a better idea of what I mean by each phase, I describe each individually in the next four sections.
Phase 1: Specifying the problem domain
The specification phase of a software project involves bringing together all the concerned parties to discuss and define the end product of the software development process. During specification, you define "the vision" -- the target you will aim at for the remainder of the project. The deliverable that should come out of the specification phase is a written document that defines the requirements of the software system.
The requirements specification is much like a contract. It is a contract between all the concerned parties, but most importantly from the developer's perspective, it is a contract between the developer and whatever party desires the end product in the first place: perhaps a client, a customer, management, or the marketing department. When a specification is agreed to in spoken terms but is not written down, it is basically an oral contract. Although an oral contract is legally binding, in many cases not having something written down is a recipe for trouble. Different people tend to have different recollections of oral agreements, especially when it comes to details. A disagreement on details is even more likely if the details were never discussed as part of the oral agreement in the first place, which is a common feature of oral contracts.
When all the parties involved get together and try to write down the requirements of a software project, it forces an exploration of the problem domain. The problem domain is the end product described in a human (not computer programming) language. The same end product expressed in a computer language is the solution domain. In the course of exploring the problem domain, many ambiguous details can be identified and discussed, and disagreements can be resolved right from the beginning.
A good specification gives you a well-defined target to aim for as you develop. But it doesn't guarantee that the target won't move. Some adjustments in the vision of the end product are almost inevitable during the design and implementation phases; however, a good specification can help reduce the magnitude of such adjustments. Skipping the specification phase, or not covering the details sufficiently, can lead to the same kind of misunderstanding between parties that can occur with an oral contract. Thus, having a good specification first helps advance the subsequent design and implementation phases to a successful conclusion.
Phase 2: Designing the solution domain
Once you have a written specification that everyone involved agrees to, you are ready for what I call the design phase -- the process of planning, and in some way documenting, the architecture of your solution domain. I include many activities under the name "design," including:
Defining the system:
- Partitioning the system into individual programs (and documenting it)
- Defining and documenting the interfaces between the individual programs
- Deciding on and documenting third-party libraries (Java packages) your Java programs will use
- Deciding on and documenting new libraries (Java packages) you will build that multiple components of your system will share
Building user interface prototypes:
- Building user interface prototypes for those system components that have any user interface
Doing object-oriented design:
- Designing and documenting class hierarchies
- Designing and documenting the individual classes and interfaces
Defining the system
As a first step in the design phase, you must partition your system into its component parts. For example, you may require several processes at various places on a network. You may have some applets and some applications. Some components of the system may be destined to be written in Java and others not. If you want to use JDBC, you may need to select a third-party JDBC library that will enable you to access the database of your choice. All these decisions must be made before you can begin any object-oriented designs of the individual programs in the system.
As you define the system, you will likely want to document your work in one or more technical specifications. Documentation allows you to communicate the design to other interested parties in the organization and to get their feedback. You can pass out the specification, call a design review meeting, then present the system design at the meeting. The group can discuss your design and hopefully find any problems and make suggestions. Getting feedback -- and making adjustments to your system design as a result of the feedback -- is an example of iteration in the process of software development.
Building user interface prototypes
Building a user interface prototype is often a valuable activity during the design phase. Once the user interface prototype is completed, the parties that agreed to the specification can gather together again to review the preview version. Having a prototype gives the parties another chance to visualize and discuss the end target. By requiring everyone who agreed to the specification to review and sign off on a user interface prototype, you help ensure that all parties have compatible expectations for the end product. With the visual tools available today for the development of Java-based user interfaces, developing a user interface prototype can be very fast, and the end result is a framework of Java code that you can then endow with functionality during the implementation phase.
Note that the process of demonstrating a user interface prototype is a prime example of the iterative nature of the development process. When the interested parties (who have all agreed on a written specification) actually see user interface prototypes, they often have new ideas, or a better understanding, or a more detailed understanding -- in other words, a clearer vision -- of the end product. During the demonstration, some adjustments may be made to the specification. By this time, however, hopefully the adjustments will be minor.
Doing an object-oriented design
As you design a Java program, you must think in terms of all the programming technologies offered by the Java language, including multithreading, garbage collection, structured error-handling, and object orientation. Yet, because the dominant architectural characteristic of the Java programming language is object orientation, a Java program design phase is fundamentally a process of object-oriented design.
Doing an object-oriented design involves creating inheritance hierarchies and designing the fields and methods of individual classes and interfaces. Three basic categories of classes that you will come up with in a design are:
- User-interface classes
- Problem domain classes
- Data management classes
User interface classes are those that compose the program's user interface, such as classes that represent windows and dialogs. Problem domain classes are those that represent objects that you identified in the problem domain. For example, if your problem domain involved elevators, you might have an
Elevator class in your solution domain. Data management classes are those that you create to manage objects or data. Neither user interface classes nor data management classes have corresponding objects in the problem domain.
Phase 3: Implementation
Implementation is coding. Writing for loops, if statements, catch clauses, variables, and comments; compiling; unit testing; bug fixing -- that's implementation: the stark act of programming.
Phase 4: Integration and test
During the integration and test phase, the members of the project team, each tasked with building a specific part of the whole, meet and try to get all the pieces of the software system to work together. During this phase the team members find out how well the interfaces between the individual system components were defined and communicated during the system partitioning phase. The coding that takes place during this phase should primarily be bug fixing.
Documentation of software designs
There are many approaches to software design. Formal methodologies attempt to guide you through the process of transforming a problem domain into a solution domain. In designing Java programs, you may choose to use a formal methodology, to combine several formal methodologies, or to forgo formal methodology and design by the seat of your pants. But no matter how you attack the design phase of your software project, you should in some way document your design.