📄 7serial.html
字号:
<html>
<head>
<title>Advanced Features of C++</title>
<link rel="stylesheet" href="../rs.css">
</head>
<body background="../images/margin.gif" bgcolor="#ffffe0">
<!-- Main Table -->
<table cellpadding="6">
<tr>
<td width="78"> </td>
<td>
<h2>Code Review 7: Serialization and Deserialization</h2>
</td></tr>
<tr>
<td class=margin valign=top>
<a href="source/serial.zip">
<img src="../images/brace.gif" width=16 height=16 border=1 alt="Download!"><br>source</a>
</td>
<td>
<h3>The Calculator Object</h3>
<p>Look at <var>main</var>: There are too many objects there. The symbol table, the function table and the store. All three objects have the same lifespan--the duration of the program execution. They have to be initialized in particular order and all three of them are passed to the constructor of the parser. They just scream to be combined into a single object called--you guessed it--the <var>Calculator</var>. Embedding them in the right order inside this class will take care of the correct order of initialization.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>class Calculator
{
friend class Parser;
public:
Calculator ()
: _funTab (_symTab),
_store (_symTab)
{}
private:
Store & GetStore () { return _store; }
PFun GetFun (int id) const { return _funTab.GetFun (id); }
bool IsFunction (int id) const { return id < _funTab.Size (); }
int AddSymbol (std::string const & str)
{
return _symTab.ForceAdd (str);
}
int FindSymbol (std::string const & str) const
{
return _symTab.Find (str);
}
SymbolTable _symTab;
Function::Table _funTab;
Store _store;
};</pre>
</td></tr>
</table>
<!-- End Code -->
<p>Of course, now we have to make appropriate changes (read: simplifications) in <var>main</var> and in the parser. Here are just a few examples--in the declaration of the parser:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>class Parser
{
public:
Parser (Scanner & scanner, Calculator & calc);
...
private:
...
Scanner & _scanner;
auto_ptr<Node> _pTree;
Status _status;
Calculator & _calc;
};</pre>
</td></tr>
</table>
<!-- End Code -->
and in its implementation.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>// Factor := Ident
if (id == SymbolTable::idNotFound)
{
id = _calc.AddSymbol (strSymbol);
}
pNode = auto_ptr<Node> (new VarNode (id, _calc.GetStore ()));</pre>
</td></tr>
</table>
<!-- End Code -->
<p>Have you noticed something? We just went ahead and made another major top-level change in our project, just like this! In fact it was almost trivial to do, with just a little help from the compiler. Here's the prescription.
<p>Start in the spot in <var>main</var> where the symbol table, function table and store are defined (constructed). Replace them with the new object, calculator. Declare the class for <var>Calculator</var> and write a constructor for it. Now, if you are really lazy and tired of thinking, fire off the compiler. It will immediately tell you what to do next: You have to modify the constructor of the parser. You have to pass it the calculator rather than its three separate parts. At this point you might notice that it will be necessary to change the class declaration of the <var>Parser</var> to let it store a reference to the <var>Calculator</var>. Or, you could run the compiler again and let it remind you of it. Next, you will notice all the compilation errors in the implementation of <var>Parser</var>. You can fix them one-by-one, adding new methods to the <var>Calculator</var> as the need arises. The whole procedure is so simple that you might ask an intern who has just started working on the project to do it with minimal supervision.
<p>The moral of this story is that it's never too late to work on the improvement of the high level structure of the project. The truth is that you rarely get it right the first time. And, by the way, you have just seen the method of top-down program modification. You start from the top and let the compiler lead you all the way down to the nitty-gritty details of the implementation. That's the third part of the top-down methodology which consists of:
<ul>
<li>Top-down design
<li>Top-down implementation and
<li>Top-down modification.
</ul>
<p>I can't stress enough the importance of the top-down methodology. I have yet to see a clean, well written piece of code that was created bottom-up. You'll hear people saying that some things are better done top-down, others bottom-up. Some people will say that starting from the middle and expanding in both directions is the best way to go. Take all such statements with a very big grain of salt.
<p>It is a fact that bottom-up development is more natural when you have no idea what you're doing-- when your goal is not to write a specific program, but rather to play around with some "neat stuff." It's an easy way, for instance, to learn the interface to some obscure subsystem that you might want to use. Bottom-up development is also preferable if you're not very good at design or if you dislike just sitting there and thinking instead of coding. It is a plus if you enjoy long hours of debugging or have somebody else (hopefully not the end user!) to debug your code.
<p>Finally, if you embrace the bottom-up philosophy, you'll have to resign yourself to never being able to write a professionally looking piece of code. Your programs will always look to the trained eye like those electronics projects created with Radio Shack parts, on breadboards, with bent wires sticking out in all directions and batteries held together with rubber bands.
<p>The real reason I decided to finally get rid of the top level mess and introduce the <var>Calculator</var> object was to simplify the job of adding a new piece of functionality. Every time the management asks you to add new features, take the opportunity to sneak in a little rewrite of the existing code. The code isn't good enough if it hasn't been rewritten at least three times. I'm serious!
<p>By rewriting I don't mean throwing it away and starting from scratch. Just take your time every now and then to improve the structure of each part of the project. It will pay off tremendously. It will actually shorten the development cycle. Of course, if you have stress-puppy managers, you'll have a hard time convincing them about it. They will keep running around shouting nonsense like "if it ain't broken, don't fix it" or "if we don't ship it tomorrow, we are all dead." The moment you buy into that, you're doomed! You'll never be able to do anything right and you'll be spending more and more time fixing the scaffolding and chasing bugs in some low quality temporary code pronounced to be of the "ain't broken" quality. Welcome to the maintenance nightmare!
<p>So here we are, almost at the end of our project, when we are told that if we don't provide a command to save and restore the state of the calculator from a file, we're dead. Fortunately, we can add this feature to the program without much trouble and, as a bonus, do some more cleanup.
<h3>Command Parser</h3>
<p>We'll go about adding new functionality in an orderly fashion. We have to provide the user with a way to input commands. So far we've had a hack for inputting the <i>quit</i> command--an empty line was interpreted as "quit." Now that we want to add two more commands, <i>save</i> and <i>restore</i>, we can as well find a more general solution. I probably don't have to tell you that, but...
<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
<td bgcolor="#ffffff" class=defTable>
Whenever there are more than two special cases, you should generalize them.</td></tr>
</table>
<!-- End Definition -->
<p>The calculator expects expressions from the user. Let's distinguish commands from expressions by prefixing them with an exclamation sign. Exclamation has the natural connotation of commanding somebody to do something. We'll use a prefix rather than a suffix to simplify our parsing. We'll also make <i>quit</i> a regular command; to be input as "!q". We'll even remind the user of this command when the calculator starts.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>cerr << "\n!q to quit\n";</pre>
</td></tr>
</table>
<!-- End Code -->
<p>The new Scanner method <i>IsCommand</i> simply checks for the leading exclamation sign. Once we have established that a line of text is a command, we create a simple <var>CommandParser</var> to parse and execute it.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>if (!scanner.IsEmpty ())
{
if (scanner.IsCommand())
{
CommandParser parser (scanner, calc);
status = parser.Execute ();
}
else
{
Parser parser (scanner, calc);
status = parser.Parse ();
if (status == stOk)
{
double result = parser.Calculate ();
cout << result << endl;
}
else
{
cerr << "Syntax error\n";
}
}
}</pre>
</td></tr>
</table>
<!-- End Code -->
<p>Here's the new class, <var>CommandParser</var>,
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>class CommandParser
{
enum ECommand
{
comSave,
comLoad,
comQuit,
comError
};
public:
CommandParser (Scanner & scanner, Calculator & calc);
Status Execute ();
private:
Status Load (std::string const & nameFile);
Status Save (std::string const & nameFile);
Scanner & _scanner;
Calculator & _calc;
ECommand _command;
};</pre>
</td></tr>
</table>
<!-- End Code -->
and this is how it parses a command.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>CommandParser::CommandParser (Scanner & scanner, Calculator & calc)
: _scanner (scanner),
_calc (calc)
{
assert (_scanner.IsCommand());
_scanner.Accept ();
std::string name = _scanner.GetSymbolName ();
switch (name [0])
{
case 'q':
case 'Q':
_command = comQuit;
break;
case 's':
case 'S':
_command = comSave;
break;
case 'l':
case 'L':
_command = comLoad;
break;
default:
_command = comError;
break;
}
}
</pre>
</td></tr>
</table>
<!-- End Code -->
<p>Notice that we use the <var>Scanner</var> method <var>GetSymbolName</var> to retrieve the command string.
<p>The <i>load</i> and <i>save</i> commands require an argument, the file name. We retrieve it from the scanner using, again, the method <var>SymbolName</var>.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>Status CommandParser::Execute ()
{
scanner.AcceptCommand ();
std::string nameFile;
switch (_command)
{
case comSave:
nameFile = _scanner.GetSymbolName ();
return Save (nameFile);
case comLoad:
nameFile = _scanner.GetSymbolName ();
return Load (nameFile);
case comQuit:
cerr << "Good Bye!" << endl;
return stQuit;
case comError:
cerr << "Error" << endl;
return stError;
}
return stOk;
}</pre>
</td></tr>
</table>
<!-- End Code -->
<p>We use the new method, <var>AcceptCommand</var>, to accept the command and read the following string. The string, presumably a file name, must be terminated by a whitespace. Notice that we can't use the regular <var>Accept</var> method of the <var>Scanner</var>, because it will only read strings that have the form of C++ identifiers. It would stop, for instance, after reading a dot, which is considered a perfectly valid part of a file name. (If we were stricter, we would even make provisions for file names with embedded spaces. We'd just require them to be enclosed in quotation marks.)
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void Scanner::AcceptCommand ()
{
ReadChar ();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -