《重构改善既有代码的设计》之重构列表--大型重构

2023-12-16 14:48

本文主要是介绍《重构改善既有代码的设计》之重构列表--大型重构,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Tease Apart Inheritance (梳理并分解继承体系)

某个继承体系同时承担两项责任。

建立两个继承体系,并通过委托关系让其中一个可以调用另一个。

动机

继承是个好东西,它可以明显减少子类中的代码量。函数的重要性可能并不和它的大小成比例--在继承体系中尤然。

不过,先别急着为这个强大的工具欢呼雀跃,因为继承也很容易被误用,并且这种误用还很容易在开发人员之间蔓延。今天你为了一项小任务而加入一个小小的子类,明天又为另一项任务在继承体系的另一个地方加入另一个子类。一个星期(一个月、一年)之后,你就会发现自己深陷泥沼,而且连一根拐杖都没有。

混乱的继承体系是一个严重的问题,因为它会导致代码重复,而后者正是程序员生涯的致命毒药。它还会使修改变得困难,因为特定问题的解决策略被分散到了整个继承体系。最终,你的代码将难以理解。你无法简单的说:“这就是我的继承体系,它能计算结果。”而必须说“它会计算出结果。。。额,这些是用以表现不同表格形式的子类,每个子类又有一些子类针对不同的国家。”

要指出继承体系是否承担了两项不同的责任并不困难:如果继承体系中 的某一特定层级上的所有类,其子类名称都以相同的形容词开始,那么这个体系很可能就是承担着两项不同的责任。

做法

1、首先识别出继承体系所承担的不同责任,然后建立一个二维表格(或者三维表格乃至四维表格,如果你的继承体系够混乱而你的绘图工具够酷的话),并以坐标轴标示出不同的任务。我们将重复运用本重构,处理两个或两个以上的维度(当然,每次只处理一个维度)。

2、判断哪一项责任更重要些,并准备将它留在当前的继承体系中。准备将另一项责任移到另一个继承体系中。

3、使用Extract Class从当前的超类中提炼出一个新类,用以表示重要性稍低的责任,并在原超类中添加一个实例变量,用以保存新类的实例。

4、对应于原继承体系中的每个子类,创建上述新类的一个子类。在原继承体系的子类中,将前一步骤所添加的实例变量初始化为新建的子类实例。

5、针对原继承体系中的每个子类,使用Move Method 将其中的行为搬移到与之对应的新建子类中。

6、当原继承体系中的某个子类不再有任何代码时,就将它去除。

7、重复上述步骤,直到原继承体系中的所有子类都被处理过为止。观察新继承体系,看看是否有可能对它实施其他重构手法,例如Pull Up MethodPull Up Field

二、Convert Procedural Design to Objects (将过程化设计转化为对象设计)

你手上有一些传统过程化风格的代码。

将数据记录变成对象,将大块的行为分为小块,并将行为移入相关对象之中。

动机

有一次,我们的客户在项目开始时给开发者提出了两条必须遵守的条件:(1)必须使用java;(2)不能使用对象。

这故事固然好笑。不过,尽管java是面向对象语言,“使用对象”可远不仅仅是调用构造函数而已。对象的使用也需要花时间去学习。往往你会面对一些过程化风格的代码所带来的问题,并因而希望它们变得更面向对象一些。典型的情况是:类中有着长长的过程化函数和极少的数据,旁边则是一堆哑数据对象--除了数据访问函数外没有其他任何函数。如果你要转换的是一个纯粹的过程化程序,可能连这些东西都没有。

我们并不是说绝对不应该出现只有行为而几乎没有数据的对象。在Strategy模式中,我们常常使用一些小型的策略对象来改变宿主对象的行为,这些小型的策略对象就只有行为而没有数据。但是这样的对象通常比较小,而且只有在我们特别需要灵活性的时候,才会使用它们。

做法

1、针对每一个记录类型,将其转变为只含访问函数的哑数据对象。

如果你的数据来自关系式数据库,就把数据库中的每个表变成一个哑数据对象。

2、针对每一处过程化风格,将该处的代码提炼到一个独立类中。

你可以把提炼所得的类做成一个Singleton(为了方便重新初始化),或是把提炼所得的函数声明为static

3、针对每段长长的程度,实施Extract Method及其他相关重构将其分解。再以Move Method将分解后的函数分别移到它所相关的哑数据类中。

4、重复上述步骤,直到原始类中的所有函数都被移除。如果原始类是一个完全过程化的类,将它拿掉将大快人心。

三、Separate Domain from Presentation (将领域和表述/显示分离)

某些GUI类之中包含了领域逻辑。

将领域逻辑分离出来,为它们建立独立的领域类。

动机

提到面向对象,就不能不提MVC(模型-视图-控制器)模式,在Smalltalk-80环境中,人们以此模式维护GUI(图形用户界面)和领域对象间的关系。

