OO设计中对象的创建和使用(一)

来源:互联网 发布:阿里云cname域名解析 编辑:程序博客网 时间:2024/04/28 05:17
背景

  这篇文章是我在Net Objectives工作时写的,我在那里的工作是指导人们编写有效的面向对象程序。本文将介绍一些实用但不同以往的观点,用来解决每天出现的设计问题。本文不关注对象做什么,而是对象的使用和对象的实例化。基于这些观点,可以大规模地简化和改进代码,以满足将来维护的需要。

  引用Martin Fowler的观点

  在《UML精粹》第三版中,Martin Fowler提出了对象设计的三个层面:概念层、规约层(Specification)、实现层

  概念层,概念层中的对象是承担一定职责的实体,通常用抽象类和接口(Interface)来描述,这些实体之间以各种方式相互联系来实现应用系统的目标。 如果我是一个对象,在概念层我关心的是"我的职责是什么"。

  规约层,规约层中的对象通过它的公共方法来实现它的职责,每个公共的方法都是对象按照指定的方式提供的服务。

   如果我是一个对象,在规约层我关心是"别人如何使用我"
 
  实现层,实现层是对象的代码,对象通过代码来实现它的职责。

   如果我是一个对象,在实现层我关心的是"我如何完成我的职责"

  要清楚的认识到,先关注什么特性,什么以后再关注,这是一个很有用的做事方法(所有工作都适用)。要成功认知的实践一项复杂活动,这些特性往往是问题的关键。

  在软件开发的活动中,内聚性就是这种特性。对于系统内的一个实体,内聚性的念是指这个实体内部的紧密程度。当我们谈到"强内聚"这个术语,也就是指某些的打包在一起的东西属于同一个功能(对方法而言)、同一个职责(对类而言)或是同一个领域(对包而言)。相反,如果把很多不相关的东西混在一个包里,我们称这为弱内聚。例如:一个系统只有一个类,这个类负责所有的事,还含有一个上帝方法,能做所有的事

  藕合,是另一个关键特性。这个概念是指系统的实体之间依赖程度。依赖性越强,关系就会越错综复杂,系统也就越难以维护。


  对于系统中一个实体,只立足于一个概念层次来工作,有很有益处的。同样的,把自己的思维过程划分成三部分也是有利的:

  首先,这样有助于减弱藕合。如果对象之间的关系保持在抽象层面上,后期实现的子类之间的藕合会更弱。这些意见是设计模式的作者"Gang of Four"他们提出的。他们认为设计者应该"面向接口作设计"。

  其次,能使系统内聚性更好结构更清楚,因为我们能围绕对象的职责来编写实现细节,而不是其它的方式。这样,一个对象被明确的定义职责范围,而并不是包含一些无关的方法和状态(这对眼前的问题豪无帮助)。

  最后,其它开发者能因此有一个清楚的认识过程,因为如果对一个问题,让一个人同时从多个层面去理解它,那么他将很容易泄气。

  我的新观点

  下面才是正题,我将推荐一些观点,这是些类似的特性,它能帮助我们实现灵活性和健壮性,而这正是我们一直在寻找的面向对象方法。我把我的这些观点概括为:对象创建VS对象使用。

  我们看看下面这段代码:
   public class SignalProcessor
{
 private ByteFilter myFilter;

 public SignalProcessor()
 {
  myFilter = new HiPassFilter();
 }

