下面通过3个示例来说明外部表现与内部结构的区别。
1.模块设计
为一个单机版超市销售系统进行模块设计,基本功能有:销售处理、退货、商品出/入库、销售数据统计分析。
这里的基本功能需求(销售处理、退货、商品出/入库、销售数据统计分析)就是模块设计方案的对外表现,如果不能认识到内部结构不同于外部表现,那么很容易就会得出图1-11的模块设计,它为每一个功能都设计了一个独立的模块。
图1-11 外部表现与内部结构示例一(1)
内部结构要考虑坚固性,但很明显图1-11的设计方案根本没有坚固性可言。相比之下,图1-12的方案使用了分层风格,设计了固定的层间接口,使得View、Logic和Data三个层次相对独立,就有了更好的可修改性和可复用性。在图1-12方案的Logic层和Data层,不同模块之间为了消除重复冗余而增加了相互依赖,虽然结构更复杂,但提高了可维护性。
图1-12 外部表现与内部结构示例一(2)
很明显图1-12的设计方案是更好的设计,因为它并不仅仅满足了外部表现的要求,还考虑了内部结构的坚固性。
2.类结构设计
为入库管理进行面向对象静态结构设计:已有库存商品存储在文件FF.dat中,用户在界面上输入入库商品和数量,系统增加FF.dat中相应商品的数量。
如果直接、线性地反映功能要求,那么得出图1-13的设计方案是比较自然的。在图1-13的方案中,在界面接受用户请求后,直接调用Store类的add方法,add方法负责操纵FF.dat文件,把数据增加情况写入文件。
图1-13 外部表现与内部结构示例二(1)
图1-13的设计方案在质量上乏善可陈,它的界面、业务逻辑(数据增加逻辑)、持久化(读写FF.dat)三个方面的代码是混在一起的:
● 复用时,要三个方面一起复用,难以将其中一个方面(例如持久化)独立出来复用,可复用性差。
● 如果三个方面中的一个需要修改,难免会影响到其他两个,可修改性差。
● 如果三个方面中的任意一个需要灵活性,例如多个界面使用相同的业务逻辑或者数据持久化到多个不同的文件,方案难以实现,灵活性和可扩展性差。
相比之下,图1-14的设计方案是更加坚固的。图1-14的设计使用了典型的设计方法:
● 界面、业务逻辑(Store类)、持久化文件都得到了分割处理。
● 将控制逻辑独立封装为Controller,实现了界面和业务逻辑的解耦合,二者可以独立变化。
● 将持久化职责封装为StoreDao,实现了业务逻辑和持久化的解耦合,二者可以独立变化。
● Controller和Dao还可以使用接口方法,被设计得易于调整。
因为进行了解耦合处理,所以图1-14的设计方案有更好的可扩展性、可修改性和可复用性:
● 界面、业务逻辑(Store类)、持久化都很容易独立复用,调整相关的Controller和Dao即可。
● 界面、业务逻辑(Store类)、持久化都很容易修改,不影响接口的情况下完全没有修改副作用,即使修改影响了接口也仅仅是调整Controller和Dao即可。
● 调整Controller和Dao,可以实现界面、业务逻辑(Store类)、持久化的灵活性和可扩展性。
图1-14 外部表现与内部结构示例二(2)
3.程序设计
将1~7数字转变为“星期一”~“星期日”字符串。
一个比较直观的程序实现如图1-15所示。但是这个实现中使用了多项条件分支,是一个可维护性较差的方案。
图1-15 外部表现与内部结构示例三(1)
需要专门说明的是,多项分支条件都会被认为可维护性不佳。在维护工作中,如果修改涉及分支条件,就可能会因为判定条件维护不善而出现修改错误或者连锁副作用。例如:i值从1~7修改为0~6,那么就需要对getWeekDay方法内的每一个分支都做条件修改,只要有一个分支条件修改时不慎出错,就会带来修改错误。
另一个可以帮助判定多项分支条件质量不佳的是方法的复杂度度量指标:圈复杂度。圈复杂度与方法代码内部的分支数成正比,圈复杂度越高,代码质量越低。如果方法内部有多项分支条件,圈复杂度就会高,质量会低。
图1-16是一个可维护性更好的设计方案,它使用了表驱动的编程方法,它的圈复杂度大大降低了,质量提升了。图1-16的方案使得程序更不易读(分离了数据和逻辑),但更易于修改和复用(消除了多项条件分支)。
图1-16 外部表现与内部结构示例三(2)
综合上述3个示例,作为一个设计师要时刻谨记:设计师的主要任务是针对给定的外部表现要求,构造一个坚固、优雅的内部结构,虽然很多时候内部结构可以简单到与外部表现基本相同,但不要想当然地直接将外部表现映射为内部结构。