Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Designing Professional C++ Programs

Additionally, C++ provides a useful standard library, including a string class, I/O facilities, and many common data structures and algorithms. Also, many design patterns, or common ways to solve problems, are applicable to C++. Chapter 4 covers design with the standard library and introduces design patterns.

Because of all of these issues, tackling a design for a C++ program can be overwhelming. One of the authors has spent entire days scribbling design ideas on paper, crossing them out, writing more ideas, crossing those out, and repeating the process. Sometimes this process is helpful, and, at the end of those days (or weeks), leads to a clean, efficient design. Other times it can be frustrating, and leads nowhere. It’s important to remain cognizant of whether or not you are making real progress. If you find that you are stuck, you can take one of the following actions:

Ask for help. Consult a coworker, mentor, book, newsgroup, or Web page.

Work on something else for a while. Come back to this design choice later.

Make a decision and move on. Even if it’s not an ideal solution, decide on something and try to work with it. An incorrect choice will soon become apparent. However, it may turn out to be an acceptable method. Perhaps there is no clean way to accomplish what you want to accomplish with this design. Sometimes you have to accept an “ugly” solution if it’s the only realistic strategy to fulfill your requirements.

Keep in mind that good design is hard, and getting it right takes practice. Don’t expect to become an expert overnight, and don’t be surprised if you find it more difficult to master C++ design than C++ coding.

Two Rules for C++ Design

There are two fundamental design rules in C++: abstraction and reuse. These guidelines are so important that they can be considered themes of this book. They come up repeatedly throughout the text, and throughout effective C++ program designs in all domains.

Abstraction

The principle of abstraction is easiest to understand through a real-world analogy. A television is a simple piece of technology that can be found in most homes. You are probably familiar with its features: you can turn it on and off, change the channel, adjust the volume, and add external components such as speakers, VCRs, and DVD players. However, can you explain how it works inside the black box? That is, do you know how it receives signals over the air or through a cable, translates them, and displays them on the screen? We certainly can’t explain how a television works, yet we are quite capable of using it. That is because the television clearly separates its internal implementation from its external interface. We interact with the television through its interface: the power button, channel changer, and volume control. We don’t know, nor do we care, how the television works; we don’t care whether it uses a cathode ray tube or some sort of alien technology to generate the image on our screen. It doesn’t matter because it doesn’t affect the interface.

47

Chapter 2

Benefiting from Abstraction

The abstraction principle is similar in software. You can use code without knowing the underlying implementation. As a trivial example, your program can make a call to the sqrt() function declared in the header file <cmath> without knowing what algorithm the function actually uses to calculate the square root. In fact, the underlying implementation of the square root calculation could change between releases of the library, and as long as the interface stays the same, your function call will still work. The principle of abstraction extends to classes as well. As introduced in Chapter 1, you can use the cout object of class ostream to stream data to standard output like this:

cout << “This call will display this line of text\n”;

In this line, you use the documented interface of the cout insertion operator with a character array. However, you don’t need to understand how cout manages to display that text on the user’s screen. You need only know the public interface. The underlying implementation of cout is free to change as long as the exposed behavior and interface remain the same. Chapter 14 covers I/O streams in more detail.

Incorporating Abstraction in Your Design

You should design functions and classes so that you and other programmers can use them without knowing, or relying on, the underlying implementations. To see the difference between a design that exposes the implementation and one that hides it behind an interface, consider the chess program again. You might want to implement the chess board with a two-dimensional array of pointers to ChessPiece objects. You could declare and use the board like this:

ChessPiece* chessBoard[10][10];

...

ChessBoard[0][0] = new Rook();

However, that approach fails to use the concept of abstraction. Every programmer who uses the chess board knows that it is implemented as a two-dimensional array. Changing that implementation to something else, such as an array of vectors, would be difficult, because you would need to change every use of the board in the entire program. There is no separation of interface from implementation.

A better approach is to model the chess board as a class. You could then expose an interface that hides the underlying implementation details. Here is an example of the ChessBoard class:

Class ChessBoard { public:

// This example omits constructors, destructors, and the assignment operator. void setPieceAt(ChessPiece* piece, int x, int y);

ChessPiece& getPieceAt(int x, int y); bool isEmpty(int x, int y);

protected:

// This example omits data members.

};

Note that this interface makes no commitment to any underlying implementation. The ChessBoard could easily be a two-dimensional array, but the interface does not require it. Changing the implementation does not require changing the interface. Furthermore, the implementation can provide additional functionality, such as bounds checking, that you were unable to do with the first approach.

48

Designing Professional C++ Programs

Hopefully this example convinced you that abstraction is an important technique in C++ programming. Chapters 3 and 5 cover abstraction and object-oriented design in more detail, and Chapters 8 and 9 provide all the details about writing your own classes.

Reuse

The second fundamental rule of design in C++ is reuse. Again, it is helpful to examine a real-world analogy to understand this concept. Suppose that you give up your programming career in favor of work as a baker. On your first day of work, the head baker tells you to bake cookies. In order to fulfill his orders you find the recipe for chocolate-chip cookies in the cookbook, mix the ingredients, form cookies on the cookie sheet, and place the cookie sheet in the oven to bake. The head baker is pleased with the result.

Now, we are going to point out something so obvious that it will surprise you: you didn’t build your own oven in which to bake the cookies. Nor did you churn your own butter, mill your own flour, or form your own chocolate chips. I can hear you think, “That goes without saying.” That’s true if you’re a real cook, but what if you’re a programmer writing a baking simulation game? In that case, you would think nothing of writing every component of the program, from the chocolate chips to the oven. However, you could save yourself time by looking around for code to reuse. Perhaps your office-mate wrote a cooking simulation game and has some nice oven code lying around. Maybe it doesn’t do everything you need, but you might be able to modify it and add the necessary functionality.

To point out something else that you took for granted, you followed a recipe for the cookies instead of making up your own. Again, that goes without saying. However, in C++ programming, it does not go without saying. Although there are standard ways of approaching problems that arise over and over in C++, many programmers persist in reinventing these strategies in each design.

Reusing Code

The idea of using existing code is not new to you. You’ve been reusing code from the first day you printed something with cout. You didn’t write the code to actually print your data to the screen. You used the existing ostream implementation to do the work.

Unfortunately, programmers generally do not take advantage of all the available code. Your designs should take into account existing code and reuse it when appropriate.

For example, suppose that you want to write an operating system scheduler. The scheduler is the component of the operating system that is responsible for deciding which processes run, and for how long. Since you want to implement priority-based scheduling, you realize that you need a priority queue on which to store the processes waiting to run. A naïve approach to this design is to write your own priority queue. However, you should know that the C++ standard template library (STL) provides a priority_queue container that you can use to store objects of any type. Thus, you should incorporate this priority_queue from the STL into your design for the scheduler instead of rewriting your own priority queue. Chapter 4 covers code reuse in more detail, and introduces the standard template library.

Writing Reusable Code

The design theme of reuse applies to code you write as well as to code that you use. You should design your programs so that you can reuse your classes, algorithms, and data structures. You and your coworkers should be able to utilize these components in both the current project and in future projects. In general, you should avoid designing overly specific code that is applicable only to the case at hand.

49