本文不是为了论证面向对象方法论那需要深厚的理论知识和丰富的实践经验本人两方面都差得很远 这里只是试图给出一个对面象接口的深入浅出的简单原则 就象数学很难数论很难但是九九表不难各位数字之和被整除推出这个整数能被整除也不难(但是两者都很有用) 其实总感觉oo被多数人都误解了Fp世界的人一说oo必然就拿出oo的类呀继承啊来和fp比较一番多态被他们理解为在继承中的一种定制(也就是override了) 而oo世界中的人呢有的也是抱着类继承不放增量设计是他们的圣经有的则是捧着一本本经典的面向对象着作念经什么design pattern拉refactoring啦OO software construction啦孜孜不倦地一个一个原则一个一个定义一个一个模式地反复辨析所谓读书破万卷下笔如有神啊书上很多微言大义被反复引用到处套用但是有时候却总是看上去不是那么回事同一句话可以被两个人引用却得到不同的结论就象都是读新约的Christian却搞出了天主教新教等等互指为异端的教派到底是书错了?还是读书的人错了? 个人最讨厌故弄玄虚把简单的事情搞复杂这里让我试试能不能简单地解释一下面向接口这个oo原则 任何软件都是由各种不同的模块组成的(没错最小的软件如一个hello world 也是) 从自顶向下的观点看一个大模块由若干个小模块组成一个小模块又由若干个更小的模块组成就象大楼由砖造成砖由分子组成分子由原子组成一样 这些模块之间不可能是互相独立的相互之间肯定要有各种关系 这些关系可以被总结为简单的两种 需求和服务 需求就是我要求别人给我提供什么样功能的服务 服务就是我提供一个什么样功能的服务 所有的关系都是这样两个原子关系的组合 当设计任何一个模块的时候你所看见的就只应该是这个模块对外界的需求和要提供的服务你不应该看见隔着十万八千里的模块乙也不应该看到容器或者配置文件是如何把模块们(包括你现在设计的模块)组装起来的那些都属于另外一个维度另外一个和你不相交的宇宙空间的事 一些c++同志喜欢二分法软件在他们那里变成一个简单的库用户这样的结构在他们看来库可以任意复杂只要给用户提供一个简单的接口就够了 他们没有看到所谓的库用户的划分是相对的而不是绝对的一个模块提供一定的功能那么它相对于使用它的功能的模块就是一个库而这个模块可能还要别人提供一些功能那么象对于提供这些服务的模块它又是用户 两个模块很又可能互相都是用户也都是库(只不过相对于不同的服务层面不同的维度而已) 这样的服务/需求的关系遍布于软件的各个地方 而所谓oo 面向接口就是用来管理这些依赖关系的 就象你整理自己的计算机网络布线或者电视机后面的各种颜色的线一样oo也就是一套行之有效的整理这些关系让它们不要变成一团乱麻的经验之谈 任何一个理论系统要想优美就要遵循下面的准则 完整 自恰 简单 比如几何学用了几条最简单的互相不相关的(所谓正交是也)的公理组建出了一个宏伟的大厦 面向对象的设计原则也应该如此我试着给出下面两个公理让我们看看能不能 .完整地描述面向对象方法 .不自相矛盾 .简单 原则A需求者只要求自己需要的ask no more ask no less! 原则B服务者只提供最小的能够提供足够功能的界面 promise no more promise no less! 从这两个原则我们试着推演一下其它的许多oo的准则来 .Ioc原则或者dip原则所谓具体依赖抽象抽象不依赖具体这是关于需求者的一个设计方法 遇到一个需要的功能这个功能的实现实际上和我自己模块的实现不相关正交所以我定义一个接口从外界注射进来一个实现 那么用原则A是怎么得到这个准则的呢? 首先ask no less 所以如果功能不是和我正交的那么仅仅定义一个接口从外界注射进来对我就不够不符合no less比如我的实现碰巧让我需要一个InputStreamReader而不能是StringReader那么如果仅仅从外界注射进来一个Reader对我的实现来说它达不到我的要求 所以根据no less ioc进来的需要是和当前模块实现正交的 然后no more如果我不用ioc直接自己new一个FileReader如何?本来只需要InputStreamReader 你却要求它的子类型FileReader? 明显违反了no more的要求 再举个例子ioc要求不要new而是从外界注射那么是不是说我们就永远不能new呢?永远都不能Xinstance()呢? 当然不是注意我们的前提是正交是no less 假如我有一个抽象工厂 java代码: interface PersistenceFactory{ } Persistence create(); } 那么当实现这个工厂的jdbc实现的时候很可能是这样 java代码: class JdbcPersistenceFactory{ } Persistence create(){ } Return JdbcPersistenceinstance(); } } 这里你用了一个静态工厂直接依赖于JdbcPersistence实现类了是不是违反了ioc规则呢? 当然不是请注意我们的模块本身就是实现JdbcPersistence的那么从外界再ioc一个PersistenceFactory或者Persistence就不符合正交no less的要求了 而且其实从常识就可以看出来你JdbcPersistenceFactory的任务就是生成一个关于jdbc的PersistenceFactory你如果自己不做再ioc进来这层层推诿真正的工作谁做呢? .Lsp所谓任何地方如果你期待的是一个父类型Base那么把它替换成任何的子类型Derived Derived程序都能正常工作 还是关于需求者的如果你做到了ask no more比如说你只需要Base提供的功能就不要在接口上要求Derived Derived如此我们自然可以任意替换实际的实现 如果你做到了ask no less需要InputStreamReader就直接要求InputStreamReader而不是Reader你就不会需要在代码中做downcast到InputStreamReader的动作也就不会出现把Reader替换成StringReader之后出现的运行时错误 .单一职责原则一个模块只应该做一件事 仍然是需求者的设计方法这里的事的概念应该是一个正交于其它事的功能两个互相紧密耦合的事其实是一件事 根据ask no more如果一个模块做了两件正交的事也就是把两个正交的模块耦合在一起就意味着在我这个滥模块的某个地方有从一个模块到另一个模块的不正当的需求你要求了你不应该要求的 Ocp开闭原则软件模块应该是可以不用改动代码而被扩展的 其实ocp与其说是一个原则不如说是一个理想它并没有指出具体的可操作方法而只是给了一个目标 一些人认为这就意味着类可以继承这个看法太狭隘了扩展一个模块固然可以用继承和override但是用接口组合一样可以做到关键是如果你的模块依赖抽象的接口而不是具体的类那么别人就可以很容易通过接口组合adapter decorator什么的通过给你传递不同的接口实现而达到扩展的目的 这里面仍然是一个简单的ask no more在起作用 总而言之所谓面向接口对需求者来说就是用接口定义好自己需要的功能no more no less而所谓多态就是用来实现接口用的工具而已 完了 以上都是关于需求者的那也是面向接口的主要方面那么如何约束服务者呢? 封装啊封装实际上完全是给服务提供者的工具你可以用它来隐藏自己的实现细节通过最小化对客户的服务承诺来得到最大的设计弹性 你要写一个BankAccount是否要公开所有的内部成员呢?一般可能都不是吧? 对于这些服务的提供者如果公开了数据成员那么对用户的accountbalance = ;这种动作你没有任何弹性只能老老实实地做field update 相比于setBalance()后者可以自由地在内部做trace啦或者把职责转交给内部类啦等等等等灵活得多 那么为什么后者灵活呢?因为用方法封装了field之后我们promise的东西少了我不对客户承诺我肯定修改我的balance成员变量而是简单地说我肯定会修改那个逻辑上的balance你再getBalance()就可以得到这个新的值至于我是不是物理上内部有一个balance变量是不是setBalance()就直接去修改这个变量对不起无可奉告我可能没有也可能有可能今天没有明天有也可能今天有明天一重构就没有了 两者其实都达到了用户的需求但是后者明显没有承诺不必要承诺的实现细节所以根据promise no more的原则封装后比封装前好 下面再唠叨一遍静态工厂 对于类 java代码: class X implements I{ } public X(){…} public static I instance(){return new X();} } 下面两个方法都各自对服务做了什么承诺呢? jav |