liveBook · Manning

Encapsulate what varies

As explained in Object-Oriented Software Design in C++

Encapsulate What Varies is a design principle that isolates aspects of an application likely to change behind stable boundaries.

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

How Object-Oriented Software Design in C++ covers it

How deep it goes
Defines the principle and works through extracting book attributes and matching behavior, without covering broader implementation strategies.
Examples
Uses a library catalogue, Book and Attributes classes, genre and year filters, and searches for books by Stephen King.
What it assumes
Assumes basic classes and member functions while teaching change isolation beyond strict object-oriented encapsulation.
What you can do after
Apply the principle by extracting changing attributes and their matching behavior into a cohesive class.
How it connects
Connects encapsulation with delegation, loose coupling, abstraction, polymorphism, and the Single Responsibility Principle.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Object-Oriented Software Design in C++. The section links point to the parts of the book it draws on.