1. Trang chủ
  2. » Công Nghệ Thông Tin

Absolute C++ (4th Edition) part 84 pdf

10 449 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 218,32 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

For example, you can have a stack of ints with an under-lying vector, stack or a stack of ints with an underlying list, stack or a stack with some other underlying container class, but t

Trang 1

20 Patterns and UML

Einstein argued that there must be simplified explanations of nature, because God is not capricious or arbitrary No such faith comforts the software engineer Much of the complexity that he must master is arbi-trary complexity

F Brooks, “No Silver Bullet: Essence and Accidents of Software Engineering,” IEEE Computer, April 1987.

INTRODUCTION

Patterns and UML are two software design tools that apply no matter what programming language you are using, as long as the language provides for classes and related facilities for object-oriented programming This chapter presents a very brief introduction to these two topics It contains no new details about the C++ language

A pattern in programming is very similar to a pattern in any other context

It is a kind of template or outline of a software task that can be realized as dif-ferent code in difdif-ferent, but similar, applications

UML is a graphical language that is used for designing and documenting software created within the object-oriented programming framework This chapter uses some material from all of the chapters that come before However, if you have read most, but not all, of the previous chapters, you can still get all or most of the benefit from reading this chapter

Patterns

I bid him look into the lives of men as though into a mirror, and from others to take an example for himself.

Terence (Publius Terentius Afer, 190–159 B C ), Adelphoe

Patterns are design principles that apply across a variety of software applica-tions To be useful the pattern must apply across a variety of situaapplica-tions To be substantive the pattern must make some assumptions about the domain of applications to which it applies For example, the Iterator pattern applies to container classes of almost any kind Recall that when we discussed iterators in Chapter 19, we first described them in the abstract as ways of cycling through

a range of data in any kind of container We then gave specific applications of

20.1

pattern

20_CH20.fm Page 838 Monday, August 18, 2003 2:08 PM

Trang 2

Patterns 839

the Iterator pattern, such as list iterator, constant list iterator, reverse list iterator, con-stant reverse list iterator, vector iterator, concon-stant vector iterator, reverse vector iterator, constant reverse vector iterator, and so forth Using the overriding pattern of an iterator allowed you to organize your knowledge about container manipulation so that you could easily understand and communicate about software that used the container and iterator classes of the STL

Imagine the huge amount of detail you would have had to digest if we had pre-sented each kind of container iterator separately, with different names for begin( ),

end( ), and ++ Indeed, to make sense of that mountain of detail, you might have had

to invent the Iterator pattern yourself Now that you know about the Iterator pattern, you would surely see that pattern no matter how we presented iterators However, until somebody had the insight to see and explain the pattern, the various iterators were a large number of different applications that seemed similar but were not organized by any overriding principles Another related pattern we have been using is the Container pattern In fact, the way patterns are usually organized, these would be seen as parts of

a larger pattern known as the Container-Iterator pattern This brief chapter can give only a taste of what patterns are all about In this section

we will discuss a few sample patterns to let you see what patterns look like There are many more known and used patterns and many more yet to be explicated This is a new and still developing field of software engineering

ADAPTER PATTERN

The Adapter pattern transforms one class into a different class without changing the underlying class, merely by adding a new interface (The new interface replaces the old interface of the underlying class.) For example, in Chapter 19 we mentioned that the

stack and queue template classes of the STL were adapter classes We described both the stack and queue interfaces and said you could choose the underlying class that would actually store the data For example, you can have a stack of ints with an under-lying vector, stack<int, vector<int> > or a stack of ints with an underlying list,

stack<int, list<int> > (or a stack with some other underlying container class, but two is enough for our point) In either case, list or vector, the underlying class is not changed Only an interface is added

How might the interface be added? That is an implementation detail that need not

be part of the Adapter pattern There are, however, at least two obvious ways to do it

For example, for a stack adapter the underlying container class could be a member vari-able of the stack class, or the stack class could be a derived class of the underlying container class

THE MODEL-VIEW-CONTROLLER PATTERN

The Model-View-Controller pattern is a way of dividing the I/O task of an applica-tion from the rest of the applicaapplica-tion The Model part of the pattern performs the heart

of the application The View part is the output part; it displays a picture of the Model’s

Iterator pattern

Container-Iterator pattern

Adapter pattern

Model- View-Controller pattern

20_CH20.fm Page 839 Monday, August 18, 2003 2:08 PM

Trang 3

840 Patterns and UML

state The Controller is the input part; it relays commands from the user to the Model Normally each of the three interacting parts is realized as an object with responsibilities for its own tasks The Model-View-Controller pattern is an example of a divide-and-conquer strategy: One big task is divided into three smaller tasks with well-defined responsibilities Display 20.1 diagrams the Model-View-Controller pattern

