As computer science continues to evolve, our programs get more and more powerful, using larger and larger amounts of code. The Pop program has some 10,000 lines of code, and if we were to take into account the code for the underlying Windows functions, we'd be looking at hundreds of thousands of lines more. How can we deal with such large programs?
Our only hope is to continually move to higher levels. We learn to design and program in higher-level and more abstract ways. In the earliest days, software engineers worked very close to the hardware writing microcode to directly control the processor chip. Machine language is a step up from this, consisting of coded instructions that the processor can read and execute. Assembly language is human-readable code quite close to the machine language level, but which allows the programmer a few higher-level constructs like macro statements to abbreviate having to write out repetitive blocks of code.
Assembly language gave way to a range of high-level languages designed to be something much closer to something a human can read. Some of the earlier languages were Fortran, APL, Modula and Pascal. Eventually these converged on C, which is still something of an industry standard. Libraries of C functions are available so that programmers don't continually have to reinvent the wheel. With the advent of object-oriented languages like C++ and Java, computer science moved to a new still-higher level of programming and design. There's no turning back. An object-oriented approach (OO for short) is the way.
We can draw a UML dependency diagram to illustrate the progress, with the arrows indicating that the higher levels depend on the lower levels (see Figure 4.1).
Simply using C++ or Java doesn't guarantee that you are doing object-oriented software engineering. Object-oriented techniques can be used at a variety of levels. Software engineers often distinguish among three kinds of OO: object-oriented analysis (OOA), object-oriented design (OOD), and object-oriented programming (OOP).
The idea behind OO software engineering is to break your programs up into independent self-sufficient objects. If you don't plan to alter an object's behavior, you don't need to worry about how its code works. All you need to know is what the object does. The object becomes like a black box with input/output jacks. You feed things into it and you get things out, and you don't worry about what's inside.
The objects of OO are instances of data structures called classes. A class is like a C structure, except that it has functions, or methods, inside it as well as data fields (see Figure 4.2). A class is like a high-level data type. And an object is an instance of a class. To make the distinction between class and object quite clear, you might compare a class to a type like int, and an object to a specific integer like 2.
There is some variability in the language that people use to talk about classes. The data fields of a class can also be called class attributes. And the methods of a class can also be called the class's functions or its operations.
The OO approach suggests that instead of trying to analyze a problem in terms of a zillion small tasks, we look at the problem in terms of a few high-level classes. Figuring out which classes to use for your program is the process of OOA. Deciding what members and methods your classes should have is a matter of OOD. And actually implementing the code for the classes is the work of OOP.
The three stages do blend together a bit, so if we list the expected outcomes of the OOA, OOD, and OOP processes, it makes sense to list some outcomes in two lines.
Which classes? UML diagrams
UML diagrams, *.h header
*.h header, *.cpp implementation
A preliminary way of describing the stages is to say that OOA involves looking at a problem with the aim of understanding it. OOD means defining and designing an appropriate solution. And OOP is building that solution. These three steps are really part of any reasonable approach to problem solving (see Table 4.1). Now let's look into what we do to make the steps object-oriented.
The OOA stage is a high-level design phase in which we figure out which classes to use and what data and methods to put into them. Initially you might simply write out class names and key data and methods. But after a bit, you want to actually start moving down towards the detailed design phase and writing out correct C++ class headers.
The OOA phase shades into the OOD phase when we begin thinking in some detail about what to put into the classes. The OOD phase of the process continues through the writing of the *.h header files, while the OOP part kicks in when you write the *.cpp files where the implementation of the methods lives. You shouldn't think that first you finish the OOA and OOD, and then you move into OOP without ever coming back to OOA and OOD again.
The reason you can't just finish one stage off completely and then start on the next stage is that it's so hard to design a program. You're rarely going to nail it right off the bat. Of course you do need a design to get started, but every time you finish a new alpha build, you should step back and take a long look at your design. A good place to start is by looking at the ugliest, most complicated parts of your code, the parts that you feel most uneasy about. Ask yourself how this could be made simpler. And, as long as you're revisiting your design, think about what features you want to add to your program next, so that you can lay the groundwork for them.
OOA is about figuring out how to arrange a collection of classes that does a good job of representing your real-world problem in a format which a computer program finds easy to deal with. OOD is about what kinds of data and methods go into your classes and about how the classes relate to each other in terms of inheritance, membership and function calls. OOP is about making the class implementations work. The three go hand in hand. You need to do some OOA and OOD before you OOP, and after you OOP you learn enough new things about your program to go back and improve the OOA and OOD. Like so many other things in the software engineering process, it's a feedback loop that you can run through as many iterations as your schedule allows. And, again like other things in software engineering, the precise boundaries between OOA, OOD and OOP can be somewhat fuzzy.
A useful terminology that people sometimes use is to speak of a distinction between 'top-down' design and 'bottom-up' design. In our present context, we're thinking of OOA and OOD as a top-down process where you use some high-level abstract thinking to figure out which classes to use and how to design them. And then you move down into the details. This describes a top-down movement from OOA to OOD into OOP.
The bottom-up part of the process comes about like this. When you go about implementing a design you find out a lot of things about it that you hadn't anticipated ? some things work out easier than you'd hoped, and a few things turn out to be harder. So then you change your design to make the hard things work better. This describes a bottom-up movement from OOP into OOD and OOA.
The OOD expert Grady Booch puts it like this.
Our experience indicates that design is neither strictly top-down, nor strictly bottom-up. Instead . . . well-structured complex systems are best created through the use of 'round-trip gestalt design.' This style of design emphasizes the incremental and iterative development of a system through the refinement of different yet consistent logical and physical views of the system as a whole . . . Object-oriented design may seem to be a terribly unconstrained and fuzzy process. We do not deny it. However, we must also point out that one cannot dictate creativity by the mere definition of a few steps to follow or products to create.
?[Grady Booch, Object-Oriented Design (Benjamin/Cummings, 1991), p. 188]