对象的自治和行为的扩展与适配

开发 开发工具
在坏的设计中,数据往往是分散的,甚至是杂乱的,这就好像一群失去意识的猛兽,我们无法控制、协调以及管理它们。

在坏的设计中,数据往往是分散的,甚至是杂乱的,这就好像一群失去意识的猛兽,我们无法控制、协调以及管理它们。这种漫无头绪的散乱数据,犹如猛兽的肆意妄为,会给系统带来无尽的灾难。随着系统的演化,这种灾难会逐渐蔓延至系统的各个角落。因此,在面向对象设计过程中,对数据分类是识别对象的一个前提。但是,仅仅封装了数据的对象,如果没有操作数据的行为,仍旧是没有意识的死亡对象。

[[213601]]

我始终认为,对象在拥有自己数据的情况下,应该是自治的。这种“自治”类似于SOA中服务自治的概念,但由于对象应该保持足够合理的细粒度,因此这种自治是有限度的自治;或者说它体现的是专家的自治。如果对象拥有足够的数据信息,就必须树立这些信息的权威,这些信息的处理就应该由对象自己来完成。如果它拥有的信息量不够,或者根本不具备,则可以委派给其他对象。此时,行为即对象的意识,是对象能够自治的前提。

对象自治依赖于面向对象设计的一个重要原则,即对象的数据与行为应该封装在一起。Craig Larman提出的“信息专家模式”正是说明了这一点,该模式认为拥有信息的对象才是处理这些信息的专家。

对象自治是一个很有趣的概念,我们把对象拟人化,使得对象成为组成社区的基本元素。在这个社区里,每个对象的行动都应该由自己来控制。无论是完成某个操作,还是发出请求,或者响应事件,对象都应该有自己的判断。判断的合理性来自于它掌握的信息量,以及我们赋予它的意识的灵性。在构建软件系统时,我们的目标就是要搭建这样一个由自治对象组成的社区,而不是无序的混沌世界。每当我们在操作数据时,发现数据开始具有发散、混乱、模糊、蔓延等特征时,就是封装数据的信号。不管这些数据的数量,还是大小,它都应该作为对象存在于系统,同时该对象应具备操作该数据的能力。