As a very simple example, the Model might be a container class, such as a stack The View might display the top of the stack The Controller gives commands to push or pop data on the stack The Model (the stack) notifies the View to display a new top-of-stack value whenever the top-of-stack contents change

Any application can be made to fit the Model-View-Controller pattern, but it is par-ticularly well suited to GUI (graphical user interface) design projects, where the View can indeed be a visualization of the state of the Model (A GUI is simply a windowing interface of the form you find in most modern software applications, as opposed to the simple text I/O we have used in this book.) For example, the Model might be an object

to represent your list of computer desktop object names The View could then be a GUI object that produces a screen display of your desktop icons The Controller relays commands to the Model (which is a desktop object) to add or delete names The Model object notifies the View object when the screen needs to be updated

We have presented the Model-View-Controller pattern as if the user were the Con-troller, primarily to simplify the example The Controller need not be under the direct control of the user, but could be some other kind of software or hardware component

Display 20.1 Model-View-Controller Pattern

Controller

Manipulate

action2( )

data1 data2 Model

update( )

View 20_CH20.fm Page 840 Monday, August 18, 2003 2:08 PM

Trang 4

Patterns 841

The most efficient sorting algorithms all seem to follow a similar pattern Expressed recursively, they divide the list of elements to be sorted into two smaller lists, recursively sort the two smaller lists, and then recombine the two sorted lists to obtain the final sorted list In Display 20.2 this pattern is expressed as a template function to sort an array into increasing order using the <

operator.

Our Sorting pattern uses a divide-and-conquer strategy It divides the entire collection of ele-ments to be sorted into two smaller collections, sorts the smaller collections by recursive calls, and then combines the two sorted collections to obtain the final sorted array The following is the heart of our Sorting pattern:

int splitPt = split(a, begin, end);

sort(a, begin, splitPt);

sort(a, splitPt, end);

join(a, begin, splitPt, end);

Although the pattern imposes some minimum requirements on the functions split and join , the pattern does not say exactly how the functions split and join are defined Different defini-tions of split and join will yield different sorting algorithms.

Array indexes are examples of iterators, and we will use the notation [begin, end) from Chapter

19 to comment the pattern and indeed to think about and derive the pattern.

The function split rearranges the elements in the interval [begin, end) and then divides the interval at a split point, splitPt The two smaller intervals [begin, splitPt) and

[splitPt, end) are then sorted by a recursive call to the function sort Note that the split

function both rearranges the elements in the array interval [begin, end) and returns the index

splitPt that divides the interval [begin, end) After the two smaller intervals are sorted, the function join then combines the two sorted intervals to obtain the final sorted version of the entire larger interval.

The pattern says nothing about how the function split rearranges and divides the interval

[begin, end) In a simple case, split might simply choose a value splitPt between begin

and end and divide the interval into the points before splitPt and the points after splitPt , with no rearranging We will see an example that realizes the Sorting pattern by defining split

this way On the other hand, the function split could do something more elaborate, such as moving all the “small” elements to the front of the array and all the “large” elements toward the end of the array This would be a step on the way to fully sorting the values We will also see an example that realizes the Sorting pattern in this second way.

The simplest realization of this Sorting pattern is the m meeeerrrrg m g geeee ssssooo orrrrtttt realization given in Display 20.3

A test program is given in Display 20.4 The merge sort is an example in which the definition of

split is very simple It divides the array into two intervals with no rearranging of elements The

join function is more complicated After the two subintervals are sorted, it merges the two sorted subintervals, copying elements from the array to a temporary array The merging starts by comparing the smallest elements in each smaller sorted interval The smaller of these two

ele-merge sort

20_CH20.fm Page 841 Monday, August 18, 2003 2:08 PM

Trang 5

842 Patterns and UML

Display 20.2 Divide-and-Conquer Sorting Pattern

1 //This is the file sortpattern.cpp.

2 template < class T>

3 int split(T a[], int begin, int end);

4 //Rearranges elements [begin, end) of array a into two intervals

5 //[begin, splitPt) and [splitPt, end), such that the Sorting pattern works.

6 //Returns splitPt

7 template < class T>

8 void join(T a[], int begin, int splitPt, int end);

9 //Combines the elements in the two intervals [begin, split) and

10 //[splitPt, end) in such a way that the Sorting pattern works.

11 template < class T>

12 void sort(T a[], int begin, int end)

13 //Precondition: Interval [begin, end) of a has elements.

14 //Postcondition: The values in the interval [begin, end) have

15 //been rearranged so that a[0] <= a[1] <= <= a[(end - begin) - 1].

