Encapsulate What Varies isolates code likely to change behind a stable boundary. Separating variable code from stable code prevents changes from leaking through the application. section 1.8 section 2.3.2
A catalogue that directly handles book titles, authors, years, and genres must change whenever those attributes change. Extracting them into an Attributes class removes that coupling. Attributes also owns matching and case-insensitive comparisons, while Catalogue delegates the work. section 2.3.2
The refactoring produces several related design improvements:
- Attributes::is_match() compares fields and supports unspecified genre or year values as don’t-care targets. section 2.3.2
- Catalogue remains focused on storing books and delegates attribute matching to the object that owns those details. section 2.4
Searching the catalogue
A target with an empty title and first name, last name King, year 0, and genre horror finds Carrie and It: A Novel. The search behavior stays inside Attributes rather than spreading comparisons through Catalogue.
Listing 2.5 (Program 2.2 Books-2): Attributes.h
#include <iostream>
using namespace std;
enum class Genre #1
{ #1
ADVENTURE, CLASSICS, DETECTIVE, FANTASY, HISTORIC, #1
HORROR, ROMANCE, SCIFI, UNSPECIFIED #1
}; #1
inline ostream& operator <<(ostream& ostr, const Genre& genre)
{
switch (genre)
{
case Genre::ADVENTURE: ostr << "adventure"; break;
case Genre::CLASSICS: ostr << "classics"; break;
case Genre::DETECTIVE: ostr << "detective"; break;
case Genre::FANTASY: ostr << "fantasy"; break;
case Genre::HISTORIC: ostr << "historic"; break;
case Genre::HORROR: ostr << "horror"; break;
case Genre::ROMANCE: ostr << "romance"; break;
case Genre::SCIFI: ostr << "scifi"; break;
default: ostr << "unspecified"; break;
}
return ostr;
}
class Attributes
{
public:
Attributes(const string ttl,
const string lst, const string fst,
const int yr, const Genre gen)
: title(ttl), last(lst), first(fst),
year(yr), genre(gen) {}
string get_title() const { return title; }
string get_last() const { return last; }
string get_first() const { return first; }
int get_year() const { return year; }
Genre get_genre() const { return genre; }
bool is_match(const Attributes& target_attrs) const #2
{ #2
return equal_ignore_case(target_attrs.get_title(), title) #2
&& equal_ignore_case(target_attrs.get_last(), last) #2
&& equal_ignore_case(target_attrs.get_first(), first) #2
&& ( (target_attrs.get_year() == 0) #2
|| (target_attrs.get_year() == year)) #2
&& ( (target_attrs.get_genre() == Genre::UNSPECIFIED) #2
|| (target_attrs.get_genre() == genre)); #2
} #2
private:
string title;
string last;
string first;
int year; #3
Genre genre; #3
static bool equal_ignore_case(const string string1, #4
const string string2) ...
};
inline ostream& operator <<(ostream& ostr, const Attributes& attrs)
{
ostr << "{TITLE: '" << attrs.get_title()
<< "', LAST: '" << attrs.get_last()
<< "', FIRST: '" << attrs.get_first()
<< "', YEAR: " << attrs.get_year() #5
<< ", GENRE: " << attrs.get_genre() << "}"; #5
return ostr;
}
#1 The genre attribute
#2 Explicitly match attributes, including year and genre.
#3 The added year and genre attributes
#4 Static member function moved from class Catalogue
#5 Also, output the year and genre attributes.
Inspect the Attributes class to see the varying book data collected behind one boundary. section 2.3.2
