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:
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)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With