16 {

17 if ((end - begin) > 1)

18 {

19 int splitPt = split(a, begin, end);

20 sort(a, begin, splitPt);

21 sort(a, splitPt, end);

22 join(a, begin, splitPt, end);

23 }//else sorting one (or fewer) elements, so do nothing.

24 }

25 template < class T>

26 void sort(T a[], int numberUsed)

27 //Precondition: numberUsed <= declared size of the array a.

28 //The array elements a[0] through a[numberUsed - 1] have values.

29 //Postcondition: The values of a[0] through a[numberUsed - 1] have

30 //been rearranged so that a[0] <= a[1] <= <= a[numberUsed - 1].

31 {

32 sort(a, 0, numberUsed);

33 }

Display 20.3 Merge Sort Realization of Sort Pattern (part 1 of 2)

1 //File mergesort.cpp: the merge sort realization of the Sorting pattern.

2 template < class T>

3 int split(T a[], int begin, int end)

4 {

5 return ((begin + end)/2);

Trang 6

Patterns 843

Display 20.3 Merge Sort Realization of Sort Pattern (part 2 of 2)

7 template < class T>

8 void join(T a[], int begin, int splitPt, int end)

10 T *temp;

11 int intervalSize = (end - begin);

12 temp = new T[intervalSize];

13 int nextLeft = begin; //index for first chunk

14 int nextRight = splitPt; //index for second chunk

15 int i = 0; //index for temp

16 //Merge till one side is exhausted:

17 while ((nextLeft < splitPt) && (nextRight < end))

18 {

19 if (a[nextLeft] < a[nextRight])

20 {

21 temp[i] = a[nextLeft];

22 i++; nextLeft++;

23 }

24 else

25 {

26 temp[i] = a[nextRight];

27 i++; nextRight++;

28 }

29 }

30

31 while (nextLeft < splitPt)//Copy rest of left chunk, if any.

32 {

33 temp[i] = a[nextLeft];

34 i++; nextLeft++;

35 }

36 while (nextRight < end) //Copy rest of right chunk, if any.

37 {

38 temp[i] = a[nextRight];

39 i++; nextRight++;

40 }

41 for (i = 0; i < intervalSize; i++)

42 a[begin + i] = temp[i];

43 }

Trang 7

844 Patterns and UML

Display 20.4 Demonstrating the Sorting Pattern

1 //Tests the Divide-and-Conquer Sorting pattern.

2 #include <iostream>

3 using std::cout;

4 using std::cin;

5 using std::endl;

6 #include "sortpattern.cpp"

7 #include "mergesort.cpp"

8 void fillArray( int a[], int size, int & numberUsed);

9 //Precondition: size is the declared size of the array a.

10 //Postcondition: numberUsed is the number of values stored in a.

11 //a[0] through a[numberUsed - 1] have been filled with

12 //nonnegative integers read from the keyboard.

13

14 int main( )

15 {

16 cout << "This program sorts numbers from lowest to highest.\n";

17 int sampleArray[10], numberUsed;

18 fillArray(sampleArray, 10, numberUsed);

19 sort(sampleArray, numberUsed);

20 cout << "In sorted order the numbers are:\n";

21 for ( int index = 0; index < numberUsed; index++)

22 cout << sampleArray[index] << " ";

23 cout << endl;

24 return 0;

25 }

26 void fillArray( int a[], int size, int & numberUsed)

27 <The rest of the definition of fillArray is given in Display 5.5.>

S AMPLE D IALOGUE

This program sorts numbers from lowest to highest.

Enter up to 10 nonnegative whole numbers.

Mark the end of the list with a negative number.

80 30 50 70 60 90 20 30 40 -1

In sorted order the numbers are:

20 30 30 40 50 60 70 80 90

Trang 8

Patterns 845

ments is the smallest of all the elements in either subinterval, and so it is moved to the first posi-tion in the temporary array The process is then repeated with the remaining elements in the two smaller sorted intervals to find the next smallest element, and so forth.

There is a trade-off between the complexity of the functions split and join You can make either of them simple at the expense of making the other more complicated For the merge sort,

split was simple and join was complicated We next give a realization in which split is com-plicated and join is simple.

Display 20.5 gives the q q quu uiiiicccckkkk ssssooo u orrrrtttt realization of our Sorting pattern If the line

#include "mergesort.cpp"

in Display 20.4 is replaced by the following:

#include "quicksort.cpp"

then the program will give the same input and output The files mergesort.cpp and quick-sort.cpp give two different realizations of the same Sorting pattern.

In the quick-sort realization, the definition of split is quite sophisticated An arbitrary value in the array is chosen; this value is called the ssssp p plllliiiittttttttiiiinn ng n g vvvvaaa g alllluu u ueeee In our realization we chose a[begin ],

