5.5 Functional Decomposition(功能分解)
|
反模式名称:Functional Decomposition 别名:No Object−Oriented AntiPattern(非面向对象反模式)、“No OO”(非面向对象)[Akroyd 1996] 最常见规模:应用层 重构方案名称:Object−Oriented Reengineering(面向对象再设计) 重构方案类型:过程 根源:贪婪、懒惰 不平衡的力量:复杂性管理、变化管理 轶事证据:“这是我们的‘主’例程,在这个名为LISTENER(监听器)的类中。” |
5.5.1 背景
Functional Decomposition在过程式编程环境中是不错的做法。它对理解更大规模应用的模块化本质也是有益的。不过,它不能直接转换成类层次关系,而这就是问题的来源。在定义这个反模式时,作者们开始于Michael Akroyd对此主题的最初考虑。我们重新设定了它的格式以符合我们的模板,并用解说和图示进行了扩展。
5.5.2 一般形式
这个反模式是有经验的非面向对象开发人员使用面向对象语言来设计、实现一个应用时出现的问题。当开发人员习惯于使用一个“主”例程来调用许多个子例程时,他们往往会把每个子例程变成一个类,完全忽略掉类层次关系(也几乎全完全忽略掉面向对象)。结果代码在类结构上非常类似Pascal或FORTRAN这样的结构化语言。它会难以想像的复杂,因为那些机灵的过程式开发人员会想出非常聪明的办法来使用面向对象架构复制他们那些久经考验的(非面向对象)方法。
如果开发组以前使用C语言刚刚转换到C++,或者试图使用CORBA接口,或者刚实现某种被认为可以帮助他们的对象工具,就最可能遇到这种反模式。从长远角度来看,花钱进行面向对象培训或雇用采用对象进行思考的新程序员,可能更廉价一些。
5.5.3 症状和后果
● 采用“函数”名,例如Calculate_Interest或Display_Table作为名称的类可能就意味着存在这种反模式。
● 类的所有属性都是私有的,只在类的内部被使用。
● 只有单个操作,如一个函数的类。
● 令人难以置信的退化架构,完全丢弃了面向对象架构的要点。
● 完全没有利用面向对象原则如继承和多态。它的维护成本可能极其昂贵(首先如果它能工作的话;不过永远不要低估了逐渐跟不上技术发展的老程序员的灵活性)。
● 无法清晰地记录(甚至解释)系统是如何工作的。类模型完全没有意义。
● 根本不能期望获得软件复用。
● 测试人员感到挫折和无望。
5.5.4 典型原因
● 缺乏对面向对象的理解。实现者没有了解面向对象的本质。这在开发人员从非面向对象编程语言切换到面向对象编程语言时相当常见。由于在架构、设计和实现范型上都要发生变化,一个公司可能要花3年时间才能完全达到面向对象。
● 缺乏对架构的实施。当实现者不理解面向对象时,架构设计得有多好并不重要。实现者根本就不能理解他们到底在做什么。如果没有适当的监督,他们通常会找到某种办法来用他们了解的方法蒙混过关。
● 说明灾难。有时,负责生成说明和需求的人并不一定有开发面向对象系统的经验。如果他们说明的系统在需求分析之前做出架构性承诺,就有可能而且常常会导致类似Functional Decomposition的反模式。
5.5.5 已知例外
在不要求面向对象的解决方案时,Functional Decomposition反模式没什么问题。这一例外可以被扩展到用于处理具有纯功能本质而被包装起来,以便为实现代码提供面向对象接口的实现方案。
5.5.6 重构方案
如果还有可能探知软件基本需求的内容,就为软件定义一个分析模型来从用户的角度解释软件的关键特性。这对发现特定代码集中很多软件构造的内在动机是很重要的,而这些动机随着时间的流逝可能已经被遗忘了。对Functional Decomposition反模式解决方案中的所有步骤,都要提供所使用的过程的详细文档以作为将来维护工作的基础。
接下来,建立一个结合了已有系统的主要部分的设计模型。不要太强调改善模型而是建立一个基础来解释系统中尽可能多的部分。理想情况下,设计模型可以证明大部分软件模块的正确性,或者至少说明它们的合理性。为已有的代码集建立设计模型是具有启发意义的;它提供了对整个系统的组织结构的深度理解。完全有理由预测认为系统中某些部分的存在原因已经无人知晓,也无法做出合理的推断。
对没能进入设计模型的类,应用下面这些处理原则:
(1)如果该类只有一个方法,就尝试把它作为某个已有类的组成部分。某些类被设计为其他类的辅助类,把它们和它们辅助的基类合并起来往往更好一些。
(2)试着把数个类合并成一个满足某个设计目标的新类。这样做的目标是把几种类型的功能合并到单个类中。与先前较细粒度的那些类相比,它应该捕获了更广阔领域中的概念。例如,不要分别使用几个类来分别负责管理设备访问、过滤与设备交换的信息和控制设备,而是把它们合并到单个设备控制器对象中,该对象的方法可以完成之前被分散到多个类中的活动。
(3)如果类中没有任何形式的状态信息,就要考虑把它改写成函数。系统中潜在地有某些部分可能最好是被建模成函数,从而可以在系统的不同部位不受限制地访问它们。
对设计进行检查,找到相似的子系统。它们是复用的候选对象。作为程序维护的一部分,应该重构代码集以便在相似子系统之间复用代码(参阅Spaghetti Code反模式的解决方案以获得对软件重构的详细说明)。
5.5.7 示例
Functional Decomposition建立于用于操作数据的离散函数,例如可以通过使用Jackson结构化编程方法来建立。函数往往就是面向对象环境中的方法。两种环境中对函数的划分是根据不同范型进行的,从而产生了对函数及相关数据的不同分组。
图5-12中的简单例子显示了客户贷款场景的功能化版本:
(1)添加一个新客户。
(2)更新客户地址。
(3)计算给客户的一笔贷款。
(4)计算一笔贷款的利息。
(5)计算一笔贷款的还贷进度表。
(6)改变一个还贷进度表。
图5-12 客户贷款系统的功能分解
图5-13显示了客户贷款应用的面向对象视图。前一个图中的功能被映射成了对象方法。
图5-13 客户贷款系统的面向对象模型
5.5.8 相关解决方案
如果在存在Functional Decomposition问题的系统中已经投入了过多的工作,你也许可以通过采用类似于The Blob反模式替代方法的办法来挽回事态。
你也许可以把“主例程”类扩展成“协调器”类来管理系统的所有或大部分功能,而不是从头开始重构整个类层次关系。然后可以合并函数类并补充它们,让它们在修改后的“协调器”类指导下进行自己的处理,从而把它们转变成准面向对象的类。这个过程也许可以产生更可用的类层次关系[Fowler 1997]。
5.5.9 对其他视角和规模的适用性
架构视角和管理视角在最初预防和在开发进行中监督防止Functional Decomposition反模式方面都扮演了关键的角色。如果一开始就规划了正确的面向对象架构而在开发阶段出现了问题,那么保证实施最初的架构就是管理层面对的管理挑战。与之相似,如果出现问题的原因是最初就普遍缺乏正确的架构,那么发现这个问题、踩下刹车和获取架构性帮助仍然是管理方面的责任——越早进行代价就越低。
|





