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

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

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

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

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

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

public interface ExcelTableExporter {
    public void export(ReportTable table, WritableWorkbook workbook);
}

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

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

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

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

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

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

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

public interface DrawingElement {
    public void draw(ReportCanvas canvas);
    public object getElement();
}

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

public interface ReportCanvas {
    public void addElement(DrawingElement element);
}

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

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

public class RowHeaderExcelElement implements DrawingElment{
    private object cell;

    @Override
    public void draw(ReportCanvas canvas) {
        canvas.addElement(this);
    }

    @Override
    public object getElement() {
        if (isNumber()) {
            cell = createNumberCell();
        } else {
            cell = createLabelCell(); 
        }
        return cell;
    }
}

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

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

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

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

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

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2017-11-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Crossin的编程教室

【Pygame 第11课】 GAME OVER

昨天得知《MacTalk·人生元编程》在多看书城上线之后,一咬牙,花了2.99元入手了。本书是微信公众账号“MacTalk”中的文章经重新审阅、校订、整理、排版...

36112
来自专栏编舟记

我是怎样学习新编程语言的

学习新的编程语言的最终目的是解决实际问题。掌握编程语言的过程,在某种程度上近似学习一种新的工程实践。不仅解决问题固然可乐,学习的过程也同样充满了新鲜感,不过需要...

693
来自专栏企鹅号快讯

学习C语言你所必须要了解的知识

C 语言的发展方向 ? 20世纪80年代初,C 在 UNIX系统的小型机世界中已经是主导语言了,从那时开始,它已经扩展到个人计算机和大型机, 大部分软件开发商公...

3348
来自专栏数据小魔方

think-cell chart系列3——瀑布图(上)

今天要跟大家分享的是think-cell chart系列的第三篇——瀑布图(上)。 还是以一个案例图表开始我们今天的分享。 ? 所用到的案例数据如下: ? ...

4898
来自专栏java架构师

设计模式学习笔记之桥接模式

这个模式在看书时,一直没想到更好的应用场景,由此感慨一下《设计模式之禅》这本书, 通过这本书,的确对各种模式有了个比较清晰的理解,甚至对模式的结构也能很明确。也...

3517
来自专栏编程之旅

《代码整洁之道》读书小结

最近晚间的加班暂时暂停了,大概已经整整一个月每天焦头烂额的写着业务代码,被各种逻辑搞的整个人都不大好了,好在是写的差不多了。

863
来自专栏python+iOS学习交流

2018最新最全BAT 全套高级iOS面试题以及面试资料强势来袭

一千个读者眼中有一千个哈姆雷特,一千名 iOS 程序员心目中就有一千套 iOS 高级面试题。本文就是笔者认为可以用来面试高级 iOS 程序员的面试题。

1662
来自专栏信数据得永生

JavaScript 编程精解 中文第三版 七、项目:机器人

3206
来自专栏Java架构解析

网上的人说 Java 的性能已经达到甚至超过 C++,是真的吗?

好多Java程序员都说由于JIT技术的引入,Java的性能已经和C++一样了,而且Java的开发效率极高,可以省下60%的时间。请问事实真的是这样吗?我平常也都...

1191
来自专栏牛客网

iOS秋招总结 = 面经 + 闲言碎语 (不断更新)

面经包含:携程、阿里、京东、腾讯 十一假期,秋招基本上已经结束了,剩下少量面试和少量流程中。虽然还没有最终决定,不过也还是决定来开个帖子,写一些总结回馈一下牛...

3754

扫码关注云+社区