浅谈静态OO设计的陷阱
静态OO设计 是指 面向使用C++/C#/Java这些静态类型OO语言 的项目 进行的设计.这种设计很容易陷进一些陷阱.
这些陷阱是:
1.is-a 的类型系统和 like-a 的真实世界不匹配的陷阱
静态类型系统推崇的编译时类型检查机制,实际上是强调了消息的接受者必须是某一特定类型.
举个例子,比如某人需要购买一部手机.在真实世界里,去商场购买和从淘宝购买,都是实现需求的合理手段.
如果,用静态OO语言来描述,就是:
商店->买(手机型号,现金对象&,);
淘宝网->买(手机型号,信用卡对象&);
对于"商店"和"淘宝网"这样两个对象,很难抽象出一个统一基类.
对于"买/2"这个方法,也很难抽象出一个接口.(void*不算哈).
但是这是一个真实的世界的实际存在的场景.
那么,"人" 这个类的购买手机方法怎么定义呢?
对于静态类型系统确实是个难题.
如果,把"买"看成是对商店和淘宝网的一个消息,静态类型系统要求的就是"淘宝网"和"商店"必须继承/实现同一个接口,如:
class 商店 : 可以接受"买"消息的接口 IBuy
{};
class 淘宝网 : 可以接受"买"消息的接口 IBuy
{};
class 人
{
bool 购买手机(购买方式* pIBuy);
};
这样的方法在消息的数量少的时候,是可行的.但是,真实世界里,对象的功能无法穷尽,消息的种类也是无穷无尽.
最后,在静态类型OO设计里,不得不为了每一个消息定义一个接口.也就是常见的,一个命令字一个类.这在一些过度设计的服务器程序的消息分派模块中很常见.
通常认为,一个消息一个类,实现了单一职责原则,殊不知,真正,彻底的方式就是不用接口.
为什么说,可以不用接口呢?
因为,所有的函数的作用都是通过由参数,返回值和副作用三者的组合来实现的.接口实际只是一个调用约定.
如果,我们能够保证,对不符合 调用约定的对象 发送 其不能识别的消息 的结果 是可控的话,这种事先约定就变成了一个负担.
改成函数指针的形式就是:
class 人
{
bool 购买手机(FUNC_POINTER* pBuy)// FUNC_POINTER 是预定义的一个函数指针类型
{
return pBuy(...);// 但是这么做隐含着一个风险,就是穿错了参数,会导致程序崩溃,要解决这个问题只能通过引入动态类型系统,来保证消息传递的安全性.
}
};
例如:有的单根静态类型语言在事件机制中,这样定义 事件参数
class 事件参数类
{
对象指针 sender;
对象指针 arg_obj;
};
这里其实是为了规避类型系统的限制.如果用C语言描述就是这样:
struct EventArg
{
void* pSender;
void* pArg;
}
这里静态系统的困难时原发性的. "void*"或者"object引用"的使用,就是静态类型系统对无穷类型的真实世界的投降.
归根到底,是静态类型检查的有效性只在简单的场景有效率.
在复杂系统中,为了满足静态类型系统的血盆大口,只有两个选择,
1. 要不像 Boost 那样用模板让 模板系统去生产代码喂饱静态类型系统;
2. 要不像 C 程序员的风格 和静态类型系统耍无赖,用 void * 瞒天过海.
显然,
第一种方法是用一种更加复杂的复杂性取代原先的复杂性,问题只会越来越复杂,而且在设计和调试效率上都是很低效的.
第二种方法,和不用静态类型系统没什么差别.
说到这里,算是讲清楚了第一个矛盾.动态类型系统好处在这里也就不表了,大家可以参考.
2. 串行的命令式语义(同步函数调用)和无穷并发的真实世界不匹配的陷阱
真实世界的每个对象都是并发的运作着.大部分情况下,对象和对象间没有一种串行的关系.但是,在命令式OOP语言中,函数调用是串行的(阻塞的),状态(内存)是共享的,而不是像每个人的大脑那样是独立的.
这必然要求,在做设计的时候进行一个映射 从 并发真实世界 到 串行命令世界 的映射.
然而,所有的抽象都有损失.
这种并行到串行的映射引入了两个问题:
1. 命令式OOP语言为了实现并发引入了高阶的并发模型,比如线程.在这种模型中,状态在各个执行绪之间共享,好比让一群人公用一个草稿本进行代数演算.问题是显而易见的.为了抢草稿本的使用权,浪费大量时间,如果有人人不守规矩在本子上乱涂乱画,所有的人的工作都进行不下去了.
2. 并发世界的消息大部分是无严格先后顺序的,在状态共享的串行命令式系统中,所有消息必须排队(在一个线程里,你不可能同时执行两个函数),即使不是必须串行的消息.这就使得,当一个命令需要很长时间完成的时候,其他命令不得不等待,这不但低效而且有的时候是不可以接受的.
为了利用多个CPU Core的计算能力,有的设计是通过一个树状锁的数据结构来实现多线程互斥访问临界数据.结果,不但容易遇到死锁问题,而且随着CPU Core数量的增加,锁碰撞几率节节升高,其效率并不理想.
解决这个问题的方法,有两个:
1. 在硬件上通过事务内存来提高效率,但是还是解决不了设计上必须经行并串映射的复杂度.
2. 函数式编程模型,使用像Erlang/OTP 这种FP的平台.

被折叠的 条评论
为什么被折叠?



