Question

I just...am not entirely sure I understand encapsulation. Maybe it might have to do with the fact that I'm still learning programming in a class and haven't made any like..REAL world programs to be used by other people, but I just don't understand what it's trying to accomplish. I understand that its restricting access to some of a class' members and functions. But like..restricting it from who? I've seen several examples where they have a private data member, but they have public get or set methods that allow the data member to be manipulated anyway. So how was anything restricted or hidden?

My book says the following:

" Encapsulation provides two important advantages:

  1. User code cannot inadvertently corrupt the state of an encapsulated object.
  2. The implementation of an encapsulated class can change over time without requiring changes in user-level code. "

I guess I'm confused about the words they are using. How, or can someone give me an example, of how user code could possibly corrupt the state of an object?

I know my question is kind of all over the place but so is my mind when it comes to thinking about encapsulation so I'm having a difficulty encapsulating all my thoughts about it (..lol)

Was it helpful?

Solution

My favorite example of encapsulation is driving a car.

The typical driver knows how to make a car go forward by turning on the ignition, and stepping on the gas pedal. They don't need to know anything about internal engine combustions in order to get to work every morning.

The gas pedal exposes a very simple interface for operating a very complex machine. Meaning, the really complicated internal details are encapsulated from the driver.

Now, in terms of code, say you want to use a Map of some kind, but you don't know how to write generic hash functions for your keys, or how to implement any of the other underlying details.

In Java you can simply use a HashMap, without having to worry about what the standard library is doing underneath. These details are encapsulated from the user.

OTHER TIPS

Let us tackle both points separately.

1. Class invariants

In programming, reasoning is made easier when you have invariants. For example, you may already have heard of loop invariants:

for (size_t i = 0; i < vec.size(); ++i) {
    // something that does not alter i
}

In this loop, 0 <= i < vec.size() is an invariant which guarantees that vec[i] is always a valid expression.

Class can have invariants too, for example if you consider std::string its size() method returns the number of characters in its buffer. Always.

Now, suppose that you write your own string class:

// Invariant: size represents the number of characters in data.
struct String {
    size_t size;
    char* data;
};

It's good to document what you wish was the invariant, but I can perfectly do:

void reset(String& str) {
    delete str.data;
    str.data = 0;
}

and forget to reset str.size, thus violating the invariant.

However, if you tuck away the class members:

// Invariant: size() returns the number of characters accessible via data()
class String {
public:
    size_t size() const { return _size; }
    char const* data() const { return _data; }

    // methods which maintain the invariant
private:
    size_t _size;
    char* _data;
};

now only you can violate the invariant. Thus in case of bug you have less code to audit.

2. Implementation change insulation

The idea behind is that you should be able to switch the internal representation of information without adapting the users of the class. For example:

class Employee {
public:
    std::string const& name() const { return _name; } // Bad

private:
    std::string _name;
}; // class Employee

Now, if I realize that std::string is not an appropriate representation for name (I would need wide characters, for example):

class Employee {
public:
    std::string const& name() const { return _name; } // error!

private:
    std::basic_string<char32_t> _name;
}; // class Employee

I am stuck. I cannot return a std::string const& any longer (I don't have an internal std::string any longer). I could alter the return of name() to make a copy:

std::string Employee::name() const { return encodeUtf8(_name); }

unfortunately it may still break clients:

std::string const& name(Employee const& e) {
    std::string const& n = e.name(); // Bind temporary variable to const&
    return n;                        // Returns reference to local variable!!
}

Whereas if Employee had been designed from the start with std::string name() const we could make the change without issues.

Note: in real-world usage, you have to make external API with insulation in mind, but internal API may perfectly expose data-representation... at the cost of more changes on your plate when a change is made.

Excellent explanation by @Kepani. I would just like to explain a new statement from http://www.tutorialspoint.com/cplusplus/cpp_data_encapsulation.htm

Encapsulation is an Object Oriented Programming concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse.

So if any outside entity tries to access or change anything in class variables then they unknowingly may harm data contained in it. So in simple terms we basically create a group and limit its usage and access.

Just like car internals can only run a car, your are only asked to drive it and not to go inside it and make the wheels run or use any outside element to move the wheel as they may be out of sync and harm it.

The point about encapsulation is not so much that "you can't change the private data in any way", but rather that the private data's storage itself is hidden. For example:

class Employee
{
  ... 
};

class Company
{
  public:
   std::vector<Employee> employees;
   ... 
};

Now, a vector of Employees may very well work for a company with a small number of employees, but if the company keeps growing, it may cause problems because it's slow to search [In REALITY, probably not!]. However, since we have exposed the std::vector<Employee>, we can't alter what type of storage there is inside Company (without risk of breaking something in the rest of the code). If the employees is private, we can change it to any other type that makes sense for the application.

If we instead have:

class Company
{
   private:
     std::vector<Employee> employees;
     ...
};

we can easily change it to:

class Company
{
    private:
      std::map<std::string, Employee> employees;
};

Now, std::map is a "tree", whcih can be searched in log2(n) steps for n elements, where a vector takes (n/2) searches on average - if we have 10000 employes, that's a difference betweeen about 16 steps and 5000 steps to find the right thing. If the company grows to 100000 employes, the vector will take anoter 45000 steps on average, but only another 3 to find the right one in the map.

The whole point is that "We can change the way the data is stored, and have control how it is accessed".

As an example, suppose we had a very simple string class:

struct String
{
    char * data = nullptr;
    size_t size = 0;

    void resize(size_t new_size) {
        data = realloc(data, new_size);
        size = new_size;
    }
    char & at(size_t i) {
        if (i >= size) throw std::range_error();
        return data[i];
    }
};

We can see that this class has a couple of invariants:

  • data must point to memory allocated by malloc and friends;
  • the size member must match the allocated size.

Without encapsulation, it's easy to break the invariants:

String s;
s.size = 42;
s[10] = 'X';        // BOOM! out-of-range access

s.data = "Hello!";
s.resize(3);        // BOOM! tries to reallocate static memory

By making the data members private, we can prevent people from arbitrarily changing them; they can only be changed through the public interface, which we implement carefully to maintain the invariants.

For bonus points, you might consider how to correctly fix the memory leak in my example; but that's somewhat beyond the scope of this question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top