Dec 29
注: 以下内容稍有处理,隐去一些不适合放上来的内容。

felix021 10:14:18
我昨天跟同学(即sandy)有讨论了一下,他说,我举的例子属于“非本质性复杂”,就是说,是语言层面可以解决的,通过取舍,或者修正历史遗留问题解决,但是框架方面的,比如机制和策略的分离,这个属于本质性的复杂,不是语言层面可以解决的

zja 10:34:01
我觉得做出这个选择的最重要标准是系统的规模。
这句说的挺本质,
写的不错,
另外多发现一些现有框架的优点,
尽可能多的找出设计的的矛盾对立面
比如开放性,封闭性
开放性:当有新的需求到来的时候,能不能快速适应
封闭性:框架严格戒定了我能做什么,我不能做的开发都自行实现
两都又各有优缺点:开放的框架适应性强,封闭的框架清楚,明了,稳定

felix021 10:43:07
嗯 我们的框架非常强大 这个是肯定的

zja 10:43:15
实际类似这样的矛盾对立面尽可能多的了解到的话,
设计上就会提高了,想的面多了,
不存在怎么样做是最完美的,
只能说针对现有的情况,且实很好的解决了问题

felix021 10:43:22
只是我在使用过程中遇到一些困扰

felix021 10:43:39
这种感觉很像是我在用 C++的时候的那种感觉

felix021 10:44:17
仔细想想其实这是不可调和的矛盾,但是还是觉得有些别扭

zja 10:45:42
玩程序就像练武,
一开始不要总武当不好,少林不好,xx也不好,
不如深入的喜欢一门,比如武当,
当你对这一门武艺精通到一定程度,(内功)
那么这时候才是你该跳出来的时候,
其它武功哪地方好,应该熔合进来,
往往这时候,跳出来是最难的了。


felix021 10:46:03
这个比喻好~~受教

zja 10:47:16
如果没有专一的对一门武艺的深入研究,连架子都拉的不对,
内功就更不用提了,
那么根本没有理解道这门武艺一剑封喉的点,
框架有时候也可以这么理解,
分久必合,合久必分,

felix021 10:48:38
嗯~我内功还欠缺的很,这些是这实习几个月的感受。
以前对框架性的东西接触的少,呵呵。

zja 10:48:48
当你使一个框架的时候,总会觉得其它方式更好,
因为他的优点总是被乎略,缺点总是很容易被看到,
草总是远处的绿,
因此要更深入的去理解它的时候,
你要看穿这些东西,
比如这个大框架,比如...框架,我们都很容易发现它的缺点,
但我觉得多去发现它的优点,本质的东西掌握了一定层面,
可能再去想想它的缺点(跳出来)会更有意义,

felix021 10:51:39
嗯 学习这框架之后的确是感受到他们的强大 也是用了一段时间以后才发现有一些不合意的地方

zja 10:52:22
这东西就是一个往返的过程,
这说明进步了,
但我觉得可能还不够,可能过段时候你会又感觉到它的好,
再过段时候你又会感觉到他的不好,
就是多层面多角度的去看问题,
当然,这要你我们慢慢积累的经验,
往往许多东西走到一定层面,就出会现回归,
比如某server的接口拆分,
现在看起来当初合的有很多道理了,
但当初想拆的时候,同样有N多理由需要拆,
这就是一组矛盾,要辨证的去看,
我觉得没有绝对的错,也没有绝对的对,
看应用,看需求,看规模,

felix021 10:56:56
嗯。需求的变化,规模的增长,带来太多不确定性,不是可控的,只能在变化中学习,不断摸索不变的东西积累起来,最后才能以不变应万变。

zja 10:58:36
是的,
康神的一句话,现在记得很清楚,
基本原则不能违背,
基本原则遵守的好的,敌人再绕,总在我们手掌心,
但基本原则打破了,我们发现一个露洞修一个,发现一个修一个,总也修不完,

