我自己的感觉, 大概也和我自己的工作习惯有关系, 一旦一个东西怎么做比较有把
握了, 我就会指导手下, 怎么怎么分对象, 每个对象做什么什么 --- 直接到类的
时候较多, 我这边的项目多数是要求性能高, 总体代码量未必是非常大,
interface 用得不多.
对架构设计不是非常有把握的东西, 我会自己来做, 这个时候, 我自己动用的东
西, 主要是 gp 为多了.
比如说这里的IStack接口必然会依赖于Stack里的元素类型。而元素类型和Stack的概念没什么关系,foo也未必关心该元素的类型。这就是GP大显身手的地方了。
在 07-9-16,red...@gmail.com<red...@gmail.com> 写道:
而concept感觉在这方面就会灵活很多,至少不用动整个的继承体系。
GP 程序, 只有到将各个类, 模板类最终组装的时候, 才会开始耦合, 耦合度低得多了.
这两年我设计的程序慢慢转向这个风格:
1. 模块之间的接口, 主要用 free function, delegate, 少用类, 如果用到类,
只用简单的单个类, 不将类之间的关系弄到接口中.
2. 一个模块内部的实现, GP + OB, 基本很少用到 interface 和 继承, 对象组合
用得较多.
lijie 写道:
haskell中的type class是否就可以看做是interface + concept呢?
(我很少在 OO, GP 这么高的层次对自己的经验做总结, 可能三不五时想起以前碰
到过的一些问题).
用 OO 设计系统, 扩展灵活性是需要事先准备的, GP 则不同, 这点带来的影响其
实很大.
拿 log4j, log4cpp 之类的东西来说, 一边是前端可能有多个实例可以提交 log,
一边可能是console, file, syslog 等多种 appender, 要支持这种灵活的体系,
需要事先将这些interface/基类, 以及之间的关系, 具体的log 类, append 类之
前就准备好, 不然的话, 以后就需要复杂的重构了.
如果是 GP, 反正是最后组装, 那我最初设计的类, log 知道后端有一个接收函数
即可, 刚开始可以直接用 console append 做后端, 需要实例化的时候, 做一个粘
合层, 将一个 log 和 console appender 结合起来即可, log 其实不管后端是什
么类型, appender 也不管前端是什么; 万一以后要支持 多log 前端对多
appender, 基本上只需要写一个加强的粘合层就好, 原来写好的代码可以做到不改
都可以.
可能 OO 支持者说, OO 方法, 先抽象一个 ILogFront, 一个 ILogAppender 接口
也可以, 然后在体系架构中增加一个多路器.
但是
1. 如果这里多一个 interface, 那里多一个 interface, 复杂度就白白多了很多
了, 广东话说, 多个香炉多个鬼, 东西多了, 危险总是多了
2. 传说, 人同时能够把握的维度数目是有限的; 我自己知道同一个时候, 上下文
context 太复杂, 我会晕的,
3. 这整个体系还是需要事先设计好, 如果我比较笨, 可能就只有等到所有代码写
完了, 才想起还要加一个类到里面去, 就要调整很多代码了.
这里只是多一个间接层的问题, 某些时候, 一个类自己有几个相关的间接层, 或者
某个间接层相连的, 还是间接层, 都不奇怪.
-------------------
OO 的方式, 多半会需要为各种灵活性准备很多中间层, 各种基类多半都需要知道
这些中间层, 架构会比较复杂, 而实际应用用没有必要用到这么多灵活性的时候,
白白带来很多阅读理解和维护和运行的开销.
GP 的方式会好很多, 基本部件做好了, 缺省的粘合部件不合适, 可以另外换一个
粘合部件, 复杂性没有影响到基本的部件. 而且, 的粘合层, 无论 OO, GP 都少不
了, 耦合度放在这边, 也比较自然.
pongba 写道:
这东西没有实际业务领域对应的对象, 只是负责将另外两种对象之间连接起来.
象 log4j 这种东西, 给你的用来输出 warn, error 各种信息的那个对象(忘了叫什么名字了), 是有业务领域意义的; 具体对应
到 file, console, syslog 的 appender 对象, 也是有业务意义的, 但是, 将这两个东西组装起来, 使得前端一个
或者多个对象的输出, 可以到后端一个多个 appender 中去的这个玩意, 是没有业务领域意义的, 只是为了让事情可以转起来, 需要它的工
作.
还有, 一个 IM 软件, 网络收包解析部分的代码, 和界面显示的代码, 也需要一个东西串起来, 这里可能还需要并行-->串行的转换, 这玩意
其实也没有业务领域的意义, 也可以认为是一个粘合层.
很多时候, 软件结构, 为了增加灵活性, 无非就是增加中间层, 这些中间层, 应该也可以认为是粘合层.
<<unix 编程艺术>> 一书中, 认为 OOP 带来最大的问题, 就是粘合层过厚, 传统 C 编码, 粘合层是很薄的, 你可以找那本书看
看.
> --
> 刘未鹏(pongba)|C++的罗浮宫http://blog.csdn.net/pongba
> TopLanguagehttp://groups.google.com/group/pongba- 隐藏被引用文字 -
>
> - 显示引用的文字 -
胶合层
当自顶向下和自底向上的汽车撞在一起的时候,情形通常是一片混乱。顶层的应用逻辑和底层的元操作必须由胶合层来阻隔。
几十年来,Unix程序员明白了一个道理,胶合层是令人厌恶的东西,应该让粘结层越薄越好,此乃性命攸关之大事!胶合层应该用来把东西粘在一
起,而不是用来掩盖层与层之间的冲突和裂痕。
拿上面那个浏览器的例子来说,粘结层包括:把由HTML解析而来的文档对象应设为显示缓冲区里的位图。这部分代码是声名狼藉的难写,错误百
出。HTML解析和GUI库的错误和缺陷都会在这层里表现出来。
浏览器的胶合层不仅要在规范和元操作之间充当中介者,还要在若干不同的外部规范中间充当中介者----HTTP网络协议的行为,HTML文档结构,
不同的图形和多媒体格式,以及来自GUI的用户行为。
一层胶合层已经很容易出错了,但这还不是最糟糕的。如果一个设计者意识到胶合层的存在,并且试图去用自己的一套数据结构或者对象把这个胶合层组
织到一个中间层中,那么结果就会是多出两个胶合层----一个在那个中间层之上,一个在其下。那些聪明但却欠缺历练的程序员经常积极地跳到这个陷阱里去。他
们把基本的类(应用逻辑,中层和元操作)做得像课本上的例子那样漂亮,最后却为了把这些漂亮的代码粘合到一起而在很多个越来越厚的胶合层中忙得团团转,
直到困死。
C语言本身被认为是薄胶合层的良好范例。
Unix和面向对象语言
自1980年代中期开始,新的语言纷纷宣称自己对面向对象编程提供直接支持。
OO设计的概念首先在图形系统,GUI系统和仿真系统里被证明是很有价值的。然而历史证明,在这些领域之外,OO并没有带来明显的益处,这令很
多人感到吃惊,感到幻灭。应该试图去理解其中的道理,这将会是很有意义的事情。
在Unix传统的模块化技术与围绕OO语言发展起来的模式之间,存在着一些冲突和张力。Unix程序员较之其他人对于OO抱有更大的怀疑态度。
原因之一是多样性法则。OO被说成是软件复杂性问题唯一正确的解决之道,这未免令人生疑。不过,还有更深层的原因。
我们刚才提到,Unix的模块化传统中,薄胶合层是一个重要原则,从顶层程序对象到下层硬件之间的抽象层越少越好。
这部分是因为C的影响。在C中间模拟真正的对象是件很费力的工作。因此,叠置一大堆抽象层简直是要人老命的事情。因此,在C中的对象层次倾向于
平坦和透明。长此以往,Unix程序员使用其他语言也习惯于薄粘接/浅层次。
OO语言使得抽象变得容易了----也许是太容易了。它鼓励整个架构具有厚厚的、精致的胶合层。如果问题域确实复杂,确实需要大量的抽象,这可能是
好事。但是这也是很糟糕的事,因为程序员最后会把很简单的事情用很复杂的办法来做,仅仅因为他们可以这么做。
所有的OO语言都有有一些倾向,吸引程序员跳进"过度分层"的陷阱里。对象框架和对象浏览器并不能取代好的设计和文档,但是却经常被看成一回
事。太多的层次破坏了透明性----我们很难看穿下面的东西,很难在思想上对于代码的功能建立清晰的模型。简单性、明晰性和透明性一口气全被破坏了,结果代
码充满了晦涩的错误,带来严重的维护性问题。
这种情况还在继续恶化,很多培训班把厚厚的软件分层当成好东西传授----你拥有那一大堆类被认为是数据中所潜藏的知识的体现。问题在于,在胶合层
中的"smart data"经常与程序所操作的自然实体无关,而仅仅只是胶合本身。(一个确切的标志就是抽象子类的不断增值,以及所谓
的"minxins"。)
Unix程序员对这些问题有本能的直觉。Unix中OO语言没有能够替代非OO语言如C,Perl(虽然支持OO,但很少有人用
到),Shell等,这大概是原因之一。Unix世界里对于OO的批评比别的领域中要尖刻得多。Unix程序员知道什么时候不应该用OO,就算是要用
OO,他们也尽可能的保持对象设计的简洁。正如Michael Padlipsky所说:"如果你知道你在干什么,三层足够;如果你不知道你在干什么,
十七层也没用。"
OO在GUI、仿真和图形领域里取得成功的原因,可能是因为在这些领域中,相对而言,比较容易解决"类型存在与否"的问题。例如,在GUI和图
形系统中,类和可视对象之间存在着自然的映射关系。如果你发现自己所增加的类并不直接映射可视对象,则你也可能就会发现胶合层已经变得很厚。
On 9月16日, 下午8时20分, pongba <pon...@gmail.com> wrote:
Eric Raymond谈模块化原则,胶合层和面向对象的缺陷
模块化 ---- Keep it clean, keep it simple
程序员所面对的复杂性日益增大,而划分代码的方法也有一个自然的发展过程。一开始,软件不过是一大块机器代码。最早的过程化语言带来了"依据子
例程划分代码"的观念,接下来我们发明了程序库,为不同程序提供公共服务。再下来我们发明了独立地址空间和进程间通信。如今,我们已经惯常于把程序系统
分布在彼此相隔万里的互联主机上。
Unix的早期开发者同时也是软件模块化思想的先锋。在他们之前,模块化只是计算机科学的思想,还不是工程实践。其弘旨是:要开发可正确工作的
复杂软件的唯一途径,就是用定义良好的界面把诸多简单模块连结起来,形成整个系统。只有如此,大部分局部问题才可能在局部得到修正或者优化,而不至于破
坏整体。
今天所有的程序员都被教育要在子程序的层次上进行模块化。有些幸运者掌握了在抽象数据类型(ADT)层次上的模块化能力,就已经被认为是好的设
计者了。如今的设计模式运动,就是希望把这个层次再提高一步,发现那些有助于对程序大型结构进行良好组织的成功的设计抽象。
封装和最佳模块体积
模块代码的第一重要特质乃封装。封装良好的模块决不互相暴露内部信息。它们不去窥测其他模块的实现,不胡乱地共享全局数据,而是通过API互相
通信。
模块之间的API有双重身份。在实现层次,API函数扼守模块之间的连接点,阻止内部信息外泄。在设计层次,API实际上定义了你的系统架
构。
模块分解越细致,模块越小,API的定义越重要。整体的复杂度、错误的可能性也随之降低。
然而,如果分解过度,导致过小的模块,会得到意想不到的情况。下面的图来自Hatton 1997年的统计数据。可见到图形是U形的。
Hatton的数据是在不同语言和不同平台上经过广泛统计的道德,故具有可信性。可见,代码量在200到400逻辑行之间的模块效果最佳。
紧凑性和正交性
代码并不是唯一具有所谓"最佳单块体积"的软件要素。语言和APIs同样受到人类认识能力的限制而逃不出Hatton的U曲线。
因此,Unix程序员在设计APIs、命令集、协议以及其他东西的艰苦思索过程中发现了模块化的两个要素:紧密性和正交性。
紧凑性
紧凑性是指设计对于人脑而言"易于理解和接受的程度"。
紧凑的软件工具跟顺手的日常手工工具一样拥有很多优点。它让人们乐于使用,用起来方便自然,大大提高你的效率,而且安全可靠,不像那些复杂的工
具,动不动就弄伤你。
紧凑并不意味着功能弱,如果一个设计建筑在容易理解的抽象基础之上,那么它可以非常强大和灵活,同时又保持紧凑。紧凑也不意味着容易学习,你可
能必须先理解抽象之下精致的概念模型,之后才能感到容易。
软件很少有绝对紧凑的,但是很多软件是相对紧凑的,它们有一个紧凑的工作集,一个功能子集,可以满足80%甚至更多的专家级用户的日常需求。
举例来说,Unix系统调用API就是紧凑的,而C标准库则不是。Unix工具中make(1)是紧凑的,autoconf(1)和
automake(1)则不是。标记语言中,HTML是紧凑的,而DocBook不是。Man(7)是紧凑的,troff(1)不是。通用语言中,C和
Python是紧凑的,Perl, Java, Emacs Lisp和Shell不是。C++则是"反紧凑"的----该语言的设计者自己都承认,他不指
望有人能够完全理解C++。
不过也不是说不紧凑的设计就是邪恶的或者糟糕的。有些问题域太复杂,紧凑的设计无法实现。这里强调紧凑性,其目的并不是希望读者把它看成是一个
绝对要求,而是像Unix程序员那样,合理对待,尽力实践,决不轻易放弃。
正交性
正交性有助于你将复杂设计紧凑化,在这一点上,它的重要性非常突出。在纯正交的设计中,操作没有副作用,每一个行动只改变一件事情,不影响其它
东西。对于系统中的每一个属性,有且只有一条途径去改变它。
电脑监视器是正交性的良好范例,你可以调明暗而不影响饱和度,色彩平衡的控制也彼此独立。如果不是如此,想象一下你会遇到多大麻烦!
软件中的非正交性设计太多了。举个例子,格式转换函数的作者经常会不假思索地要一个源文件的路径名作为参数,可是输入经常来自一个打开的文件句
柄,如果设计成正交的,则该函数不应该有"打开文件"的副作用,则将来这个函数可以处理来自各种源头的数据流。
Doug McIloy的名言"只做好一件事"经常被认为是关于简单性的箴言,而其实这句话里对于正交性的强调至少是同样分量的。
非正交性的主要问题是副作用扰乱了程序员和用户的思维模型,而且经常被遗忘,带来程度不同的灾难。
Unix API整体上是一个很好的正交设计范例,正因为如此,在其他平台上的C库都尽力模仿它。所以就算你不是Unix程序员,也值得研究
它。
SPOT法则
The Pragmatic Programmer 一书中指出了一类特别重要的正交性,"Don't Repeat Yourself"法
则:任何知识点应当是唯一的,无歧义的,在系统中以确定无疑的方式存在的。在本书里,我遵循Brain Kernighan的建议,把这个法则称为
Single Point Of Truth,或简称SPOT法则。
重复导致不一致,对代码构成潜在的危害。因为如果你要改变重复信息中的一个,就必须记得改变它所有的化身。这体现出你根本没有清晰地组织你的代
码。
软件是多层的
宽泛地说,当你在设计函数或者对象层次结构(hierarchy)时,有两个方向可供选择,而你的选择对于代码的分层(layering)将有
重大的影响。
自顶向下,自底向上
一个方向是自下而上,从问题域中一定会用到具体的操作出发向上----从具体到抽象。举个例子,如果你要为磁盘驱动器开发一个固件
(firmware),则在低层可以有一些操作原语如"磁头移至某物理块","读物理快","写物理快","切换LED"等。
另一个方向是自上而下,从抽象到具体,从最顶层的程序或者逻辑整体描述规范出发向下到个别的操作。比如某人设计一个可以控制不同介质的海量存储
器控制器,可以从抽象的操作出发,比如"寻址逻辑块","读逻辑块","写逻辑块","切换指示设备"。这上面所说的硬件层次的操作很不相同,
一个大一些的例子是Web浏览器。自顶向下的设计从一个规范说明出发----能接受哪些URL类型,能显示哪些图像,对Java和
JavaScript支持如何,等等。与这个自顶向下的视图相对应的实现层是应用的主事件循环。
同时Web浏览器必须要调用大量的专用元操作(primitives)。比如建立网络连接,发送数据,接受响应,比如GUI相关的操作,比如HTML解
析操作。
从哪端开始,这事关重大,因为你的起点很可能对你的终点构成了限制。如果你完全的自顶向下,到一定时候你可能会尴尬地发现,逻辑上所需要的元操
作实际上不能完全实现。如果你完全的自低向上,你会发现自己做了大量与程序无关的事情。
从1960年代起,初级程序员们就被教导说,写程序应该"自顶向下,逐渐细分"。自顶向下在下面三个条件成立的时候,是很好的经验:a. 你可
以事先经确定义程序的需求,b. 在实现过程中,该规范不大可能变化,c. 在最底层,你有充分的自由来选择完成工作的方式。
程序层次越高,这些条件越容易被满足。然而,即使在最高层次的应用程序开发中,这些条件仍然经常不成立。
出入自我保护,程序员试图双管齐下。一方面以自顶向下的应用逻辑表达抽象规范,另一方面用函数和库来归纳领域内的元操作,在高层设计发生变化时
可以复用之。
Unix程序员主要做系统程序设计,所以倾向于自底向上的开发方式。
一般来说,自底向上的开发更有吸引力,它使你以一种探索的方式开发,给你相对充裕的时间去细化含糊的规范,也更加符合程序员天生的懒惰----一旦
出错,报废的代码通常要少得多。
不过实际的代码一般是自顶向下和自底向上向结合的。两者经常在一个项目中运用,这直接导致了胶合层(glue layer)的出现。
Huafeng Mo 写道:
> 这是否意味着,应该将业务实体做成尽可能小的对象,然后用形同算法的自由函
> 数,或类函数的东西操作、组合这些对象。因为自由函数之类可以泛化,并且很
> 容易地替换,以此大幅提高灵活性,简化开发。
> 我的理解没错吧。:)
>
例子是我以前写的一个 rpc client, server 框架, 不必使用 IDL, 用宏定义好函
数, client 和 server 就可以通信, exception 也可以传递.
定义通信函数的例子如下:
#define bk_server_Functions() \
_DF_BEGIN() \
\
_DF(bksvr, 0, bool, login, _P2(const char * username, const char *
password) ) \
_DF(bksvr, 1, bool, test, _P1(boost::uint16_t v) ) \
_DF(bksvr, 2, void, testv, _P1(boost::uint16_t v) ) \
_DF(bksvr, 3, void, testv1, _P1(boost::uint16_t v) ) \
_DF(bksvr, 4, char *, tests, _P1(const char * name) ) \
\
_DF_END()
#define bk_client_Functions() \
_DF_BEGIN() \
\
_DF(bkclt, 0, char * , get_name, _P0() ) \
_DF(bkclt, 1, void, tests, _P1(const char * name) ) \
\
_DF_END()
客户端代码用法下附, rpc_client 这个模板类就是一个典型的粘合层, 定义等会
再给你一个回帖中给出.
// test.cpp : Defines the entry point for the console application.
//
#pragma warning(disable:4819)
#include "public.h"
#include "npre.h"
#include "rpc_prj_functions.h"
#include <rpc/rpc_client.h>
#include <rpc/rpc_calling_stub.h>
#include <rpc/rpc_dispatcher.h> // client 也提供服务, 需要 dispatcher
#ifndef POSIXTIME_PARSERS_HPP___
#include <boost/date_time/posix_time/time_parsers.hpp>
#endif
#ifndef BOOST_ASIO_BASIC_DEADLINE_TIMER_HPP
#include <boost/asio/deadline_timer.hpp>
#endif
#ifndef BOOST_THREAD_WEK070601_HPP
#include <boost/thread/thread.hpp>
#endif
#define _DEF_STUB_HELPER
#include <rpc/rpc_macro_control.h>
// 定义好服务器的函数, 供 client 调用
bk_server_Functions()
//-----------------------
// client 端的代码
class I_bkclient : public rpc::i_serving_obj
{
public:
//-----------------------------------------
// 这是需要定义的
typedef I_bkclient this_type;
//-----------------------------------------
// 定义 纯虚的 rpc 函数, 以及 lc_xxx 的非虚函数
#define _DEF_INTERFACE_FUNC
#include <rpc/rpc_macro_control.h>
bk_client_Functions()
//-----------------------------------------
// 定义调度函数, 函数定义如下:
// void void call_function(rpc::request * request)
#define _DEF_FUNC_DISPATCH
#include <rpc/rpc_macro_control.h>
bk_client_Functions()
};
class bk_client : public I_bkclient
{
public:
#define _DEF_FUNC_IMPLETEMENT
#include <rpc/rpc_macro_control.h>
_DF(bkclt, 0, char * , get_name, _P0() )
{
}
_DF(bkclt, 1, void, tests, _P1(const char * name) )
{
printf("\ngot server callback");
printf("\nbk_client::tests('%s')", name);
}
};
//------------------------------------
struct io_service_thread
{
io_service_thread(boost::asio::io_service &io_service) :
io_service_(io_service)
{
}
void operator() ()
{
io_service_.run();
LOG_DEBUG((L_INFO, "io_service terminated" ));
}
private:
boost::asio::io_service &io_service_;
};
void timer_handler(const boost::asio::error& error)
{
if (!error)
{
// Timer expired.
}
}
bool init_log()
{
return true;
}
int main(int argc, char *argv[])
{
init_log();
boost::asio::io_service io_service;
// 设置一个 timer, io_service 就不会因为没有活动 connection 就退出了.
boost::asio::deadline_timer timer(io_service);
//boost::asio::deadline_timer timer(io_service,
boost::posix_time::time_from_string("2100-12-07 23:59:59.000"));
// 100 年
timer.expires_from_now(boost::posix_time::hours(24*30*12*100));
// Start an asynchronous wait.
timer.async_wait(timer_handler);
//---------------------------
// 初始化 client
bk_client client_serving_obj;
rpc::simple_service_dispatcher server_dispatcher(client_serving_obj);
rpc::rpc_client<rpc::simple_service_dispatcher> client(io_service,
server_dispatcher);
//---------------------------
// 启动另外一个线程执行 io_serivce
io_service_thread iothread(io_service);
boost::thread thread(iothread);
//---------------------------
{
rpc::connection::scoped_lock lock(client);
for (int i=0; i<3000; i++)
client.connect(lock, "localhost", "8000", 5);
while( ! client.if_connected_dirty_get() )
{
putchar('.');
Sleep(10);
}
LOG_DEBUG((L_INFO, "\nclient connected." ));
rpc::block_call_stub bcs(client, true);
try
{
for (int i=0; i<1000; i++)
bool result = bcs.make_call(lock, rpc::bksvr_login, "abcde", "12345");
//bcs.make_call( rpc::bksvr_testv, boost::uint16_t(1));
printf("\nreply got.");
}
catch (std::exception & expt)
{
LOG_DEBUG((L_INFO, "\nmake_call got exception: %s", expt.what() ));
}
//client.connect(lock, "localhost", "8000", 50);
while( ! client.if_connected_dirty_get() )
{
putchar('.');
Sleep(10);
}
try
{
bool result = bcs.make_call(lock, rpc::bksvr_login, "abcde", "12345");
//bcs.make_call( rpc::bksvr_testv, boost::uint16_t(1));
printf("\nreply got.");
}
catch (std::exception & expt)
{
LOG_DEBUG((L_INFO, "\nmake_call got exception: %s", expt.what() ));
}
try
{
char * res_s = bcs.make_call(lock, rpc::bksvr_tests, "testcall");
printf("\n%s", res_s);
}
catch (std::exception & expt)
{
LOG_DEBUG((L_INFO, "\n make_call got exception: %s", expt.what() ));
}
}
printf("\npress enter to quit.\n");
getchar();
io_service.interrupt();
Sleep(100); // 等待 io_service 清理, 否则这里的变量先析构, 就会有问题.
不是正式做法.
return 1;
}
rpc_client 本身没有多少功能, 只是将相关的类都组织了起来而已, 这是一个胶
合类或者粘合类, 怎么说都好.
// 主动建立 rpc 连接
// 自身就是一个 rpc connection,
#ifndef __RPC_CLIENT_H
#define __RPC_CLIENT_H
#ifndef BOOST_ASIO_IO_SERVICE_HPP
#include <boost/asio/io_service.hpp>
#endif
#ifndef BOOST_ASIO_ARG_HPP
#include <boost/asio/placeholders.hpp>
#endif
#ifndef BOOST_BIND_HPP_INCLUDED
#include <boost/bind.hpp>
#endif
#ifndef __RPC_CONNECTION_H
#include "rpc/rpc_connection.h"
#endif
namespace rpc
{
// 每一个 rpc_client 只能连接一个目标.
template <class dispatcher_t>
class rpc_client: public i_connections_manager, public rpc::connection
{
typedef rpc_client<dispatcher_t> this_type;
public:
rpc_client(boost::asio::io_service& io_service, dispatcher_t &dispatcher):
rpc::connection(io_service, *this, dispatcher), // 有包收到, 会交给
dispatcher_.
dispatcher_(dispatcher)
{
this->add_reference(); // 给自己增加一个 reference, 从而不会被
remove_reference 析构
}
protected:
// i_connections_manager
virtual void connection_lost_connect(rpc::connection & connection, const
std::exception& e)
{
LOG_DEBUG((L_INFO, "\nrpc_client::connection_lost_connect() got
exception: %s", e.what() ));
// 不必做这个, 底层会做.
//net::connection::lock_visitor lv(*this);
}
// 连接要被删除之前, 通知连接管理器
virtual void last_reference_removed(rpc::connection *connection)
{
// 这里的 connection 不是单独申请的, 不必 delete.
// 反倒是可以尝试重新连接.
// 已经增加了一个 reference, 不应该被调用到
BOOST_ASSERT(false);
}
private:
dispatcher_t & dispatcher_;
};
} // namespace rpc
#endif //__RPC_CLIENT_H
居然空格都没有了, 到 VS 2005 里面格式化一下再看吧.
GP 我没有试过用作程序级别的 API, 我只是在模块内部使用较多, 所以不怎么影响程序的整体架构.
至于stack 类那种, 功能比较通用的类, 一般我都是使用 GP 来做, 不用 OO.
使用 free function/delegate 这种, 好处是隔离得比较彻底, 坏处是有时候要准备个数较多, 不过一般都尽量控制住数目,
只要可能, 我还是用这种 ---- 增加数目有点麻烦, 似乎可以抑制一些增加 api 数目的冲动 :)
On Sep 16, 10:14 pm, lijie <cpun...@gmail.com> wrote:
> OO的接口耦合可以看成是一组函数签名+一个类型的耦合;GP比较宽泛,不太好说,从concept来说应该是基于函数名称和函数签名的耦合。委托、自由函数去 掉了名称和类型的耦合,只剩下函数签名,这是个进步。以前用python时,有一段时间热衷于使用类似委托的耦合方式,后来用C++也自己做了委托类,但后来还 是回到接口了。为什么选择接口?接口可以看作是几个委托的组合,很多时候我们都需要同时绑定一组委托,所以使用接口也是很自然的事,委托这种C++编译器不提供 的东西,自己写着玩玩可以,要让项目团队接受就有难度。
> 纯GP或纯OO应用我感觉比较少了,只是偏向哪边的问题。我偏向OO是因为它的二进制接口,在项目组里要维护一套基础库,什么时候发现BUG都有可能要升级,如 果每次升级都让使用者重新编译部署,这个工作量很大的(虽然目前是以静态库提供,还是免不了要重新编译,但至少留有余地)。OO就比较简单些,如果是提供动态库 ,只需要分发二进制库就可以了,接口不变就不会影响使用者。GP也有应用,但主要是一些工具类,只在实现代码里使用的这种,通常不会让它出现在函数参数或是其它 一些影响二进制复用的地方。
>
> 下面有讨论到胶合层,GP和OO都离不了这一层,前面说哪个stack类没有实现IStack的问题,在OO里的解决办法就是外包一个类作接口耦合,GP的办法 不好说,我感觉你这里提到的都是委托的功能了,并不是GP提供的吧,或许委托也可以看成一种"泛"型?到底把GP用在哪些能影响架构的地方?我也想看看例子。
OO的接口耦合可以看成是一组函数签名+一个类型的耦合;GP比较宽泛,不太好说,从concept来说应该是基于函数名称和函数签名的耦合。委托、自由函数去掉了名称和类型的耦合,只剩下函数签名,这是个进步。以前用python时,有一段时间热衷于使用类似委托的耦合方式,后来用C++也自己做了委托类,但后来还是回到接口了。为什么选择接口?接口可以看作是几个委托的组合,很多时候我们都需要同时绑定一组委托,所以使用接口也是很自然的事,委托这种C++编译器不提供的东西,自己写着玩玩可以,要让项目团队接受就有难度。
我"编"了一个简单的案例,来源于我们的一些项目。故事很简单,一个帐务系统。搞过帐务系统或者学过财务的都应该知道,帐务系统里最核心的是科目。在中国,科目是分级的,(外国人好像没有法定的分级体系),一般有4 、5级,多的有7、8 级,甚至10多级。不管怎么分,科目的结构是树。
在科目上有一个操作称为"汇总",就是把子科目的金额累加起来,作为本科目的金额。这实际上是对指定科目的所有下级科目的遍历汇总。这是一个非常简单,但却非常重要的帐务操作。
我尝试着用两种设计实现这种操作。一种以GP为核心,另一种以OOP 为核心。从中,可以比较出两者的差异。
先看GP:
首先我定义了一个类,Account,代表科目:
class Account
{
public:
typedef vector<Account> child_vect;
pubilc:
child_vect children(); //子级科目
int account_type(); //本科目类型
float ammount(); //本科目的凭证金额累计,非最明细科目返回0
...
}
为了简便,我没有考虑科目的贷方金额、借方金额接待方向等。如需考虑,也很容易扩展出去。然后,我利用一个简单的函数模板执行上述汇总:
template<typename T, typename Pred, typename A>
float collect(T& item, Pred filter, A ammount) {
if(filter(item)==false)
return 0;
float result(0);
T::child_vect& children=item.children();
if(children.size==0) //最明细科目,没有子科目
return ammount(item.ammount);
T::child_vect::iterator ib(children.begin()), ie(children.end());
for(; ib!=ie; ++ib)
{
result+=collect(*ib, filter, ammount);
}
}
为了简单起见,我直接做了泛化版的算法,不绕弯子了。参数filter用来过滤某些不用参与运算的科目,参数 ammount是一个"可调用物",用来适配item上的接口,一会儿会看到它的作用。
另外,还需要做一个辅助的functor:
template<typename T>
struct all_true
{
bool operator(T& item) {
return true;
}
};
使用的时候非常简单:
Account& acc=AccountCenter.getAccount("...");
float result=collect(acc, all_true<Account>(),
mem_fun_ref(&Account::ammount));
相应的OOP版一般会做成这样(很多实际项目就是这么做的):
class Account
{
public:
child_vect children(); //子级科目
int account_type(); //本科目类型
float ammount() { //此成员直接执行子级科目的汇总任务
float result(0);
if(children.size==0) //最明细科目,没有子科目
return m_ammount; //或从其他途径获得,如数据库访问
T::child_vect& children=item.children();
T::child_vect::iterator ib(children.begin()), ie(children.end());
for(; ib!=ie; ++ib)
{
result+=ib->ammount();
}
}
}
使用起来更简单:
Account& acc=AccountCenter.getAccount("...");
float result=acc.ammount();
代码逻辑没有什么差别,OOP代码更少,使用上OOP 更简单。到目前为止,的确如此。但接下来的情况就不同了。
假设现实世界的古怪客户,使我们面临一个挑战:他们的业务模型中,有部分科目不参与汇总计算,是一群特殊的科目。(这种科目我还真见过)。
于是,代码需要修改。但GP和OOP 的改法完全不同。
OOP必须修改Account类,在 ammount()成员中添加一个判别:
if(g_SpecialAccounts.IsSpecial(account_type()) //假设SpecialAccounts是个
// Singleton,负责管理特殊科目
return 0;
请注意,从这里开始,Account和SpecialAccounts 已经绑在了一起。
那么GP呢?很简单,不用改任何类代码:
float result= collect(acc,
mem_fun_ref(&SpecialAccounts::IsSpecial),
mem_fun_ref(&Account::ammount));
主体代码没有任何变化,只是使用时做些变化而已。如果有了lambda,就更简便了。
如果这还算不上很大的差别的话,那么下一个挑战则足以把它们拉开距离:我们如果注意的话,MIS系统中有很多地方同科目有着相同的逻辑结构。比如,销售部门的分销组织机构,一个企业的部门组织机构。在这些结构上,通常也会发生汇总操作,比如某个省的分销商业绩汇总,或者某个部门的电费汇总。
于是,充满优化意识的程序员,会想到复用在帐务系统上已有的成果。假设我们定义了部门类:
class Department
{
public:
typedef vector<Account> child_vect;
public:
child_vect Children();
int dept_type();
float elec_fee();
float water_fee();
...
};
由于collect<>是泛化的,独立的,只需在使用时稍加变化即可:
float result=collect(dept1, all_true<Department>(),
mem_fun_ref(&Department::elec_fee));
就这么简单。如果有哪一类鸟部门不算电费的,只需把filter的实参改掉即可。如果要统计的不是电费,是水费,只需把最后一个实参换掉即可。其他的业务逻辑,只要是棵树,需要汇总数据的,都可以简单地换掉几个参数解决问题。
实际上,这里把算法也拆开成部件,需要时进行不同的组装。这才真正做到"All for one, one for all"。这些思想来源于Stepanov在stl上的杰出贡献。
相反,OOP方案,由于elec_fee() 直接返回汇总的电费,所有的汇总实现代码都在类体中间藏着,两个类要么各自写独立的代码,任劳任怨地Do Repeat Yourself(呵呵,也是DRY:) )。结果就是循环敲到手指头痛。
要么把算法分离出来,做成独立的类,通过统一的接口调用:
class Account
{
public:
Account(ICollect& collect): m_collect(collect){...}
float ammount() {
return m_collect.cacul(*this);
}
...
private:
ICollect m_collect;
};
class Deptmant
{
public:
Account(ICollect& collect):m_collect(collect){...}
float elec_fee() {
return m_collect.cacul(*this);
}
private:
ICollect& m_collect;
};
//计算接口和实现基类
struct ICollect
{
virtual float cacul(???)=0;
};
class CollectAccount : ICollect
{
public:
virtual float cacul(???) {...};
};
class CollectDeptmant : ICollect
{
public:
virtual float cacul(???) {...};
};
破绽,破绽!???该填什么?只能填一个类型。所以必须使Account 和Deptmant拥有相同的数据交换接口。好吧,再做一个接口:
struct IData
{
virtual float value()=0;
};
Account和Deptmant都实现这个接口。用 value返回需要的数值。但是如何同时又能计算电费,又能计算水费呢?给这些成员编号,用代号调用:value(1)?
最后一招:reflect。没错,reflect 可以解决这个问题。不同的计算枚举出不同的成员函数。不过撇开性能不谈,光折腾那些meta就够让人倒胃口了。
现在,我们便可以真切地看到"耦合是怎样炼成的"。八杆子打不到的两个类Account和 Department,为了共享一个算法,被绑在了ICollect(以及IData)上。如果还有其他类型的算法呢?继续加接口,还是做一个完全通用的接口(暂且不管他是否做得出来)?或者在 Account和Department下面再加一个中间层,处理抽象算法问题?(肥厚的中间层?)
方案一个比一个复杂。很多程序员(也包括我)在这种情况下,宁愿手指头痛,也不愿头痛。这也就是为什么热衷于OO 的程序员经常在Do Repeat Yourself。方案一个比一个复杂。很多程序员(也包括我)在这种情况下,宁愿手指头痛,也不愿头痛。这也就是为什么热衷于OO 的程序员经常在 Do Repeat Yourself。
float result=collect(dept1, all_true<Department>(), mem_fun_ref(&Department::elec_fee));
Department可没有实现过ammount函数,这里使用了elec_fee()成员。同样可用。这个例子并不是为GP设计的,这个例子是为业务设计的,是我针对一个帐务系统开发中发现的问题的改进方案。只是为了做案例,简化了许多。
这和接口的相比的优势,看我上一个回复。
GP的优势在于,无需为了适应一个算法而让数据对象增加ITree之类的接口。不但可以少打很多字,而且可以少很多"香炉和鬼"。:)
算法永远是少数,而数据类型则会不断的变化。OOP要求将数据类型归一化为一种类型,其工作量是O(n)的,而GP则是通过算法的泛化适应不同的类型,其工作量是分摊O(1)的。:)
其实从面向对象的本质来讲,放在collect函数中实现统计功能也是合理的。面向对象的最初目的是通过将对象的行为和对象的数据进行封装,从而分解(通过将整个系统划分成一个个粒度很小的对象)和降低整个系统的设计、理解、实现(重用)和维护的难度。
但是OOP使用20多年来的经验表明,它只是人们过于完美的一个梦想而已,这其中最重要的一点就是由于强类型之间的耦合问题大大降低了它的分解带来的好处。加上类的职责的划分非常微妙,导致了很多智力不堪重负。二十多年来的实践经验表明,自由函数(带GP)往往更能够复用,在很多方面和以依赖于对象闻名的OO有很强的互补性(其实java和C#这样的标榜纯OO的语言中,也还是有utility方法的,现在更是加上了GP)。
在 07-9-17,Huafeng Mo<longsh...@gmail.com> 写道:
我"编"了一个简单的案例,来源于我们的一些项目。故事很简单,一个帐务系统。搞过帐务系统或者学 过财务的都应该知道,帐务系统里最核心的是科目。在中国,科目是分级的,(外国人好像没有法定的分级体系),一般有4 、5级,多的有7、8 级,甚至10多 级。不管怎么分,科目的结构是树。
但是我自己觉得, 为了以后未必用得上的灵活性提早准备, 除非准备工作确实很
少, 也没有多出抽象层次, 还有点意义, 否则多半是得不偿失.
Atry 写道:
//Account类和Department类
template<typename Alg=Biz_Alg<Account> >
class Account
{
public:
float ammount() {
Alg::collect(*this, &Account::ammount);
}
};
template<typename Alg= Biz_Alg<Department> >
class Department
{
public:
float elec_fee() {
return Alg::collect(*this, &Department::elec_fee);
}
int employee_num() {
return Alg::collect(*this, &Department::emoloyee_num);
}
…
};
对比原先的GP方案,这种方案则是以OOP 为出发点,利用GP技术使其解耦,并组件化。而原先的方案则是以SP为出发点,利用GP 使其泛化和组件化。也就是说,GP被分成了两种风格:OO风格的GP 和SP风格的GP。从本质上来说,两者是等价的,都是将算法和数据对象分离。只不过前者是以类为主导,而后者则是以自由函数为主导。两者各有优势:前者集成度高,使用起来简便、清晰,更符合业务逻辑;后者更灵活,耦合度更小,扩展性更强。
根本上而言,OO风格的GP 是以业务逻辑为核心,用类封装具体实现,程序员的注意力焦点都在业务逻辑上。比较适合高层的业务级别开发。而SP风格的GP,以数据操作为核心,由自由函数将算法施加在数据实体上,程序员的注意力都集中在算法和数据本身上。这种风格比较适合底层的系统级开发。
从原先纯OO的方案来看,由于受制于强类型系统的约束,无法很优雅地将算法与数据分离。由此造成越来越严重的耦合。而 GP的引入,则利用其泛型特性,消除了强类型的负面作用,实现了解耦。
同样,在纯SP方案中,算法与数据分离,但却没有完全独立于数据。一个自由函数要么只能针对一种类型(除非放弃类型安全的保证,和代码复用),要么促使数据对象的类型归一化。后者将依旧会引发数据对象间的耦合。但 GP引入后,算法不再针对单一的类型,而是可以面向一族类型。在这种情况下,类型安全得到保证,代码复用得到实现,也无需强制地将不相关的类型归一化。
从这两种实现可以看出,无论是OO还是SP ,都可以通过GP来加以扩展,消除各自的缺陷。也就是"用OO或SP 设计,用GP来优化(代码)"。
最后,我这里还有一个方案:
template<typename C, typename R, typename Pred,R (C::*mem)() >
class collect
{
public:
collect(C const& item): m_Item(item), m_Mem(mem){}
R operator() {
//执行遍历汇总
}
private:
C& m_Item;
Pred m_Pred;
R (C::*m_Mem);
}
//用起来像这样
typedef collect<Account, float, AllTrue<Account>,
&Account::ammount() > AccountCollect;
typedef collect<Account, int, AllTrue<Department>,
&Department::employee_num > DeptElecFeeCollet;
//使用SP风格的GP方案中的Account和Department的对象,即简单类
float res=AccountCollect(acc1)();
float res=AccountCollect(dept1)();
这种方式看上去有些古怪。实际上使用一个函数对象封装了算法,构成了一个"算法适配器"。collect 是个算法框架,通过typedef将相应的判别式、成员函数,同框架一起组装起来,构成一个类型。之后,直接使用这个类型,适配一个对象,以执行相应的计算。这可以看作是介于 OOP和 SP风格之间的一种GP方案。它比 OOP风格方案稍微灵活一点点,比 SP风格方案使用上稍微简便一点点。但是,在这个方案能为我们带来多少好处,我不敢说。放在这里,只是为了展示GP 的几种可能的设计方案而已。On 9/17/07, lijie <cpu...@gmail.com > wrote:看得累,不过总算是看完了。
我怎么感觉是OO和FP的区别而不是GP?...
===================================
啊,是的,是的。lijie说的没错,这个案例的确混杂着OO和SP的比较。我刚想明白,惭愧惭愧。因为我想到了另一种GP的方案:
//Biz_Alg类包含了若干常用的算法。做成 traits形式,便于未来针对特别情况特化。template<typename T>
struct Biz_Alg
{
template<typename R, typename C, typename M>
R collect(C& item, M mem) {
//执行遍历汇总,调用item子节点的mem成员
}
};//Account类和Department类
template<typename Alg=Biz_Alg<Account> >
class Account
{
public:
float ammount() {
Alg::collect(*this, &Account::ammount);
}
};
template<typename Alg= Biz_Alg<Department> >
class Department
{
public:
float elec_fee() {
return Alg::collect(*this, &Department::elec_fee);
}
int employee_num() {
return Alg::collect(*this, &Department::emoloyee_num);
}
...
};
对比原先的GP方案,这种方案则是以OOP 为出发点,利用GP技术使其解耦,并组件化。而原先的方案则是以SP为出发点,利用GP 使其泛化和组件化。也就是说,GP被分成了两种风格:OO风格的GP 和SP风格的GP。从本质上来说,两者是等价的,都是将算法和数据对象分离。只不过前者是以类为主导,而后者则是以自由函数为主导。两者各有优势:前者集成度高,使用起来简便、清晰,更符合业务逻辑;后者更灵活,耦合度更小,扩展性更强。
根本上而言,OO风格的GP 是以业务逻辑为核心,用类封装具体实现,程序员的注意力焦点都在业务逻辑上。比较适合高层的业务级别开发。而SP风格的GP,以数据操作为核心,由自由函数将算法施加在数据实体上,程序员的注意力都集中在算法和数据本身上。这种风格比较适合底层的系统级开发。
从原先纯OO的方案来看,由于受制于强类型系统的约束,无法很优雅地将算法与数据分离。由此造成越来越严重的耦合。而 GP的引入,则利用其泛型特性,消除了强类型的负面作用,实现了解耦。
同样,在纯SP方案中,算法与数据分离,但却没有完全独立于数据。一个自由函数要么只能针对一种类型(除非放弃类型安全的保证,和代码复用),要么促使数据对象的类型归一化。后者将依旧会引发数据对象间的耦合。但 GP引入后,算法不再针对单一的类型,而是可以面向一族类型。在这种情况下,类型安全得到保证,代码复用得到实现,也无需强制地将不相关的类型归一化。
从这两种实现可以看出,无论是OO还是SP ,都可以通过GP来加以扩展,消除各自的缺陷。也就是"用OO或SP 设计,用GP来优化(代码)"。
昨天又把pongba的concept介绍看了下,它有很多类型、名称的约定,实现类有一个确保兼容的功能(#8(火星人...类型系统问题)这部分)。在concept之前,你这里的GP方案假定的东西太多,虽然使用了一些手段把名称提取出来以减少名称上的耦合(上面的return Alg::collect(*this, &Department::elec_fee);),但算法依旧有明显的假设,比如假定实现类是Tree型并且具有某某成员,或许最终为了通用而实现出迭代器,这个又回到接口的老路上了----为了假设的需求写出不一定有用的接口,好的地方就是没有引入新类型。
接口的问题有几个,一是泛型的问题,二是继承问题,三是引入新类型,的确是不少。我想有没有可能使用concept的方式来实现,它和concept的差别就是一个是静态的,另一个是动态的。比较一下看看:
以下纯属虚构。。。
concept Drawable<typename T>
{
void T::draw() const;
}
class MyClass
{
void draw() const;
};
concept_map Drawable<MyClass> { } // 特化
interface IDrawable<typename T>
{
void draw() const;
}
interface_impl IDrawable<MyClass> {} // 实现接口
IDrawable*指针可以直接调用draw,因为draw不依赖模板类型,如果是调用其它一些依赖类型的操作就需要特殊处理了,但至少别因为一个模板参数不同就互不相认。最后把它统一到一种语法上面去,同时有动态和静态支持,作为concept使用它仅是个静态的检查,把它转换成IDrawable接口时自动加一层包装类就成了。想办法消除这种OO的动态和GP的静态之间的鸿沟,就是因为必须要取舍才有了这些争论,实际上哪一种都不完美。
一不小心这个讨论已经相当深入了啊,呵呵。
我个人的看法是假如我们的系统分上中下三层的话,GP适用于上层和下层,而 OO 适用于中层。
举个例子,考虑一个用 C++ 实现的 3D 游戏引擎,底层的算法和数据结构用 GP 实现,通用而高效;中层主要使用 OO 封装,容易设计和理解;高层用 GP
实现各种 design patterns,将整个系统组织起来,起到了粘合剂的作用。这样一个系统,底层和中层的代码极具复用性,且也是代码最多的,高层处理最为
困难的设计问题,代码最少,就算是有设计错误,修改甚至重写的代价也不大,类似 C + 脚本的实现方式,可以自底向上的开发。
delphi 用于界面编程还是很方便的, 这个 PME 的机制带来的好处非常大, 这个比起 Windows message 机制易用, 类型
安全, 没有 interface 那种高耦合和笨重.
event, singal/slot 机制在适用的场合使用起来还是很方便的.
相比之下, OO 的 interface 方法能够适应的范围更广, 但是在其他方法能够适用的场合, 这个方式就显得笨重和高耦合了. 这是
不是和 C++ 语言的处境也很象? 很多地方都能够使用, 但是, 在有合适替代品的时候, 这就很有可能就不是最佳选择.
oldrev <oldrev(at)gmail(dot)com>
Regards
>>> 引用的文字:
On Tuesday 18 September 2007 23:18:32 red...@gmail.com wrote:
> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
> <html>
> <head>
> <meta content="text/html;charset=GB2312" http-equiv="Content-Type">
> </head>
> <body bgcolor="#ffffff" text="#000000">
> 动多态的时候, 虚函数避免不了.<br>
> <br>
> 此外的多数情形, 是 GP 或者这种方法(delegate) 好过用虚函数.<br>
> <br>
> Atry 写道:
> <blockquote
> cite="mid:fd8b80680709180812n163...@mail.gmail.com"
> type="cite">要处理事件就用 Handler 或者代理回调函数这样的东西,而不允许从功能类派生出来重写虚函数。那太野蛮。<br>
> <br>
> <div><span class="gmail_quote">在07-9-18,<b
> class="gmail_sendername">Atry</b> <<a moz-do-not-send="true"
> href="mailto:pop....@gmail.com">pop....@gmail.com</a>> 写道:</span>
> <blockquote class="gmail_quote"
> style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt
> 0.8ex; padding-left: 1ex;">我 也用多态啊,不过只会从一个类实现接口,不用让一个公开的类派生于另一个类。
> <div><span class="e" id="q_1151929e1168a0dd_1"><br>
> </span></div>
> </blockquote>
> </div>
> </blockquote>
> <br>
> <br>
> > </html>
> <br>
在 07-9-19,Atry<pop....@gmail.com> 写道:
On Sep 19, 10:35 pm, "Jian Wang" <oxygen.jian.w...@gmail.com> wrote:
> OO系统里真的可以避免向下转型吗?只是蠢人才这样做吗?
> JDK里有不少地方是传Object的。类似于 用户类-〉框架-〉用户类
> 的地方很多都是通过Object来传递用户数据,根本无法避免向下转型。难道设计JDK的人都是蠢人吗?
>
> 在 07-9-19,Atry<pop.a...@gmail.com> 写道:
>
> > 对有毅力的蠢人来说,不管是任何一种语言,想要搞得很复杂都是做得到的。
>
> > 在07-9-18,oldrev <old...@gmail.com> 写道:
> > > 现在很多开源 C 库都流行像 COM 一样在 struct 里放一堆函数指针,写的人痛苦用的人也痛苦啊,呵呵。
>
> > > oldrev <oldrev(at)gmail(dot)com>
> > > Regards
>
> > > >>> 引用的文字:
> > > On Tuesday 18 September 2007 23:18:32 red...@gmail.com wrote:
> > > > <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
> > > > <html>
> > > > <head>
> > > > <meta content="text/html;charset=GB2312"
> > http-equiv="Content-Type">
> > > > </head>
> > > > <body bgcolor="#ffffff" text="#000000">
> > > > 动多态的时候, 虚函数避免不了.<br>
> > > > <br>
> > > > 此外的多数情形, 是 GP 或者这种方法(delegate) 好过用虚函数.<br>
> > > > <br>
> > > > Atry 写道:
> > > > <blockquote
>
> > cite="mid:fd8b80680709180812n16310b43u1d7ed241a9055...@mail.gmail.com"
> > > > type="cite">要处理事件就用 Handler
> > 或者代理回调函数这样的东西,而不允许从功能类派生出来重写虚函数。那太野蛮。<br>
> > > > <br>
> > > > <div><span class="gmail_quote">在07-9-18,<b
> > > > class="gmail_sendername">Atry</b> <<a
> > moz-do-not-send="true"
> > > > href="mailto: pop.a...@gmail.com">pop.a...@gmail.com</a>> 写道:</span>