Encapsulation In OOP (封装)
于2011-05-06 13:00 Team Learning时给大家做了简单介绍。
An example from Annotation library
[code lang="cpp"]
class Node {
public:
typedef std::map<std::string, std::string> attributes_t;
std::string getAttribute(const std::string& key) const;
void setAttribute(const std::string& key, const std::string& value);
const attributes_t& getAttributes() const; // ???
private:
attributes_t attributes;
};
[/code]
Method 'getAttributes' is very generous (慷慨) because it:
- returns a (const) reference to the member variable;
- exposes the details of the implementation (i.e., std::map).
To avoid returning reference
[code lang="cpp"]
attributes_t getAttributes() const;
[/code]
Return a copy, less efficient.
To avoid exposing implementation details
We find the only place to call 'getAttributes' is to serialize all attributes:
[code lang="cpp"]
foreach(..., node->getAttributes()) {
// Create a XML element for this attribute.
}
[/code]
So, what about to provide a method like this?
[code lang="cpp"]
struct AttributeHandler {
void operator()(const string& k, const string& v) {
visit(k, v);
}
private:
virtual void visit(const string& k, const string& v) = 0;
};
void foreachAttribute(AttributeHandler& ah);
[/code]
And why not public member variable?
[code lang="cpp"]
class Node {
public:
attributes_t attributes;
};
[/code]
So that we don't have to provide any methods for attributes.
Don't do it!
Hybrids (混血, 杂交) are the worst of both worlds
Hybrid: Half object and half data structure.
"Public variables" tempts other external functions to use those variables the way a procedural program would use a data structure.
Hybrids make it hard to add new functions but also make it hard to add new data structures.
- It's hard to add new data structures to procedural code because all the functions must change.
- It's hard to add new functions to OO code because all the classes must change.
Back to the beginning,
Why shall we keep variables private?
We don't want anyone else to depend on them.
We want to keep the freedom to change their type or implementation on a whim or an impulse.
Why, then, do so many programmers automatically add getters and setters to their objects, exposing their private variables as if they were public?
A Case About Encapsulation
取自《冒号课堂》,见References
[code lang="java"]
class Person {
private Date birthday;
private boolean sex;
private Person[] children;
}
[/code]
Get birthday
[code lang="java"]
public Date getBirthday() { // ???
return birthday;
}
[/code]
Method 'getBirthday' is dangerous because it returns the reference to a mutable member variable.
It's the same as the 'getAttributes' as we saw before:
[code lang="cpp"]
attributes_t& getAttributes();
[/code]
Make 'getBirthday' return a clone of birthday:
[code lang="java"]
public Date getBirthday() {
return birthday == null ? null : new Date(birthday.getTime());
}
[/code]
Set birthday
[code lang="java"]
public Person(Date birthday, boolean sex) { // ???
this.birthday = birthday;
this.sex = sex;
}
public void setBirthday(Date birthday) { // ???
this.birthday = birthday;
}
[/code]
Get children
[code lang="java"]
public Person[] getChildren() {
return children;
}
[/code]
Array is also mutable. So, clone the children?
[code lang="java"]
public Person[] getChildren() {
if (children == null || children.length == 0) { return null; }
Person[] childrenCopy = new Person[children.length];
System.arraycopy(children, 0, childrenCopy, 0, children.length);
return childrenCopy;
}
[/code]
Get children (cont.)
Or replace 'getChildren' with:
[code lang="java"]
getChild(int index);
getChildCount();
getFirstChild();
getLastChild();
findChildByXXX();
...
[/code]
Set children
Method 'setChildren' is too generous (太慷慨了):
[code lang="java"]
public void setChildren(Person[] children) {
this.children = children;
}
[/code]
Consider to replace with:
[code lang="java"]
addChild(Person child);
removeChild(Person child);
clearChild();
[/code]
Get sex
[code lang="java"]
public boolean getSex() {
return sex;
}
[/code]
- Change boolean to Boolean so that we have an extra null value for sex? Or
- Use int, char, string?
- Use enumerated type?
- ...
Get sex (cont.)
What about this?
[code lang="java"]
public boolean isMale();
public boolean isFemale();
[/code]
Set sex
[code lang="java"]
public void setSex(<T> sex) {
this.sex = sex;
}
[/code]
Getter and setter don't have to be provided as a pair.
Get age
[code lang="java"]
public int computeAge() { // ???
// compute age by birthday.
// ...
}
[/code]
The 'compute' in the name exposes the implementation details.
Consider to change to:
[code lang="java"]
public int getAge() {
// ...
}
[/code]
References
- Robert C. Matin, Clean Code - A Handbook of Agile Software Craftsmanship.
- 郑晖, 《冒号课堂-编程范式与OOP思想》
- Wm. Paul Rogers, Encapsulation is not information hiding.