The ORV Pattern
Before getting into the meat of how this pattern will help solve these problems, there is one prerequisite for its use. For this pattern to work, one must declare all data private and use member access functions (accessors and mutators) as the sole means by which member variables are accessed. This is, of course, on top of any business-specific processing being performed by other member functions of the class. Its also not a bad idea to have all member functions of the class use the same accessors and mutators, thus ensuring consistent access to the data members of your class. The former is absolutely essential not only for good coding practice, but also for this pattern. The latter is strongly encouraged, but not required. That said, let's get on with it.
class EXPORT HPISocket_c : public ClientSocket_c { private: // CLASS INTEGRITY CHECKING DATA AND FUNCTIONS static const long m_sclSerialNumber = 0x001001001; mutable long m_mlSerialNumber; void SetSanity() const throw(); void ClearSanity() const throw(); void SanityCheck(int,string const&) const throw(EXSanity_c); private: // DATA MEMBERS char m_cDataClass; // DON'T ASK, DON'T TELL. string m_DBUser; // USER HITTING THE SERVER string m_DBPassword; // PASSWORD OF SAME string m_HostName; // SERVER TO HIT string m_UserName; // 'REAL' USER Items_t m_Items; // LIST OF ITEMS TO GET Filters_t m_Filters; // RESTRICTION CRITERION Dictionary_c m_Dictionary; // MAP OF ALL VALID FIELD IDs ResultSet_t m_ResultSet; // ACTUAL RETURNED DATA DWORD m_tTimeLastUsed; // USED BY LRU ALGORITHM public: // CLASS MANAGEMENT FUNCTIONS HPISocket_c(string const& User = "",string const& Password = "") throw(EXSanity_c); virtual ~HPISocket_c() throw(EXSanity_c); HPISocket_c(HPISocket_c const& Source) throw(EXSanity_c); public: // OPERATOR OVERLOADS bool operator < (HPISocket_c const& Source) throw(EXSanity_c); bool operator == (HPISocket_c const& Source) throw(EXSanity_c); HPISocket_c const& operator = (HPISocket_c const& Source) throw(EXSanity_c); public: // EXPOSED FUNCTIONS int GetData() throw(EXSanity_c); bool EstablishConnection() throw(EXSanity_c); private: // INTERNAL FUNCTIONS int SendRequest(stringstream const&) throw(EXSanity_c); int ReceiveReply(stringstream&) throw(EXSanity_c); int FormatRequest(stringstream&) throw(EXSanity_c); int Parse() throw(EXSanity_c); int CreateResultSet(stringstream&,Row_t&,Row_t&) throw(EXSanity_c,EXHPI_c); void Singleton(Row_t&,Value_c const&) throw(EXSanity_c); bool AppendField(stringstream& Output, Value_c const& Input, Filter_c const& FidFilter) throw(EXSanity_c); public: // ACCESSOR MEMBER FUNCTIONS string const& DBUser() const throw(EXSanity_c); string const& DBPassword() const throw(EXSanity_c); string const& HostName() const throw(EXSanity_c); string const& UserName() const throw(EXSanity_c); Dictionary_c const& Dictionary() const throw(EXSanity_c); Items_t const& Items() const throw(EXSanity_c); Filters_t const& FilterList() const throw(EXSanity_c); ResultSet_t const& ResultSet() const throw(EXSanity_c); char const DataClass() const throw(EXSanity_c); unsigned long const Rows() const throw(EXSanity_c); DWORD const TimeLastUsed() const throw(EXSanity_c); public: // MUTATOR MEMBER FUNCTIONS string const& DBUser(string const& x) throw(EXSanity_c); string const& DBPassword(string const&) throw(EXSanity_c); string const& HostName(string const&) throw(EXSanity_c); string const& UserName(string const&) throw(EXSanity_c); Dictionary_c const& Dictionary(Dictionary_c const&) throw(EXSanity_c); Items_t const& Items(Items_t const&) throw(EXSanity_c); Filters_t const& FilterList(Filters_t const&) throw(EXSanity_c); char const DataClass(char) throw(EXSanity_c); DWORD const TimeLastUsed(DWORD) throw(EXSanity_c); ResultSet_t const& ResultSet(ResultSet_t const&) throw(EXSanity_c); };
The Object Registration and Validation pattern helps solve this problem, and serves as the basis upon which the other patterns—detailed in my book, but not in this article—are built. It's designed to prevent an invalid object from being used in any capacity. Listing 1 shows the entire class declaration, while listing 2 shows the member functions and variables used to implement the pattern. Note: I've included a real-life class declaration to show how accessors and mutators look, and to show that the footprint of the ORV pattern really isn't all that big. And personally, I find code containing the proverbial foo and bar classes as less than trivial, thus not particularly useful.
class EXPORT HPISocket_c : public ClientSocket_c { // CODE OMMITED FOR BREVITY private: // CLASS INTEGRITY CHECKING DATA AND FUNCTIONS static const long sm_clSerialNumber; mutable long m_mlSerialNumber; void SetSanity() const throw(); void ClearSanity() const throw(); void SanityCheck(int,string const&) const throw(EXSanity_c); // CODE OMMITED FOR BREVITY };
Basically, this pattern is composed of two member variables and three member functions that use them. The member functions are SetSanity, ClearSanity and SanityCheck, as shown in listing 3. They are used to initialize the object, clear the object, and check on the state of the object, respectively. Each function is in inline and privately scoped to A) minimize the related overhead and B) make them invisible to consuming objects as well as children of the class in which they are declared.
inline void HPISocket_c::SetSanity() const throw() { m_mlSerialNumber = sm_clSerialNumber; } inline void HPISocket_c::ClearSanity() const throw() { SanityCheck(__LINE__,__FILE__); m_mlSerialNumber = 0L; } inline void HPISocket_c::SanityCheck(int const iLine,string const& File) const throw(EXSanity_c) { if((this == NULL) || (m_mlSerialNumber != sm_clSerialNumber)) throw EXSanity_c(iLine,File,SANITY_FAILURE,EXBase_c::EXError); }