felix021 10:59:40
嗯 我该再好好回顾设计模式相关的内容 现在再去看 应该会有更深的体会

zja 11:00:14
多关注矛盾很有意义,
比如大小表问题,
比如分表与不分表问题,
比如缓存与一致性问题,等等,
看现有的如何很好的解决的,
这时候你才会想,我遇到这样的矛盾很纠结的时候,这是不是一个很好的参考点,

felix021 11:01:07
嗯 这样的矛盾是本质上的复杂性,如果能想清楚,就能在实际的设计、开发过程中做出最好的权衡

zja 11:01:37
是的,
其实再多想一步,
编程是这样,工作,生活也是这样,

felix021 11:03:29
嗯 我一直觉得程序和生活之间还是有很多相通的地方的
~多谢jiuan了,以上所说的内容我会再好好想想 :)
Dec 28
一个完美的框架,应该是一个实现了对修改关闭,对扩展开放的框架。需要修改,意味这这个框架仍然存在瑕疵;不适合扩展,那就没有被称为框架所需的内涵。当然,完美的框架是不存在的,也没有一个框架能够满足所有的情况,所以需要根据具体的情况来做出选择。

我觉得做出这个选择的最重要标准是系统的规模。

对于一个小型系统,也许是一次性的(没有多少扩展需求),或者代码比较少(修改起来比较容易),那么框架的存在就不一定是必要的了。不论是自己设计一个框架,抑或是学习并采用一个现有的框架,都需要耗费比较多的时间精力,对于一个小型系统而言,很可能得不偿失。当然,不需要框架并不意味着可以随意开发(随意开发的过程是痛苦的,越往后越明显,felix深有感受...),高内聚低耦合等基本的设计原则还是必须遵从的,这样能够使开发过程更简单。更严格一点说,一个良好的设计本身就带有一些框架的性质。

对于一个中型系统,如果规模虽然稍大,但是仍然可以在少数几个人的掌握之下,那么一个强大的框架对开发是有相当助益的。在这种情况下,学习框架的开销是可以接受的,一次投入多次产出:在掌握了框架以后,每当需要增加新的功能的时候,只需要按照框架的规则写一个模块并插入到系统中,多余的事情都由框架来完成,非常轻松。

对于一个大型系统,其规模已经大到不是几个人可以掌握的了,那么在开发之初对框架的选择是至关重要的。因为当系统大到一定规模再更换框架,在很多情况下是不现实的,所以在开发之前就必须有足够的考虑。一个强大的框架是必须的,但我觉得这还不是全部:这个框架还必须简单。俗话说计划赶不上变化,当初设计得再好的功能,逐渐也可能会不适应后来的需求(PM一句话,RD两行泪=.=),以至于成为整个系统的累赘。所以框架还必须要简单(无为而治)——这个简单不是说框架需要提供少的功能。引用讲述Unix设计哲学的一段话:
引用
The distinction between mechanism and policy is one of the best ideas behind the Unix design. Most programming problems can indeed be split into two parts: “what capabilities are to be provided” (the mechanism) and “how those capabilities can be used” (the policy). If the two issues are addressed by different parts of the program, or even by different programs altogether, the software package is much easier to develop and to adapt to particular needs.
框架就应该是提供mechanism(机制)的部分,而策略,就应该是具体的某个功能。这样一个框架就简单了,当你用这个框架进行开发的时候,你是在使用框架提供的机制来实现你的策略,你可以把握策略的实现,而不用担心在什么地方框架带来了难以察觉的干扰。

这些是非常理想化的选择,但是实际中又存在许多难以避免的问题。

其一,温水煮青蛙
这个寓言大家都耳熟能详了,虽然其真实性有待验证,但是其寓意还是很有警示意义的。一个系统的规模并不一定是刚开始的时候就可以知道的。比如创业的小公司,刚开始可能只是设计一个小型或者中型的系统,直到某一天发现系统负载或者是其扩展性已经或者将在可见的未来无法满足要求。这时候为了发展只能进行底层的重构。