 public byte[] process(byte[] signal)
 {
  // Do preparatory steps
  myFilter.filter(signal);
  // Do other steps
  return signal;
 }
}


  这是一个SignalProcessor的例子,它使用ByteFilter的一个实现(HiPassFilter)来完成自己的一部分工作。基本上这是不错的作法,内聚性是比较好的,每个类是一个整体,通过和其它类协作来完成自己的任务。并且,对于ByteFiler可以提供多种实现,而不需要改变SignalProcessor的设计。这是一个"可拔插"的设计,并且很容易扩展。

  概念上,SignalProcess是负责处理字节数组的信号。规约方面,SignalProcessor表现为一个返回字节数组的process()方法。

  而SignalProcessor的实现的方面。我们看到,SignalProcessor调用ByteFilter的实例,在我们经过这里的时候,我们只需考虑它的规约(filter()方法),而不需考虑它的实现。这样很好,干净清楚。

  但是,问题在于,SignalProcessor和ByteFilter之间的调用混合了两种不同的概念(创建的概念和使用的概念)。SignalProcessor掌控了HiPassFilter这个类的创建,同时它也使用这个实例来工作。

  这看起来好象微不足道,并且实际中也会经常出现。但是,让我思考一下这两个职责,使用对象和制造对象,把它们看做相对独立的事情,并审视它们之间建立的藕合。 关于对象使用的观点

  一个客户对象要使用服务对象,要通过服务对象提供的公共方法。如果服务对象被引用为"Object"这种类型,那么就只有一些通用方法可以调用。所以,如果客户对象要使用服务对象,就要满足以下三个条件之一。

  ·知道这个服务对象的实际类型

  ·知道这个服务对象实现的某一个接口

  ·知道这个服务对象继承树上端的一个基类

  为了尽可能的降低藕合,我们倾向于满足后两个条件之一,这样,将来根据需要,可以改变实际被使用的客户对象,而不需要改变客户对象的代码。

  换句话说,理想情况下,客户对象应该依赖一个抽象,而不是具体现存的一个类,这样将来就可以自由的添加不同的服务类,而不需要维护客户类的代码。特别在服务类被大范围使用的情况下,这种设计原则显示犹为重要。 对象创建的观点

  显而易见地,如果我们要避免客户对象知道Bytefilters的具体的实现和这个对象如何创建,这就是意味着,就要有另一个东西,在另一个地方知道这些信息。

  因此,我提出一种独特的概念。使用和创建。同样的,客户对象不涉及对象的创建,对象的创建者也不会涉及对象的使用。我们通常称这种"创建者"为工厂类。

  这种设计就意味着下面这种设计模型。

点击放大此图片

  需要认真考虑的是创建和使用之间最自然的藕合,做到这一点,任何东西发生变化时都不会提心吊胆地去维护。

  如果把ByteFilter的抽象和两个具体实现看作"多态性服务"(ByteFilter是一个服务类,但它有两个版本,而通过多态性这种机制来实现不同版之间的变化),而SignalProcessor这个类只以使用的视角关注这个服务,ByteFilterFactory则是负责不同服务类的创建。

  而ByteFilter这个抽象类型(本质上也只是一种抽象)有一些做为接口的公共方法,这个抽象类型是SignalProcessor类和ByteFilter这个多态性服务之间的藕合。而SignalProcessor和两个具体类之间不存在任何的藕合,但前提是我们是一个好的OO程序员,不会给接口随意的添加方法。

  ByteFilterFactory和ByteFilter多态性服务之间的藕合就是另一种情况了。这个工厂类和子类建立藕合,因为它必须通过"new"关键字来创建实例。因为这个工厂类很自然地和构造器之间存在藕合,同时也和ByteFilter存在藕合(在把值返回给SignalProcessor之前,需要创建这个类型的引用),但是工厂类却不关心ByteFilter的公共方法,因为工厂类的概念应该只是创建。工厂类创建对象,但绝不调用对象的方法。

  这一切带来的结果就是,当客户对象或是工厂类需要改变时,维护工作的痛苦会减少很多。

  如果具体的子类要改变,例如ByteFilter需要添加或移除不同的实现,或者某个实现的业务规则发生改变。同时,只要维护一下ByteFilterFactory的代码。而不需要影响SignalProcessor。

  如果ByteFilter的接口发生改变,添加、移除或改变公共方法,然后就需要修改一下SignalProcessor,但不会影响ByteFilterFactory。

  我们非常感兴趣一个问题,客户对象和工厂类之间有一个很薄弱的环节,那就是ByteFilter这个抽象本身,而不是它的接口。要特别强调这样一个事实,很多资深的设计员都认为正确的抽象是OO设计的关键问题。即使接口有问题,也好过错误的抽象概念。

  因此,清晰的划分就意味:实体A和实体B之间的关系,应该仅仅是A创建B或是A使用B,而不能两种关系都有。
原创粉丝点击