一种计算机编程架构,面向对象编程( 二 )


面向对象只是这里面其中一种做法而已 。一个想要把程序编好的人 , 需要注重的是理解问题 , 然后尝试做出几种不同的抽象 , 评估各自优缺点后得到一个当时可行解的能力 。而现有的大环境、教育体系 , 没有那么多真实的、复杂的案例 , 只能用一些简单的sample code来教授 。并且在说明问题本身时 , 简化问题本身 , 而突出代码设计的“模式” 。
这就好像是在用视频教人游泳一样 。学习者自己需要认识到这些培训只是个参考 , 玩真的还是要到项目里去体会 。即便是用面向对象做抽象也会有问题 。很多时候 , 面向对象编程并不是一种好的“抽象” 。如果抽象做得好 , 透过抽象出来的“接口”就可以轻易的使用这个系统 。这时“大量的复杂性”被隐藏到接口后的实现里 。这就像是你看电视从来都不需要拆开壳子看里面液晶屏幕和视频信号的转换 , 只需要知道【电源】、【调台】、【调音量】就能用 。
一个抽象做得好 , 往往要“deep” , 隐藏足够的复杂度 。而面向对象的文化/教育往往会鼓励程序员做很多无意义的 , 无性价比的抽象 。看看有些代码里完全不知所云的adaptor , factory , builder等就是这种做法的产物 。此外 , 在大量使用继承作为设计方法时 , 也没有起到任何实质的隔离作用 。如果你尝试扩展一个继承体系 , 往往需要了解整个继承体系才能写对代码——这时 , 复杂性并没有被隐藏起来 。
你也许只是代码写的少了而已 。对于这种复杂度没有降低 , 编写代码只是写的少 , 但是要看懂还是得结合整个体系才能做到的方式 , 不是抽象 , 是“压缩” 。压缩只能少写代码 , 却会让系统更难以理解了 。也许不太容易理解压缩在这里意思 。比如在一段被压缩的数据中有3个bytes是“A” , “1” ,  “8” 。但是他们的意思可能是A连续出现18次 , 也许是A1连续出现8次 。
至于到底是哪个意思 , 必须从头读所有的数据才能弄明白 。编码也是这个道理 。再说说类型本身 。一些面向对象编码对类型的定义要求的比较严格 。其本质假设是“如果一个Object的类型是XXXX” , 则其行为模式必然是“YYYY” 。但现实当中 , 一个Object的行为模式不光与他的类型有关 , 还与这个Object“如何被使用”有关 。
比方说 , 一个User的Object , 如果是用户自己看自己 , 就可以登陆、登出 , 修改昵称;如果是其他普通用户看 , 就只能看看看昵称和头像;如果是管理员来操作 , 可以reset密码、注销或者踢出登陆 。这时就得界定一个Scope , 来说明现在的User到底是哪个scope的User 。DDD的一些理念就源自于此——找到某个上下文的某个实体概念 , 不能有歧义 。
但是即便不用DDD , 也必须用各种变通的手段 , 把“如何用”的信息与类型信息结合到一起来实现逻辑 。很郁闷的是 , 这个“如何用”完全没有章法 , 可能是“iOS App登陆“ , 也可能是“第一次下单时” , 或者是“系统处于降级状态”时 。你永远也猜不到下一次可能会有个什么条件是要纳入到上下文的 。大家都知道大量用if不好 , 容易让代码变成麻花 , 无法维护 。
但面向对象编程本身没解决这个问题 。很多文章提出面向对象某个模式可以少写if , 让代码容易维护 。但是这其实是建立在那个问题的上下文已经明确的基础之上 。上下文易变的问题没有解决 , 换一个上下文 , 招数便不灵了 , 到时还得处理一坨“模式代码” , 非常恶心 。最后 , 面向对象会倾向于将不同的代码抽象为不同相互作用的Object , 但是有一些现实因素会让这么面向对象得到非常不理想的效果:安全 - 如果你的代码要求非常安全 , 那么所有的Object都要耦合安全控制的代码;要不就是在一层对外的接口之前拦截一道处理安全问题 , 内部Object都无视安全问题 。

推荐阅读