其二,机制和策略的矛盾
Unix哲学考虑得很好,程序应该分成机制和策略两块,但是将这二者完全划分清楚未必是好的,甚至是不可能的。如果框架完全不干涉策略的实现,那么意味着每一个模块的开发都会有更多的工作量。如果框架过多地干涉策略,那么意味着框架过于复杂,掌握框架的代价更高。所以这里存在需要权衡的地方,而权衡的标准,还是的得考虑系统的规模和复杂性。于是又回到上一个问题了。

其三,...
需求的不确定、某个语言的局限这样一些具体的问题,也许不应该放在这里,但是在实际的开发和维护过程中,他们又的确会困扰开发者。甚至让人头疼。

----

说了这么多,感觉有点虚,因为没有什么特别具体的例子。公司里的项目不适合拿来讨论,但是正好前几天跟同事聊到一个例子,觉得比较适合放在这里说说。

这个例子就是C++,和C。最近一段时间我对C++有一些排斥,所以很难保证我下面将要说的内容的中立性,有任何问题欢迎探讨。

我觉得C++本质上就是C的一个框架。C++包装了C语言,提供了一些方便开发的特性,比如对面向对象的支持,比如对泛型的支持。根据上面提到的命名方式,我们可以认为这些特性就是这个"C框架"提供的机制,而C++程序员开发相应的程序,就是使用这些机制来完成其特定的策略。另一方面,C++不仅提供了机制,还在一定程度上影响了策略的实现。

比如说,在C++中,类的成员函数是否具有virtual属性,决定了这个类(的这个成员函数)是否可以实现多态。当然,这可以看作是一种机制的实现,但是它不可避免地影响了具体的策略。更特别的是,如果一个被继承的类的析构函数不具有virtual属性的话,可能程序在运行过程中会出现内存泄漏。

比如说,当你编写一个inline函数的时候,你可能期望它总是被inline,但是可能由于某些你不知道的限制,实际上inline属性并没有生效,它还是作为一个函数被反复调用。

再比如说,当你使用一个模板的时候,你可能不会考虑到这些模板对内存的占用。但实际上,模板的每一次特例化,都会使得几乎相同的函数在内存中存在两份拷贝(也许这个例子有些牵强)。另一个模板的例子是在特例化的时候没有特别注意类型,比如说make_pair(1, 2.0)实际上创建了一个pair<int, float>,而不是pair<float, float>。

这些是多少都可以算做是这个C框架不仅提供机制,还影响策略的例子。我相信还有很多类似的例子是我不知道的,但是糟糕的是,要掌握这个C框架的代价太大。这也就是我近来对C++产生排斥感的主要原因(另一个原因是我担心C++用多了,以后写代码会越来越懒~~~)。

说了这些不是想说C++是多么不堪、不能使用,去年暑假有很长一段时间我在仔细学习STL,其中有很多很伟大的设计。从这个角度来说,对C++我是心存敬畏的。从框架的角度来看,C++实际上是一个很强大的C框架,很适合快速开发,比如写一个acm程序,我肯定用C++,好好用上STL。做一个小型的系统,我也许也会选用C++。我只是觉得,这个强大的框架做得不够简单,如果要考虑大型系统的开发,务必要仔细权衡。

---

最后,使用一个很著名的设计原则来结束这篇日志:

KISS: Keep It Simple, Stupid.
Jun 14
也许你听过所谓"标准"答案:因为圆形的井盖不会掉下去。

但是也许不需要考虑这么深这么复杂,从适配器模式的角度考虑,你只要这么回答就可以了:

其实很简单,因为井口是圆的,它提供了一个圆形的接口,所以它期望一个圆形的盖子,由此你不得不设计一个圆形的井盖。

方形的盖子也许容易掉下去,但是那不是主要问题,现在的问题是,方形的盖子不适合盖圆形的井口。

