- Introduction
- All Software Is Complex
- Controlling Complexity Though Design
- The Craft of Programming
Controlling Complexity Though Design
When you have the task of designing a new application, you need to consider all of the things that help make existing software more understandable. After all, once you've created the application, sooner or later you have to revisit the code to add new features or change existing ones. At that point, you'll find out whether you were successful in creating understandable, maintainable code.
To create maintainable software, you really need the help that objects provide. You need to make sure that you design the interactions between the objects in a way that makes the resulting code easy to read and understand. After all, you never want your teammates to hear you cry, "Who wrote this piece of garbage?! I can't make sense of it at all! Oh...it was me..."
Classes Represent Concepts
The first thing to remember when designing your application is to use classes to represent important concepts. How do you know what the important concepts are for your application? Simple. Look at your requirements, use cases, and test cases. The concepts that show up in those are in all probability going to become classes in your application.
For the Running Club Membership system discussed in the last article, here are some candidate classes:
Class
Description
Member
Represents a club member
Club
Represents the club to which the Member belongs (this is a very easy concept to miss, believe it or not)
ClubEvent
The special events that the club secretary wants to tell the members about
Race
A special kind of ClubEvent, against which the members report RaceResults
RaceResult
Represents a member's time and placement in a particular Race
Assigning Responsibilities to Classes
Once you have some ideas for the candidate classes, your next design task is to work through one use case and associated acceptance test cases (see Part 5 of this series, "Creating Acceptance Tests from Use Cases") to assign responsibilities for delivering the various subfunction goals to classes within the application. When initially assigning responsibilities, try to group related responsibilities together in the same class. Don't worry if you identify a responsibility that doesn't fit any of your candidate classes; it just means that you have to invent a new class for that particular responsibility.
As you work through your design ideas, you'll inevitably learn about how your design works, and in the process realign the responsibilities. Indeed, you might find it beneficial to come up with three different initial designs to explore different options. If you do this, you might even discover that the first idea you came up with is not actually the best option.
When looking through the use cases to identify the responsibilities, it's easiest to just read through the main success scenario first. Defer looking through the extensions for responsibilities until after you feel you've correctly assigned the main responsibilities.
For the use case Club Secretary : Notify members about special events, here's one possible assignment of responsibilities:
Member: Represents a club member
Knows name, email address, and phone number
Matches what it knows against specified selection criteria
Records delivery of event notification
Club: Represents the club to which the Member belongs
Knows members and special events
Sends event notification emails
Prints event notifications
ClubEvent: The special events that the club secretary wants to tell the members about
Knows name, date, and description of special event
In this design option, the ClubEvent is a data holder object without any really interesting behavioral responsibilities. Another design option would be to have the ClubEvent responsible for emailing itself.