Encapsulation In OOP (封装)

抽象和封装是OOP的基础,这些slides关于封装。
于2011-05-06 13:00 Team Learning时给大家做了简单介绍。

PDF slides at Google Doc


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:
  1. returns a (const) reference to the member variable;
  2. 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
没有评论
2011年11月14日 | 归档于 OO
标签:

MOC读码笔记

MOC(Music on Console),顾名思义,就是控制台下的音乐播放器(详见http://moc.daper.net/),因为我比较好奇它的client/server结构,所以抽空读了它的源码。

MOC以标准的C语言写成,代码质量之高出乎我的意料,也让我感叹C语言的简洁和强大。

如下附件PDF文档,是读码过程中的一点记录,涉及配置、server/client的启动过程、通信协议、音频相关知识、播放流程,和插件机制(未分析完全)。

MOC.pdf

2 条评论
2011年11月11日 | 归档于 Code Reading
标签: ,

std::vector<bool>之罪

Premature optimization is the root of all evil... -- D. Knuth

以vector<bool>为例来介绍C++的模板偏特化,不失为一个讨巧的选择。说它讨巧,一是因为来自STL标准库,现取现用;二是因为vector<bool>确实特化得厉害,较一般的vector有很大不同。这一次C++大会上(指2009年在上海举办的第二届C++技术大会),侯捷在讲C++泛型编程时,就用了vector<bool>这个例子。

vector<bool>用一个bit来存一个bool的实现技巧,说来也没什么值得玩味,一听就懂。不过,我恐怕就是因为这个"一听就懂",而忽略了更为重要的东西,即它的种种不可取之处。

仔细看去,吓了一跳,但凡C++领域的大牛,无不对此vector<bool>挥拳相向。Scott Meyers在Effective STL第18条说:避免使用vector<bool>;Bruce Eckel在Thinking In C++ V2里说:vector<bool>是一个跛足的STL容器;Herb Sutter特别撰文:除了早期大家指出的问题外,vector<bool>还有如下问题。

Scott Meyers的阐述最有意思:

As an STL container, there are really only two things wrong with vector<bool>. First, it's not an STL container. Second, it doesn't hold bools. Other than that, there's not much to object to.

身为STL容器,却算不上STL容器;身为vector<bool>,存的却不是bool。因其与众不同的容物方式,reference和iterator必然也将与众不同。于是像下面这样的代码是不能通过编译的(因为reference和iterator使用了proxy):

[code lang="cpp"]
std::vector<bool> vb;
...
bool *b1 = &*vb.begin();
bool &b2 = *vb.begin();
bool &b3 = vb.front();
bool &b4 = vb[0];
[/code]

其实我很不情愿在这里写下(或引用)这些批判的文字,vector<bool>至少可以节省空间呀,这难道不算是一个可取之处甚或优点吗?可惜的是,我随后就读到了Herb Sutter的这一段话:

vector<bool> forces a specific (and potentially bad) optimization choice on all users by enshrining it in the standard. The optimization is premature; different users have different requirements. This too has already hurt users who have been forced to implement workarounds to disable the 'optimization' (e.g., by using a vector<char> and manually casting to/from bool).

2009-12-07 19:35

没有评论
2011年11月10日 | 归档于 C/C++, Programming
标签:

Lua Performance Tips(译文)- 3R原则

没有评论
2011年11月10日 | 归档于 Lua, Programming
标签:

Lua Performance Tips(译文)- 关于字符串

About strings

和表一样,了解Lua实现字符串的细节对高效地使用字符串也会有所帮助。

Lua实现字符串的方式和大多数其他的脚本语言有两点重要的区别。其一,Lua的字符串都是内化的(internalized);这意味着字符串在Lua中都只有一份拷贝。每当一个新字符串出现时,Lua会先检查这个字符串是否已经有一份拷贝,如果有,就重用这份拷贝。内化(internalization)使字符串比较及表索引这样的操作变得非常快,但是字符串的创建会变慢。

其二,Lua的字符串变量从来不会包含字符串本身,包含的只是字符串的引用。这种实现加快了某些字符串操作。比如,对Perl来说,如果你写下这样的语句:$x = $y,$y包含一个字符串,这个赋值语句将复制$y缓冲区里的字符串内容到$x的缓冲区中。如果字符串很长,这一操作代价将非常高。而对Lua来说,这样的赋值语句只不过复制了一个指向实际字符串的指针。

这种使用引用的实现,使某种特定形式的字符串连接变慢了。在Perl里,$s = $s . "x"和$s .= "x"这两者是很不一样的。前一个语句,先得到一份$s的拷贝,然后往这份拷贝的末尾加上"x"。后一个语句,只是简单地把"x"追加到变量$s所持有的内部缓冲区上。所以,第二种连接形式跟字符串大小是无关的(假设缓冲区有足够的空间来存放连接的字符串)。如果在循环中执行这两条语句,那么它们的区别就是算法复杂度的线性阶和平方阶的区别了。比如,以下循环读一个5MB的文件,几乎用了5分钟:
[code]
$x = "";
while (<>) {
$x = $x . $_;
}
[/code]
如果将$x = $x . $替换成$x .= $,则只要0.1秒!

Lua并没有提供这第二种较快的方法,因为Lua的变量并没有与之关联的缓冲区。所以,我们必须使用一个显式的缓冲区:包含字符串片段的表就行。以下循环还是读5MB的文件,费时0.28秒。没Perl那么快,不过也不赖。
[code]
local t = {}
for line in io.lines() do
t[#t + 1] = line
end
s = table.concat(t,"\n")
[/code]

没有评论
2011年11月10日 | 归档于 Lua, Programming
标签: