Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having a lot of difficulty understanding encapsulation

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)

like image 682
FrostyStraw Avatar asked Oct 23 '13 06:10

FrostyStraw


2 Answers

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.

like image 186
yamafontes Avatar answered Sep 28 '22 03:09

yamafontes


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.

like image 37
Matthieu M. Avatar answered Sep 28 '22 03:09

Matthieu M.