as the splitting value, but any value will do equally well The elements in the array are rearranged

so that all those elements that are less than or equal to the splitting value are at the front of the array and all the values that are greater than the splitting value are at the other end of the array;

the splitting value is placed so that it divides the entire array into these smaller and larger ele-ments Note that the smaller elements are not sorted and the larger elements are not sorted, but all the elements before the splitting value are smaller than any of the elements after the splitting value The smaller elements are sorted by a recursive call, the larger elements are sorted by another recursive call, and then these two sorted segments are combined with the join function

In this case the join function is as simple as it possibly could be: It does nothing Since the sorted smaller elements all preceed the sorted larger elements, the entire array is sorted.

(The quick-sort realization can be done without the use of a second temporary array ( temp )

However, that detail would only distract from the message of this example In a real application, you may or may not, depending on details, want to consider the possibility of using the quick-sort realization without a temporary array.)

EFFICIENCY OF THE SORTING PATTERN

Essentially any sorting algorithm can be realized using this Sorting pattern However, the most efficient implementations are those for which the split function divides the array into two substantial chunks, such as half and half, or one-fourth and three-fourths A realization of split that divides the array into one, or a very few, elements and the rest of the array will not be very efficient

quick sort

splitting value

Trang 9

846 Patterns and UML

Display 20.5 Quick-Sort Realization of Sorting Pattern (part 1 of 2)

1 //File quicksort.cpp: the quick-sort realization of the Sorting pattern.

2 #include <algorithm>

3 using std::swap;

4 template < class T>

5 int split(T a[], int begin, int end)

6 {

7 T *temp;

8 int size = (end - begin);

9 temp = new T[size];

10 T splitV = a[begin];

11 int up = 0;

12 int down = size - 1;

13 //Note that a[begin] = splitV is skipped.

14 for ( int i = begin + 1; i < end; i++)

15 {

16 if (a[i] <= splitV)

17 {

18 temp[up] = a[i];

19 up++;

20 }

21 else

22 {

23 temp[down] = a[i];

24 down ;

25 }

26 }

27 //0 <= up = down < size

28 temp[up] = a[begin]; //Positions the split value, splitV.

29 //temp[i] <= splitV for i < up; temp[up] = splitV; temp[i] > splitV for

30 //i > up So, temp[i] <= temp[j] for i in [0, up) and j in [up, end).

31 for (i = 0; i < size; i++)

32 a[begin + i] = temp[i];

33

34 if (up > 0)

35 return (begin + up);

36 else

37 return (begin + 1); //Ensures that both pieces are nonempty.

38 }

Trang 10

Patterns 847

Tip

For example, the merge sort realization of split divides the array into two roughly equal parts, and a merge sort is indeed very efficient It can be shown, although we will

not do so here, that the merge sort runs in time O(N log N) and that any sorting algo-rithm (that meets some minimal and reasonable constraints) cannot be faster than O(N log N) So, a merge sort is, in some sense, optimal.

The quick-sort realization of split divides the array into two portions that might be almost equal or might be very different in size depending on the choice of a splitting value Since in extremely unfortunate cases the split might be very uneven, the

worst-case running time for a quick sort is O(N2), which is much slower than the O(N log N)

we obtained for the merge sort However, for an array that is filled with randomly cho-sen values, most splitting values will produce a division that is close enough to an even

division So, under a suitable definition of average case, the quick sort has an average-case running time that is O(N log N) In practice, the quick sort is one of the

best-performing sorting algorithms

The selection sort algorithm, which we discussed in Chapter 5, divides the array into two pieces, one with a single element and one with the rest of the array interval (see Self-Test Exercise 2) Because of this uneven division, the selection sort has a

worst-case, and even average-case, running time that is O(N2) In practice, the selec-tion sort is not a very fast sorting algorithm, although it does have the virtue of simplic-ity

We do not have room to provide proof of these running times in this book, but you can find such results in almost any data structures or analysis of algorithms text

You should not feel compelled to follow all the fine details of a pattern Patterns are guides, not requirements For example, we did the quick-sort implementation by exactly following the pat-tern in order to have a clean example In practice, we would have taken some liberties Notice that, with a quick sort, the join function does nothing In practice, we would simply eliminate the calls to join These calls incur overhead and accomplish nothing.

Display 20.5 Quick-Sort Realization of Sorting Pattern (part 2 of 2)

39 template < class T>

40 void join(T a[], int begin, int splitPt, int end)

41 {

42 //Nothing to do

43 }

44

Ngày đăng: 04/07/2014, 05:21

TỪ KHÓA LIÊN QUAN