Moving Function Content into Member Functions
In the implementation of anAccumulator, you moved the content of the Accumulator() function into the Apply() function with minimal changes. Next, take a look at the header and implementation files for aController, which embodies much of what used to be in the Calculator() function (see Listings 19.4 and 19.5).
Listing 19.4. aController Header
1: #ifndef ControllerModuleH
2: #define ControllerModuleH
3:
4: #include "ExternalInterfaceModule.h"
5: #include "AccumulatorModule.h"
6: #include "TapeModule.h"
7:
8: namespace SAMSCalculator
9: {
10: class aController
11: {
12: public:
13:
14: aController
15: (
16: anExternalInterface &theExternalInterface,
17: anAccumulator &theAccumulator,
18: aTape &theTape
19: );
20:
21: int Operate(void);
22:
23: private:
24:
*25: anExternalInterface &myExternalInterface;
*26: anAccumulator &myAccumulator;
*27: aTape &myTape;
28:
29: bool TestOK
30: (
31: anAccumulator &theAccumulator,
32: const aRequest &theRequest,
33: const float theExpectedResult
34:
35: ) const;
36:
37: void SelfTest(void) const;
38: void DisplayAccumulator(void) const;
39: void DisplayTape(void) const;
40: };
41: };
42:
43: #endif
|
The header is straightforward, so let's begin an examination of the implementation with a look at the constructor in Listing 19.5.
Listing 19.5. aController Constructor
1: aController::aController
2: (
3: anExternalInterface &theExternalInterface,
4: anAccumulator &theAccumulator,
5: aTape &theTape
6: ):
7: myExternalInterface(theExternalInterface),
8: myAccumulator(theAccumulator),
9: myTape(theTape)
10: {
11: };
|
| There is no default constructor, so this is the constructor that must be used to make aController. It requires theExternalInterface, theAccumulator, and theTape as arguments. The arguments are references to instances of the indicated classes. In lines 25–27 of the aController header, you can see the member variables that are initialized are also references. |
Normally, you may recall, the compiler will not allow you to assign anything to a reference once the reference has been defined. However, the references in lines 25–27 of the declaration have only been declared at that point. As far as the compiler is concerned, they are not defined until the flow of control has reached the constructor body. Therefore, these references can be initialized by the constructor initializer section in lines 16–18 of the implementation.
Next, let's look at a change to the SelfTest() function in Listing 19.6.
Listing 19.6. aController
SelfTest() Function
1: void aController::SelfTest(void) const
2: {
*3: anAccumulator TestAccumulator;
4:
5: try
6: {
7: if
8: (
*9: TestOK
*10: (
*11: TestAccumulator,
*12: aRequest(aRequest::add,3),
*13: 3
*14: )
15: &&
16: TestOK
17: (
18: TestAccumulator,
19: aRequest(aRequest::subtract,2),
20: 1
21: )
22: &&
23: TestOK
24: (
25: TestAccumulator,
26: aRequest(aRequest::multiply,4),
27: 4
28: )
29: &&
30: TestOK
31: (
32: TestAccumulator,
33: aRequest(aRequest::divide,2),
34: 2
35: )
36: )
37: {
38: cout << "Test OK." << endl;
39: }
40: else
41: {
42: cout << "Test failed." << endl;
43: };
44: }
45: catch (...)
46: {
47: cout << "Test failed because of an exception.";
48: };
49: };
|
| Line 3 shows one of the most important differences from the procedural version: This SelfTest() function instantiates anAccumulator and performs all tests on that object, rather than on myAccumulator. This avoids disruption of the current calculator internal state and simplifies testing. The TestOK() function has been modified to use this object, which is provided as an argument. Lines 9–14 show such a call. |
The SelfTest()
TestAccumulator will be destroyed when SelfTest() is done. This will have no effect on aController's myAccumulator.
Line 12 shows the aRequest constructor being used in the actual arguments to TestOK() to create a temporary request for use by the test.
Next, let's look at the use of one of the references provided by main() to the constructor of aController (see Listing 19.7).
Listing 19.7. aController
DisplayTape() Function
1: void aController::DisplayTape(void) const
2: {
3: int NumberOfElements = myTape.NumberOfElements();
4:
5: for ( int Index = 0; Index < NumberOfElements; Index++ )
6: {
7: myExternalInterface. DisplayRequest(myTape.Element(Index));
8: myExternalInterface.DisplayEndOfLine();
9: };
10:
11: DisplayAccumulator();
12: };
|
| In line 7, myExternalInterface function DisplayRequest() is used to take care of displaying the request Operator() and Operand(). |
Finally, the Operate() function implements the real control flow of the calculator, as shown in Listing 19.8.
Listing 19.8. aController
Operate() Function
1: int aController::Operate(void)
2: {
*3: aRequest Request = myExternalInterface.NextRequest();
4:
*5: while (Request.Operator() != aRequest::stop)
6: {
7: try
8: {
*9: switch (Request.Operator())
10: {
*11: case aRequest::selftest:
12:
13: SelfTest();
14: break;
15:
*16: case aRequest::querytape:
17:
18: DisplayTape();
19: break;
20:
*21: case aRequest::query:
22:
23: DisplayAccumulator();
24: break;
25:
26: default:
27:
*28: myTape.Add(Request);
*29: myAccumulator.Apply(Request);
30:
31: };
32:
33: Request = myExternalInterface.NextRequest();
34: }
*35: catch (runtime_error RuntimeError)
36: {
37: cerr << "Runtime error: " << RuntimeError.what() << endl;
38: }
*39: catch (...)
40: {
41: cerr <<
42: "Non runtime_error exception " << "caught in Controller.Operate." <<
43: endl;
44: };
45: };
46:
47: return 0;
48: };
|
| Line 3 gets aRequest from myExternalInterface and line 5 loops as long as the Operator() is not aRequest::stop. |
Line 9 shows that the switch statement can use the enum nested within the aRequest class.
Lines 11, 16, and 21 are cases receiving the flow of control based on the Request.Operator() tested in line 9.
Lines 28 and 29 use myTape and myAccumulator to perform the central functions of the calculator.
And, of course, lines 35 and 39 catch any exceptions without allowing the loop to be interrupted.