目录 背景 引用Martin Fowler的观点 我的新视角 对象使用的视角 分离对象的创建 实际编程中的分离观点 总结
背景 这篇文章是我在Net Objectives工作时写的我在那里的工作是指导人们编写有效的面向对象程序本文将介绍一些实用但不同以往的观点用来解决每天出现的设计问题本文不关注对象做什么而是对象的使用和对象的实例化基于这些观点可以大规模地简化和改进代码以满足将来维护的需要
引用Martin Fowler的观点 在《UML精粹》第三版中Martin Fowler提出了对象设计的三个层面概念层规约层(Specification)实现层
概念层概念层中的对象是承担一定职责的实体通常用抽象类和接口(Interface)来描述这些实体之间以各种方式相互联系来实现应用系统的目标
如果我是一个对象在概念层我关心的是我的职责是什么
规约层规约层中的对象通过它的公共方法来实现它的职责每个公共的方法都是对象按照指定的方式提供的服务
如果我是一个对象在规约层我关心是别人如何使用我
实现层实现层是对象的代码对象通过代码来实现它的职责
如果我是一个对象在实现层我关心的是我如何完成我的职责
对于系统中一个实体只立足于一个概念层次来工作有很有益处的同样的把自己的思维过程划分成三部分也是有利的 首先这样有助于减弱藕合如果对象之间的关系保持在抽象层面上后期实现的子类之间的藕合会更弱这些意见是设计模式的作者Gang of Four他们提出的他们认为设计者应该面向接口作设计 其次能使系统内聚性更好结构更清楚因为我们能围绕对象的职责来编写实现细节而不是其它的方式这样一个对象被明确的定义职责范围而并不是包含一些无关的方法和状态(这对眼前的问题豪无帮助) 最后其它开发者能因此有一个清楚的认识过程因为如果对一个问题让一个人同时从多个层面去理解它那么他将很容易洩气
我的新观点 下面才是正题我将推荐一些观点这是些类似的特性它能帮助我们实现灵活性和健壮性而这正是我们一直在寻找的面向对象方法我把我的这些观点概括为对象创建VS对象使用 我们看看下面这段代码
这是一个SignalProcessor的例子它使用ByteFilter的一个实现(HiPassFilter)来完成自己的一部分工作基本上这是不错的作法内聚性是比较好的每个类是一个整体通过和其它类协作来完成自己的任务并且对于ByteFiler可以提供多种实现而不需要改变SignalProcessor的设计这是一个可拔插的设计并且很容易扩展 概念上SignalProcess是负责处理字节数组的信号规约方面SignalProcessor表现为一个返回字节数组的process()方法 而SignalProcessor的实现的方面我们看到SignalProcessor调用ByteFilter的实例在我们经过这里的时候我们只需考虑它的规约(filter()方法)而不需考虑它的实现这样很好干净清楚 但是问题在于SignalProcessor和ByteFilter之间的调用混合了两种不同的概念(创建的概念和使用的概念)SignalProcessor掌控了HiPassFilter这个类的创建同时它也使用这个实例来工作 这看起来好象微不足道并且实际中也会经常出现但是让我思考一下这两个职责使用对象和制造对象把它们看做相对独立的事情并审视它们之间建立的藕合
关于对象使用的观点 一个客户对象要使用服务对象要通过服务对象提供的公共方法如果服务对象被引用为Object这种类型那么就只有一些通用方法可以调用所以如果客户对象要使用服务对象就要满足以下三个条件之一 l 知道这个服务对象的实际类型 l 知道这个服务对象实现的某一个接口 l 知道这个服务对象继承树上端的一个基类 为了尽可能的降低藕合我们倾向于满足后两个条件之一这样将来根据需要可以改变实际被使用的客户对象而不需要改变客户对象的代码 换句话说理想情况下客户对象应该依赖一个抽象而不是具体现存的一个类这样将来就可以自由的添加不同的服务类而不需要维护客户类的代码特别在服务类被大范围使用的情况下这种设计原则显示犹为重要
对象创建的观点 显而易见地如果我们要避免客户对象知道Bytefilters的具体的实现和这个对象如何创建这就是意味着就要有另一个东西在另一个地方知道这些信息 因此我提出一种独特的概念使用和创建同样的客户对象不涉及对象的创建对象的创建者也不会涉及对象的使用我们通常称这种创建者为工厂类 这种设计就意味着下面这种设计模型
需要认真考虑的是创建和使用之间最自然的藕合做到这一点任何东西发生变化时都不会提心吊胆地去维护 如果把ByteFilter的抽象和两个具体实现看作多态性服务(ByteFilter是一个服务类但它有两个版本而通过多态性这种机制来实现不同版之间的变化)而SignalProcessor这个类只以使用的视角关注这个服务ByteFilterFactory则是负责不同服务类的创建 而ByteFilter这个抽象类型(本质上也只是一种抽象)有一些做为接口的公共方法这个抽象类型是SignalProcessor类和ByteFilter这个多态性服务之间的藕合而SignalProcessor和两个具体类之间不存在任何的藕合但前提是我们是一个好的OO程序员不会给接口随意的添加方法 ByteFilterFactory和ByteFilter多态性服务之间的藕合就是另一种情况了这个工厂类和子类建立藕合因为它必须通过new关键字来创建实例因为这个工厂类很自然地和构造器之间存在藕合同时也和ByteFilter存在藕合(在把值返回给SignalProcessor之前需要创建这个类型的引用)但是工厂类却不关心ByteFilter的公共方法因为工厂类的概念应该只是创建工厂类创建对象但绝不调用对象的方法 这一切带来的结果就是当客户对象或是工厂类需要改变时维护工作的痛苦会减少很多 如果具体的子类要改变例如ByteFilter需要添加或移除不同的实现或者某个实现的业务规则发生改变同时只要维护一下ByteFilterFactory的代码而不需要影响SignalProcessor 如果ByteFilter的接口发生改变添加移除或改变公共方法然后就需要修改一下SignalProcessor但不会影响ByteFilterFactory 我们非常感兴趣一个问题客户对象和工厂类之间有一个很薄弱的环节那就是ByteFilter这个抽象本身而不是它的接口要特别强调这样一个事实很多资深的设计员都认为正确的抽象是OO设计的关键问题即使接口有问题也好过错误的抽象概念
实际工作中的观点 这是否就意味着你的设计中的每一个类都应该有一个工厂类而其它类都必须实例化这个工厂类?当然如果问题很简单没什么变数例如一个普通排序类那就是杀鸡用牛刀了 但这就经常会有问题我们永远不知道将来会发生什么样的变化很不幸我们预知变化的能力历来都不高有一个折衷的办法是把构造器封装在自己的类里面 只要简单地把构造器声明为private或是protected然后添加一个静态方法这静态方法返回一个实例代码片段如下
这里关键的不同在于封装了构造器通常客户对象的代码是这样写的mySender = SendergetInstance();而不是mySender = new Sender();通过把构造器声明为private可以屏蔽后一种做法 初一看这好象没有什么意义和普通做的写法没有什么不同但是这样我们封装了new这个操作符在C#或是JAVA这样语言中new是不能被重载的且我们不能控制它的返回类型你通过new后面跟一个类名来指定一个类型而它总是返回这个类型而getInstance方法就不同了它能返回其它类型作为返回值当Sender需要做一点改变时这种价值就很明显了我们可以把Sender变成一种多态性服务
这里主要的好处就是当变化来临的时候客户对象不需要改变当有很多客户对象使用这个服务时这就更有价值了因此出于将来的维护的考虑这么作是非常有必要的 但是这好象违反了概念划分的原则吧?毕竟这里Sender即是概念抽象(抽象类)同时又是具体实现(实现了工厂类)是的在眼前看来我们有时要更实用主义一些象这样允许一些不可预料的变化而不是为了所有可能的变化放置一些不必要的代码在里面 在这个例子中Sender的方法getInstance()作了一个简单的决策(决定那个子类被创建)并且可能一直都是这么简单如果问题变复杂我们将会分离出工厂类来负责创建Sender的子类这是否意味着需要修改客户对象呢把调用静态方法改为调整工厂类?完全不用我们可以做一个这样的委托
当然这是个不错的选择如果客户对象很少并且有些时间来重构我们也可以把客户对象改为直接调用工厂类但这并不是必要的而且有点钻牛角尖了你有没注意到SenderFactory的构造器也被封装了Sender调用静态方法getInstance来创建SenderFacroty实例也许有一天工厂类也会变成多态性服务所以把它也封装起来会更好吧! 另外把工厂类做成Singleton(一种设计模式它能保证一个类只有一个实例并通过一个全局的访问点访问这个实例)是很常见的在重构代码时只需举手之劳就能把构造的过程封装成Singleton 结论 在设计中实体这个概念有不同的视角每个视角表现为某一类操作的内聚而内聚为认是设计的传统美德因为强内聚的实体容易被理解更少的藕合更好的粒度更容易测试 如果我们努力使实体的操作呈现出更清晰的视角我们就改进了内聚性包括状态的内聚功能的内聚职责的内聚这将给我们带来很多的好处 划分使用视角和创建视角可以有效的提升内聚性这么作也意味有一个内聚的实体负责创建工作一般是工厂类同样的我们也不需要关心它如果和使用相结合最后实现整体的功能 实际上在我们设计中掌握这些原则意味着用各种使用关系来确定各种工厂类工厂类则专注于实例的创建(可以参考一些知名的创建模式) 在《设计模式精解》一书中Alan Shalloway and James Trott描述了这样一种观点根据上下文设计其中他们说一个设计的某些方面形成了一段上下文通过这样的上下文可以推导出设计的其它部分这是一个比较宏观的概念这个概念意味着模式在设计分析和实现中所扮演的角色不过论题的关键是这样的
随后他们总结一个更有力的设计原则
划分创建和使用就是有效的支持了这一原则因此也带来了很多优势系统的内聚性会更强可扩展性和灵活性会更好维护将会变的相当的简单 经常会发现一项好的技术会改进另一项好的技术形成强强联手 例如测试驱动开发期望编写的类有更好的可测试性(通过前期关注测试)这也意味更强的内聚性更好的粒度等等分离使用和创建的原则可以带来更优的可测试性你可以分别测试它们你可以放入一个假的对象来消除依赖性使测试工作更容易 设计模式也是这种观点的支持者其中最值得称道的是通过抽象隐藏变化和开放封闭原则这些都给我们带来了很多改善 然而使用对象不需要知道对象的类型(知道的仅仅是抽象)然后另外再关注创建的问题这就产生了工厂类这就很自然把客户对象从复杂问题中分离出来 我们正在建立自己的专业特点我们搜寻各种有价值的特性原则实践方法和模式以些形成我们智慧的基础 寻找这些思想和我们现有的思想集成我们将更成功给我们的客户公司文化和经济方面都能增添价值这也能帮助我们更好的沟通和协作建立一个鼓励创新的团队气氛 最后我们所作的工作会更有趣我相信这是一条正确的道路 |