MVC模式最核心的价值在于:它将用户界面代码(即视图;亦即现今常说的“展开层”)和领域逻辑(即模型)分离了。展现类只含有用以处理用户界面的逻辑;领域类不含任何与程序外观相关的代码,只含业务逻辑相关的代码。将程序中这两块复杂的部分加以分离,程序未来的修改将变得更加容易,同时也使同一业务逻辑的多种展现方式成为可能。那些熟稔面向对象技术的程序员会毫不犹豫地在他们的程序中进行这种分离,并且这种做法也的确证实了它自身的价值。

但是,大多数人并没有在设计中采用这种方式来处理GUI。大多数客户端/服务器结构的GUI应用都采用双层逻辑设计:数据保存在数据库中,业务逻辑放在展现类中。这样的环境往往迫使你也倾向这种设计风格,使你很难把业务逻辑放在其他地方。

Java是一个真正意义上的面向对象环境,因此你可以创建内含业务逻辑的、与展现逻辑无关的领域对象。但你还是会经常遇到上述双层风格写就的程序。

做法

1、为每个窗口建立一个领域类。

2、如果窗口内有一张表格,新建一个类来表示其中的行,再以窗口所对应之领域类中的一个集合来容纳所有的行领域对象。

3、检查窗口中的数据。如果数据只被用于UI,就把它留着;如果数据被领域逻辑使用,而且不显示于窗口上,我们就以Move Field将它搬移到领域类中;如果数据同时被UI和领域逻辑使用,就对它实施Duplicate Observed Data,使它同时存在于两处,并保持两处之间的同步。

4、检查展现类中的逻辑。实施Extract Method 将展现逻辑从领域逻辑中分开。一旦隔离了领域逻辑,再运用Move Method将它移到领域类。

5、以上步骤完成后,你就拥有了两组彼此分离的类:展现类用以处理GUI,领域类包含所有业务逻辑。此时的领域类组织可能还不够严谨,更进一步的重构将解决这些问题。

四、Extract Hierarchy(提炼继承体系)

你有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。

建立继承体系,以一个子类来表示一种特殊情况。

动机

在渐进式设计过程中,常常会有这样的情况:一开始设计者只想以一个类实现一个概念;但随着设计方案的演化,最后却可能一个类实现了两个、三个乃至十个不同的概念。一开始,你建立了这个简单的类。数天或数周之后,你可能发现:只要加入一个标记和一两个测试,就可以在另一个环境下使用这个类;一个月之后你又发现了另一个这样的机会;一年之后,这个类就完全一团糟了:标记变得和条件表达式遍布各处。

当你遇到这种瑞士军刀般的类--不但能够开瓶开罐、砍小树枝、还能在演示会上打出激光强调重点--你就需要一个好策略(亦如本项重构),将它的各个功能梳理并分开。不过,请注意,只有当条件逻辑在对象的整个生命周期保持不变,本重构导入的策略才适用。否则你可能必须在分离各种状况之前先使用Extract Class 

Extract Hierarchy 是一项大型重构,如果你一天之内不足以完成它,不要因此失去勇气。将一个极度混乱的设计方案梳理出来,可能需要数周甚至数月的时间。你可以先进行本项重构中的一些简易步骤,稍微休息一下,再花几天时间编写一些能体现产出的代码。当你领悟到更多东西,再回来继续本项重构的其他步骤--这些步骤将因为你的领悟而显得更加简单明了。

做法

我们为你准备了两组重构手法。第一种情况是:你无法确定哪些地方会发生变化。这时候你会希望每次一小步地前进。

1、鉴别出一种变化情况。

如果这种变化可能在对象声明周期的不同阶段而有不同体现,就运用Extract Class 将它提炼为一个独立的类。

2、针对这种变化情况,新建一个子类,并对原始类实施Replace Constructor with Factory Method。再修改工厂函数,令它返回适当的子类实例。

3、将含有条件逻辑的函数,一次一个,逐一复制到子类,然后在明确情况下(对子类明确,对超类不明确),简化这些函数。

如有必要隔离函数中的条件逻辑和非条件逻辑,可对超类实施Extract Method

4、重复上述过程,将所有变化情况都分离出来,直到可以将超类声明为抽象类为止。

5、删除超类中那些被所有子类覆写的函数本体,并将它们声明为抽象函数。

如果你非常清楚原始类会有哪些变化情况,可以使用另一种做法。

1、针对原始类的每一种变化情况,建立一个子类。

2、使用Replace Constructor with Factory Method将原始类的构造函数转变成工厂函数,并令它对每一种变化返回适当的子类实例。

如果原始类中的各种变化情况是以类型码表示,先使用Replace Type Code with Subclass;如果那些变化情况在对象生命周期的不同阶段会有不同体现,请使用Replace Type Code with State/Strategy

3、针对带有条件逻辑的函数,实施Replace Conditional with Polymorphism 。如果并非整个函数的行为有所变化,而只是函数一部分有所变化,请先运用Extract Method将变化部分和不变部分分隔开来。

 

 

至此,重构改善既有代码的设计就学完啦!!!

这篇关于《重构改善既有代码的设计》之重构列表--大型重构的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/500858

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机