例如在报表系统中,我们试图将构建好的报表整体导出为Excel文件。我们为导出功能定义了专门的接口ExcelTableExporter,它接收一个报表对象和工作薄对象,导出报表到Excel文件中:

  1. public interface ExcelTableExporter { 
  2.     public void export(ReportTable table, WritableWorkbook workbook); 

这一接口的定义并无不妥之处。然而,当我们在实现export()接口方法时,事情开始变得难以控制。我们需要在export()方法中遍历整个报表,获得报表的行头、列头以及数据单元格,然后计算它们的坐标,获得它们的格式,再写入到Excel单元格中。显然,ExcelTableExporter要做的事情太多了,而它所要处理的报表数据也开始变得发散而混乱。

虽然我们对报表进行了合理的分解与封装,但坐标依旧是散乱的,格式也没有和报表对象封装在一起。组成报表的元素对象仅仅拥有展现的数据值,却不知道自己该放在哪个位置,又该以什么面貌展现。换言之,这些组成报表的对象都不具备充分的自主意识,使得操作它们的ExcelTableExporter心力憔悴。它需要观察每个报表元素对象的数据,元素之间的依赖关系,考虑如何计算它们的坐标,获得符合客户要求的格式。

简言之,职责的控制权应该是拥有相关数据的报表对象,而不应该是ExcelTableExporter。

如果我们将这种展现和导出报表的功能看做是将报表数据绘制在Excel画布上,那么ExcelTableExporter就好似一位不太高明的画师,奔忙于全局的掌控与细节的刻画,却因为能力不够而无法二者兼顾。

如果我们让这些组成报表的元素对象拥有绘制自身的能力,境况是否焕然一新呢?此时,ExcelTableExporter只需要取出元素对象,放在Excel画布上,它们自己就知道该往哪儿去,该怎么绘制,根本不用ExcelTableExporter来操心。

根据单一职责原则(SRP),报表元素对象与报表直接相关,本身不应该承担绘制的责任,但放在导出报表这个场景来看,却又是合乎情理的。而且,与绘制相关的数据本身就与报表数据直接相关,例如报表元素的坐标,就依赖于报表数据的个数,以决定它占用的行数和列数。报表的格式同样设置在报表元数据中。不过,从抽象的角度来看,我们应该为其定义不同的接口,这也符合接口隔离原则(ISP)。同时,我们还需要考虑绘制行为的扩展。

例如,在未来我们可能需要考虑将报表绘制为HTML网页。因此,我们可以定义一个绘制元素的接口:

  1. public interface DrawingElement { 
  2.     public void draw(ReportCanvas canvas); 
  3.     public object getElement(); 

draw()方法负责将报表元素绘制到ReportCanvas对象中。ReportCanvas体现了“画布”的隐喻,作为载体用来添加绘制出来的报表元素。

  1. public interface ReportCanvas { 
  2.     public void addElement(DrawingElement element); 

对于Excel而言,实现draw()方法就是在内部创建单元格对象。如果使用开源项目jxl来完成excel文件的生成,则该单元格对象可以是Label对象,也可以是jxl.write.Number对象。不过,ReportCanvas是不关心这些的,它只需要能够添加DrawingElement即可。这里就体现出了抽象DrawingElement的好处。

当报表元素对象在实现该接口时,如果是针对Excel的导出,就可以把诸如Label和Number这样的单元格对象封装到实现类中。例如报表中的行头对象就可以实现DrawingElement接口:

  1. public class RowHeaderExcelElement implements DrawingElment{ 
  2.     private object cell; 
  3.  
  4.     @Override 
  5.     public void draw(ReportCanvas canvas) { 
  6.         canvas.addElement(this); 
  7.     } 
  8.  
  9.     @Override 
  10.     public object getElement() { 
  11.         if (isNumber()) { 
  12.             cell = createNumberCell(); 
  13.         } else { 
  14.             cell = createLabelCell();  
  15.         } 
  16.         return cell; 
  17.     } 

这里的RowHeaderExcelElement类就体现了“自治”思想,因为它自己知道该如何将自己拥有的数据绘制到ReportCanvas。

而从功能扩展的角度讲,如果将来需要支持Html,就可以定义新的RowHeaderHtmlElement类实现DrawingElement接口。

因为引入了DrawingElement接口,报表元素对象就将绘制元素对象的数据与行为都封装了起来,使其成为了自治的对象。由于报表元素对象自身具备绘制功能,使得ExcelTableExporter的工作变得轻松自如,只需发出绘制的请求即可:

  1. for (DrawingElement element : table.getReportUnits()) { 
  2.     element.draw(canvas); 

通过合理地将职责进行转移,尽可能站在每个对象自身的角度进行合理的职责分配,从原则上实现各个对象的“自治”,就能够各司其职,避免出现一个庞大的无所不能的“上帝”对象。

【本文为51CTO专栏作者“张逸”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2009-03-16 09:16:13

行为扩展WCF.NET

2009-11-06 14:08:06

WCF行为扩展

2009-09-04 14:00:22

上网行为管理网络应用

2011-10-27 12:04:44

流量监控上网行为管理

2010-02-26 10:46:12

WCF行为扩展

2009-11-23 09:34:05

WPF本质

2023-06-28 07:54:44

数据治理数据分析

2009-06-30 11:27:25

JSP include

2016-01-31 17:45:31

2022-09-02 09:06:58

恶意扩展Chrome

2022-11-27 23:37:33

Agent对象存储

2013-01-14 16:10:06

2022-03-11 11:51:16

自动驾驶智能技术

2009-04-21 09:00:10

IT业工作调整失业

2010-05-12 11:50:29

2018-04-02 13:42:36

大数据数据泄露监管

2012-10-23 16:12:35

2010-03-01 14:41:14

WCF行为扩展

2015-11-25 11:00:48

谷歌Chrome浏览器

2009-08-28 13:57:29

virtual ove扩展点
点赞
收藏

51CTO技术栈公众号