--

虽然今天要早起,但是还是一不小心看了太多东西,比如上面这个回答就是在"云风的Blog"的这篇文章的评论里面看到的:

http://blog.codingnow.com/2008/06/object_oriented.html

很有收获。

另外google了一下这个回答,看到另一个搞笑的(或者很有深度的?),顺便帖出来:
May 29
前天在火车上想到的一点东西。

1。命令模式
定义一个抽象类(或者接口@Java)为所有类型的下落物件,包含一些方法诸如旋转,下落,左右移动等,使得具体的开发过程中只需要根据该抽象类编码,实现了解耦,也就可以更方便地添加各种类型的下落物件。

2。工厂模式
使用工厂模式来(随机地)生成下落物件,那么在主程序中就完全和具体的下落物件解耦了。

3。是否需要单件模式
如果说把容纳所有小方块的面板设计为一个class,那么它确实只能生成一个;但是似乎程序中不至于出现要实例化它两次的情况。
对于下落物件,在每次操作过程中确实只有一个,但是有些扩展设计可以显示出下一个下落物件,所以单件模式恐怕不好使。
是不是有一句话说,如果过度使用单件模式,那么程序就是有问题的?

4。一些其他的东西。
图形的实现。
我还是不喜欢用图形库,要去搞懂怎么用图形库显示一个东西,我实在没兴趣。
但是如果用字符实现的话,可能会因为刷屏导致很闪- -||
此外,怎么捕获控制台下的按键?这个一直没去研究过。。。虽然用tc很简单,但是程序写起来会很挫。。。
May 25
前些天在《Head First设计模式》上看到了这一原则。
书上的例子非常好,鸭子的飞行行为和嘎嘎叫的行为确实可以从父类继承得到
但是如果出现橡皮鸭子,它不会叫也不会飞,如果要把它归类为鸭子,那么代码就有得写了(更详细内容参见该书)。

今天突然想起另外一个问题,某天看到一句话:Java采用接口,避免了多继承带来的问题。
于是去Google了一下多继承存在的问题,然后就发现,除了继承带来的冗余数据和方法之外,另一个大问题就是钻石问题。
@ 2009-05-26 下面举的例子有点问题,详情参见篇后sandy的评论
话说端午节快到了,该是吃粽子的时候了——
我们知道米饭和豆沙都是(继承)食品,然后我们用米饭包住豆沙放在锅里蒸一下,我们就得到了(派生)粽子。
这样的结构是:
    食品
    /     \
米饭   豆沙
    \     /
     粽子
这是一个菱形,于是被成为钻石问题:如果食品里有一个成员 "好吃程度",那么粽子的"好吃程度"是继承自哪一个呢?
在C++这个支持多继承的语言里面,如果直接使用
粽子 粽子a;
cout << 粽子a.好吃程度 << endl;

然后就会发现编译器(g++)报错:对成员"好吃程度"的请求有歧义(ambiguous)。
不过好在有3个解决方式:
1。重新定义一个好吃程度覆盖米饭和豆沙的好吃程度。
2。指明是哪个父类的好吃程度:例如  粽子a.米饭::好吃程度  或者  粽子a.豆沙::好吃程度
3。使用虚拟继承(虚基类),详情请STFW。

于是我突然想到其实这里如果使用"多用组合,少用继承"的设计原则(很像解决方法2),那么问题就解决拉:
class 粽子: public 食品{
private:
    米饭 米饭a;
    豆沙 豆沙a;
public:
    粽子(){
        好吃程度 = (米饭a.好吃程度 + 豆沙a.好吃程度) / 2; //其实我觉得豆沙a的权值应该大些=.=
    }
};

当然这只是一个很初步的想法,代码也非常不严谨,谨以此抛砖引玉,欢迎交流。
分页: 1/1 第一页 1 最后页 [ 显示模式: 摘要 | 列表 ]