2hiding.html
来自「Visual C++ has been one of most effectiv」· HTML 代码 · 共 287 行
HTML
287 行
<HTML>
<HEAD>
<TITLE>Hiding Implementation Details</TITLE>
<meta name="description" content="Hiding the details of implementation">
<meta name="keywords" content="design, implementation, methodology, programming, enum, namespace">
<link rel="stylesheet" href="rs.css" tppabs="http://www.relisoft.com/book/rs.css">
</HEAD>
<BODY background="margin.gif" tppabs="http://www.relisoft.com/book/images/margin.gif" bgcolor="#ffffe0">
<!-- Main Table -->
<table cellpadding="6">
<tr>
<td width="78">
<td>
<h3>Code Review 2: Hiding Implementation Details</h3>
<p class=topics>Embedded classes, protected constructors, hiding constants, anonymous enums, namespaces.</p>
</td></tr>
<tr>
<td class=margin valign=top>
<a href="calc1.zip" tppabs="http://www.relisoft.com/book/tech/source/calc1.zip">
<img src="brace.gif" tppabs="http://www.relisoft.com/book/images/brace.gif" width=16 height=16 border=1 alt="Download!"><br>source</a>
</td>
<td><P>A good software engineer is like a spy. He exposes information to his collaborators on the <I>need-to-know</I> basis, because knowing too much may get them in trouble. I抦 not warning here about the dangers of industrial espionage. I抦 talking about the constant struggle with complexity. The more details are hidden, the simpler it is to understand what抯 going on.
<h3>Using Embedded Classes</h3>
<P>The class <var>Link</var> is only used internally by the linked list and its friend the sequencer. Frankly, nobody else should even know about its existence. The potential for code reuse of the class <var>Link</var> outside of the class <var>List</var> is minimal. So why don抰 we hide the definition of <var>Link</var> inside the private section of the class definition of <var>List</var>.
<!-- Code --><table width="100%" cellspacing = 10><tr>
<td class =codetable>
<pre>
class List
{
friend class ListSeq;
public:
List ();
~List ();
void Add (int id);
private:
// nested class definition
<font color="Red">class Link
{
public:
Link (Link * pNext, int id)
: _pNext (pNext), _id (id) {}
Link * Next () const { return _pNext; }
int Id () const { return _id; }
private:
Link * _pNext;
int _id;
};</font>
private:
Link const * GetHead () { return _pHead; }
Link* _pHead;
};
</pre>
</table><!-- End Code -->
<P>The syntax of class embedding is self-explanatory.
<P>Class <var>ListSeq</var> has a data member that is a pointer to <var>Link</var>. Being a friend of <var>List</var>, it has no problem accessing the private definition of class <var>Link</var>. However, it has to qualify the name <var>Link</var> with the name of the enclosing class <var>List</var>--the new name becomes <var>List::Link</var>.
<!-- Code --><table width="100%" cellspacing = 10><tr>
<td class =codetable>
<pre>
class ListSeq
{
public:
bool AtEnd () const { return _pLink == 0; }
void Advance () { _pLink = _pLink->Next(); }
int GetId () const { return _pLink->Id (); }
protected:
ListSeq (List const & list)
: _pLink (list.GetHead ()) {}
private:
// usage of nested class
<b>List::Link</b> const *_pLink;
};
</pre>
</table><!-- End Code -->
<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10> </td>
<td bgcolor="#cccccc" class=sidebar>
One way to look at a class declaration is to see it as a collection of methods, data members and types. So far we've been dealing only with methods and data members; now we can see how a type--in this case, another class--is defined within a class. A class may also create aliases for other types using typedefs. We'll see more examples of these techniques later.
</td></table>
<!-- End Sidebar -->
<p>The classes <var>List</var> and <var>ListSeq</var> went through some additional privatization (in the case of <var>ListSeq</var>, it should probably be called "protectization"). I made the <var>GetHead</var> method private, but I made <var>ListSeq</var> a friend, so it can still call it. I also made the constructor of <var>ListSeq</var> protected, because we never create it in our program--we only use objects of the derived class, <var>IdSeq</var>.
<p>I might have gone too far with privatization here, making these classes more difficult to reuse. It's important, however, to know how far you can go and make an informed decision when to stop.
<h3>Combining Classes</h3>
<p>Conceptually, the sequencer object is very closely tied to the list object. This relationship is somehow reflected in our code by having <var>ListSeq</var> be a friend of <var>List</var>. But we can do much better than that--we can embed the sequencer class inside the list class. This time, however, we don't want to make it private--we want the clients of <var>List</var> to be able to use it. As you know, ouside of the embedding class, the client may only access the embedded class by prefixing it with the name of the outer class. In this case, the (scope-resolution) prefix would be <var>List::</var>. It makes sense then to shorten the name of the embedded class to <var>Seq</var>. On the outside it will be seen as <var>List::Seq</var>, and on the inside (of List) there is no danger of name conflict.
<p>Here's the modified declaration of <var>List</var>:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>class List
{
public:
List ();
~List ();
void Add (int id);
private:
class Link
{
public:
Link (Link * pNext, int id)
: _pNext (pNext), _id (id) {}
Link * Next () const { return _pNext; }
int Id () const { return _id; }
private:
Link * _pNext;
int _id;
};
public:
<font color="Red">class Seq
{
public:
Seq (List const & list)
: _pLink (list.GetHead ()) {}
bool AtEnd () const { return _pLink == 0; }
void Advance () { _pLink = _pLink->Next (); }
int GetId () const { return _pLink->Id (); }
private:
Link const * _pLink; // current link
};
friend Seq;</font>
private:
Link const * GetHead () const { return _pHead; }
Link * _pHead;
};
</pre>
</td></tr>
</table>
<!-- End Code -->
<p>Notice, by the way, how I declared <var>Seq</var> to be a friend of <var>List</var> following its class declaration. At that point the compiler knows that <var>Seq</var> is a class.
<p>The only client of our sequencer is the hash table sequencer, <var>IdSeq</var>. We have to modify its definition accordingly.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>class IdSeq: public <b>List::Seq</b>
{
public:
IdSeq (HTable const & htab, char const * str)
: <b>List::Seq</b> (htab.Find (str)) {}
};
</pre>
</td></tr>
</table>
<!-- End Code -->
<p>And, while we're at it, how about moving this class definition where it belongs, inside the class <var>HTable</var>? As before, we can shorten its name to <var>Seq</var> and export it as <var>HTable::Seq</var>. And here's how we will use it inside <var>SymbolTable::Find</var>
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>
for (HTable::Seq seq (_htab, str);
!seq.AtEnd ();
seq.Advance ())
</pre>
</td></tr>
</table>
<!-- End Code -->
<h3>Combining Things using Namespaces</h3>
<p>There is one more example of a set of related entities that we would like to combine more tightly in our program. But this time it's a mixture of classes and data. I'm talking about the whole complex of <var>FunctionTable</var>, <var>FunctionEntry</var> and <var>FunctionArray</var> (add to it also the definition of <var>CoTan</var> which is never used outside of the context of the function table). Of course, I could embed <var>FunctionEntry</var> inside <var>FunctionTable</var>, make <var>CoTan</var> a static method and declare <var>FunctionArray</var> a static member (we discussed this option earlier). There is however a better solution. In C++ we can create a higher-level grouping called a <var>namespace</var>. Just look at the names of objects we're trying to combine. Except for <var>CoTan</var>, they all share the same prefix, <var>Function</var>. So let's call our namespace <var>Function</var> and start by embedding the class definition of <var>Table</var> (formerly known as <var>FunctionTable</var>) in it.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>namespace Function
{
class Table
{
public:
Table (SymbolTable & symTab);
~Table () { delete []_pFun; }
int Size () const { return _size; }
PFun GetFun (int id) { return _pFun [id]; }
private:
PFun * _pFun;
int _size;
};
}</pre>
</td></tr>
</table>
<!-- End Code -->
<p>The beauty of a namespace it that you can continue it in the implementation file. Here's the condensed version of the file <span class="file">funtab.cpp</span>:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>namespace Function
{
double CoTan (double x) {...}
class Entry {...};
Entry Array [] =
{...};
Table::Table (SymbolTable & symTab)
: _size(sizeof Array / sizeof Array [0])
{...}
}</pre>
</td></tr>
</table>
<!-- End Code -->
<p>As you might have guessed, the next step is to replace all occurrences of <var>FunctionTable</var> in the rest of the program by <var>Function::Table</var>. The only tricky part is the forward declaration in the header <span class="file">parse.h</span>. You can't just say <var>class Function::Table;</var>, because the compiler hasn't seen the declaration of the <var>Function</var> namespace (remember, the point of using a forward declaration was to avoid including <span class="file">funtab.h</span>). We have to tell the compiler not only that <var>Table</var> is a class, but also that it's declared inside the <var>Function</var> namespace. Here's how we do it:
<!-- Code -->
<table width="100%" cellspacing=10><tr>
<td class=codeTable>
<pre>namespace Function
{
class Table;
}
class Parser
{
public:
Parser (Scanner & scanner,
Store & store,
Function::Table & funTab,
SymbolTable & symTab );
...
};
</pre>
</td></tr>
</table>
<!-- End Code -->
<p>
<!-- Sidebar -->
<table width="100%" border=0 cellpadding=5><tr>
<td width=10> </td>
<td bgcolor="#cccccc" class=sidebar>
As a general rule, a lot of traditional naming conventions for classes, functions and objects are being replaced in modern C++ by by the use of embedding classes or namespaces and scope resolution prefixes.
</td></table>
<!-- End Sidebar -->
<p>By the way, the whole C++ Standard Library is enclosed in a namespace. Its name is <var>std</var>. Now you understand these prefixes <var>std::</var> in front of <var>cin</var>, <var>cout</var>, <var>endl</var>, etc. (You've also learned how to avoid these prefixes using the <var>using</var> keyword.)
<h3>Hiding Constants in Enumerations</h3>
<P>There are several constants in our program that are specific to the implementation of certain classes. It would be natural to hide the definitions of these constants inside the definitions of classes that use them. It turns out that we can do it using enums. We don抰 even have to give names to enums梩hey can be anonymous.
<P>Look how many ways of defining constants there are in C++. There is the old-style C <var>#define</var> preprocessor macro, there is a type-safe global <var>const</var> and, finally, there is the minimum-scope <var>enum</var>. Which one is the best? It all depends on type. If you need a typed constant, say, a <var>double</var> or a (user defined) <var>Vector</var>, use a global <var>const</var>. If you just need a generic integral type constant梐s in the case of an array bound條ook at its scope. If it抯 needed by one class only, or a closely related group of classes, use an <var>enum</var>. If its scope is larger, use a global <var>const int</var>. A <var>#define</var> is a hack that can be used to bypass type checking or avoid type conversions--avoid it at all costs. By the way, debuggers don抰 see the names of constants introduced through <var>#define</var>s. They appear as literal numerical values. It might be a problem sometimes when you don抰 remember what that 74 stood for.
<P>Here抯 the first example of hiding constants using enumerations. The constant <var>idNotFound</var> is specific to the <var>SymbolTable</var>.
<!-- Code --><table width="100%" cellspacing = 10><tr>
<td class =codetable>
<pre>
class SymbolTable
{
public:
// Embedded anonymous enum
<b>enum { idNotFound = -1 }</b>;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?