Class development spans three distinct phases of the software development process: analysis, design, and implementation. Initially, analysts look at the problem or real "real world" through Rumbaugh's object-oriented glasses and identify and describe the objects they find. Those objects are abstracted or generalized into classes. Analysts will also identify the data that the system must maintain and the operations that it must perform. The data and operations are often naturally associated with one of the identified classes. Other times, the connection is less clear. Thinking about "responsibilities" can sometimes help clarify which class is the best candidate for hosting these ambiguous features. Think of the classes as living entities that can do things and ask, "Who should be responsible for maintaining this data or for carrying out this operation?" If more than one class can naturally accept the responsibility, the one you choose likely doesn't matter. And remember, you can always change your mind later.
Software developers refine classes during the design phase. They may discard unneeded classes or add classes they didn't find in the problem, but a computer program does. They may also discard, add, or update operations during the design phase. Changes made during analysis and design are relatively inexpensive - you change some UML diagrams and perhaps some documentation - compared to changes made after programming or deployment.
Unfortunately, there isn't an algorithm or recipe for choosing classes, attributes, or operations. But by now, you have enough experience to understand that two different programs can look very different and still be reasonable and correct solutions to the same problem. The same is true of the classes found in an object-oriented program. There are many software development processes - some suitable for designing software rapidly, some ideal for designing extensive software systems that must function for many years - but each process aims to produce a satisfactory system and not necessarily the best system.
In subsequent courses, you will formally study some of the more common software development processes. Here, we take a more informal approach focused more closely on individual classes than complete software systems.
One helpful way to conceptualize the relation between two classes is to see one class as a client and the other as a server or supplier. The server can supply some useful services that can help the client somehow. The two-hat technique takes advantage of the client-server perspective by guiding the software developer to keep the two aspects of a class separate and distinct. While wearing the class designer's hat, don't worry about how someone might use the class; focus instead on what services a general class of the kind you are making should provide. Similarly, while wearing the class user's hat, don't worry about how the class works or what data it may hide inside; focus on what the class can do for you as you use it to solve a sub-problem in your program.
It's common for you to be both the class designer and the class user (i.e., it's common for a programmer to wear both hats). It's also common for you to design the class while writing the code that uses it. Nevertheless, take a moment and mentally switch hats as you shift from one task to the next - the result is a better-designed class and better-organized client code. The table below summarizes some of the most important two-hat ideas."
Server / Supplier | Client |
---|---|
|
|
It is often insightful to treat classes as living entities that can do things. Building on that idea, we can organize the classes in an object-oriented program like a company's organizational chart. The boss at the top knows the company's long-range plans but not how to design a program or an electric circuit. The mid-level managers know the details about a specific project, not company strategy or system design. Engineers at the bottom know how to design software and circuits but not how to run a company or manage a project. Similarly, the classes at the top know what the program does but not how it does it. Classes near the bottom of the hierarchy have a detailed knowledge of solving one small part of the problem, but they don't know how their contribution works with the other classes in the program.
Software developers typically create classes near the top of the hierarchy to solve a specific problem and often only use them in one program. As such, developers specialize the top-level class features to meet the particular needs of that program. Classes become more general and reusable as we go from the top to the bottom. Developers often use general-purpose library classes at the bottom of the hierarchy. These classes provide a broad set of general services focused on the class rather than a specific problem. The string class is a good example: It provides numerous operations that we expect of strings without "knowing" how a program ultimately uses them.
Choosing what features, especially operations, to put in a class is similar to making a shopping list. Sometimes, you make a shopping list with a specific meal in mind. Classes closer to the top are like these lists - we tailor them to meet particular needs. Sometimes, we make a general shopping list - we don't have any specific requirements but add items to the list that we like to keep on hand. Classes closer to the bottom are like these lists - we stock our pantry with common, frequently used items.
In general, an interface is a "place where two things come together and affect each other," a place where they "touch" and where they can interact. A class's interface is how a client connects to, interacts with, or uses an instance of the class. A class's interface includes all non-private features (i.e., attributes and operations) visible and accessible by other classes. C++ and Java have in common three keywords that control feature accessibility: public
, protected
, and private
(Java also has a default accessibility that C++ does not). private
is the only level of accessibility that completely excludes a feature from a class's interface, while everyone agrees that public
features part of the class's public interface. Lego™ blocks are a second metaphor (besides the cookie-cutter mentioned previously) for objects. The objects fit together to form structures by matching posts of a precise size, orientation, and spacing on one Lego with compatible sockets or holes on a second Lego. The posts and holes are the Lego's interface.
Similarly, a class's features form its interface; its public
features that it exposes are its public interface. Typically, a class's public interface consists of its public
member functions but can occasionally include public
member variables as well. Figure 4 is an abstract representation of an object. Objects encapsulate or hide their data and their operation's algorithms (i.e., the bodies of the member functions) but expose the function's signatures (the function name, return type, and argument list). A client can use the object by just "knowing" the public interface - it does not need to know the private features or how the functions perform their tasks. The public interface effectively separates the how from the what.
As a general rule of thumb, programmers usually make attributes private and operations public, but the UML and C++ both support private operations and public attributes. Any non-private feature becomes part of the class's public interface. Making an operation private is appropriate whenever the class uses the operation internally, but it does not represent a service directly. Typical examples include operations representing code common to two or more operations or when a programmer decomposes large, complex operations into smaller, simpler sub-operations. These "helper" functions are appropriately private
in a UML diagram and a corresponding program class (regardless of the implementing language).
Conversely, non-private data makes maintaining a stable public interface much more difficult. A stable public interface is one in which features (operations and attributes) are not removed or changed once they become available to client programs. Programmers may add new features to a public interface without adversely affecting its stability. However, once client code can use a public interface feature, that feature cannot be withdrawn or modified without potentially affecting existing client code. So, you should have a compelling reason to make an attribute non-private, and articulating your reason is a prerequisite for making that design decision.
Classes and objects help software engineers manage the complexity of increasingly large programs by providing concrete implementations of several abstract programming constructs.
void sort();
. Later, when we have more time, we rewrite the function based on the quick-sort algorithm, but as long as the function signature, void sort();
, does not change, the class's interface remains stable and any existing client code is not adversely affected.int counter;
Similarly, Person (which we will assume is the name of a class) can be used to specify the type of a variable:
Person manager;Computer scientists characterize ADTs by the operations they support (i.e., their public interface).