《重构》增强代码可读性
文章目录
- 重构原则
- 何为重构
- 为何重构
- 何时重构
- 重构会影响性能吗
- 实例
- 原始类
- 进行重构
- 分解statements方法
- 提取函数
- 搬移函数
- 提炼“积分计算”功能
- 去除临时变量(以查询取代临时变量)
- 运用多态取代与价格相关的条件逻辑
- 代码迁移Movie类
- Price类 状态模式
- 搬移函数
- 以多态取代表达式
重构原则
何为重构
不改变代码本身可观察的行为,进行代码的构造变化
为何重构
- 消除重复代码,方便未来的修改
- 利用重构协助理解不熟悉的代码
- 提高以后维护代码的速率
何时重构
- 添加代码时
- 修改bug时
- 复审代码质量时
重构会影响性能吗
可能会,但原则是先写出能完善的代码,再测试性能
实例
原始类

Movie类:该类主要记录类型、价格和标题等,是单纯数据类。
/*** Movie记录类型、价格和标题等,单纯数据类。*/
public class Movie {//三种片类型public static final int CHILDRENS = 2;public static final int REGULAR = 0;public static final int NEW_RELEASE = 1;private String title;private int priceCode;public Movie(String title, int priceCode) {this.title = title;this.priceCode = priceCode;}public int getPriceCode() {return priceCode;}public void setPriceCode(int priceCode) {this.priceCode = priceCode;}public String getTitle() {return title;}
}
Rental类:表示某位顾客租了一部影片,表示行为。
/*** Rental表示某位顾客租了一部影片,表示行为。*/
class Rental {private Movie movie;private int daysRented;public Rental(Movie movie, int daysRented) {this.movie = movie;this.daysRented = daysRented;}public int getDaysRented() {return daysRented;}public Movie getMovie() {return movie;}
}
Customer类:表示顾客,有数据和相应的访问函数。
/*** Customer表示顾客,有数据和相应的访问函数*/
public class Customer {private String name;private Vector rentals = new Vector();public Customer(String name) {this.name = name;}public void addRental(Rental rental) {rentals.add(rental);}public String getName() {return name;}/*** 提供一个用于生成详单的函数*/public String statement() {double totalAmount = 0;//常客计算积分时使用int frequentRenterPoints = 0;Enumeration enumeration = rentals.elements();String result = "Rental Record for " + getName() + "\n";while (enumeration.hasMoreElements()) {//总金额double thisAmount = 0;Rental each = (Rental) rentals.elements();switch (each.getMovie().getPriceCode()) {case Movie.REGULAR:thisAmount += 2;//优惠力度if (each.getDaysRented() > 2) {thisAmount += (each.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE://果然还是新书最贵啊thisAmount += each.getDaysRented() * 3;break;case Movie.CHILDRENS:thisAmount += 1.5;if (each.getDaysRented() > 3) {thisAmount += (each.getDaysRented() - 3) * 1.5;}break;}frequentRenterPoints++;//如果是新书,另算积分呢if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() >= 1) {frequentRenterPoints++;}result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";totalAmount += thisAmount;}result += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";return result;}
}
进行重构
分解statements方法
长长的函数需要大卸八块,代码块越小,代码的移动和处理也就越轻松。将较小代码块移至更合适的类,降低代码重复使新函数更容易撰写。
提取函数
找出逻辑泥团并运用提取函数方法。本例中的switch语句需提炼至独立函数。找出函数内局部变量和参数。each(未被修改,可以当成参数传入新的函数)和thisAmount(会被修改,格外小心,如果只有一个变量修改,可以将其作为返回值)。那么将新函数返回值返回给thisAmount是可行的。
/*** 提供一个用于生成详单的函数*/
public String statement() {....while (enumeration.hasMoreElements()) {//总金额double thisAmount = 0;Rental each = (Rental) rentals.elements();thisAmount = amountFor(each);....}....
}/*** 金额计算* @param aRental* @return*/
private double amountFor(Rental aRental) {//注意double、int类型之间的转换。double result = 0;switch (aRental.getMovie().getPriceCode()) {case Movie.REGULAR:result += 2;//优惠力度if (aRental.getDaysRented() > 2) {result += (aRental.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE://果然还是新书最贵啊result += aRental.getDaysRented() * 3;break;case Movie.CHILDRENS:result += 1.5;if (aRental.getDaysRented() > 3) {result += (aRental.getDaysRented() - 3) * 1.5;}break;}return result;
}
搬移函数
观察上一步提炼出来的amountFor函数,使用了Rental类的信息却没有使用来自Customer类的信息,函数是应该放在它所使用的数据的对象内的,所以amountFor应该要放到Rental类而非Customer类,调整代码以使用新类。
class Rental {..../*** 金额计算* @return*/public double getCharge() {//注意double、int类型之间的转换。double result = 0;switch (getMovie().getPriceCode()) {case Movie.REGULAR:result += 2;//优惠力度if (getDaysRented() > 2) {result += (getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE://果然还是新书最贵啊result += getDaysRented() * 3;break;case Movie.CHILDRENS:result += 1.5;if (getDaysRented() > 3) {result += (getDaysRented() - 3) * 1.5;}break;}return result;}
}
public class Customer {..../*** 提供一个用于生成详单的函数*/public String statement() {....while (enumeration.hasMoreElements()) {...result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";totalAmount += each.getCharge();}...}
}
提炼“积分计算”功能
积分计算因影片种类而有所不同,针对“积分计算”代码运用Extract Method重构手法。局部变量each,另一个临时变量是frequentRenterPoints(这个参数在使用之前已初始化,但提炼出的函数并未读取该值,因此无需传入,只需作为新函数的返回值累加上去即可)。
class Rental {.../*** 计算常客积分* @return*/public int getFrequentRenterPoints() {//如果是新书,另算积分呢if (getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() >= 1) {return 2;} else {return 1;}}
}
public class Customer {..../*** 提供一个用于生成详单的函数*/public String statement() {double totalAmount = 0;//常客计算积分时使用int frequentRenterPoints = 0;Enumeration enumeration = rentals.elements();String result = "Rental Record for " + getName() + "\n";while (enumeration.hasMoreElements()) {Rental each = (Rental) rentals.elements();//计算常客积分frequentRenterPoints += each.getFrequentRenterPoints();result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";totalAmount += each.getCharge();}result += "Amount owed is " + String.valueOf(totalAmount) + "\n";result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";return result;}
}
去除临时变量(以查询取代临时变量)
临时变量会造成冗长复杂的函数,以查询取代临时变量方法,以查询函数替代totalAmount和frequentRentalPoints临时变量。任何函数均可调用,促成干净设计、减少冗长函数。
public class Customer {.../*** 提供一个用于生成详单的函数*/public String statement() {//常客计算积分时使用Enumeration enumeration = rentals.elements();String result = "Rental Record for " + getName() + "\n";while (enumeration.hasMoreElements()) {Rental each = (Rental) rentals.elements();result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";}result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";return result;}/*** 获取总积分* @return*/private double getTotalFrequentRenterPoints() {int result = 0;Enumeration enumeration = rentals.elements();while (enumeration.hasMoreElements()) {Rental each = (Rental) rentals.elements();result += each.getFrequentRenterPoints();}return result;}/*** 获取总金额* @return*/private double getTotalCharge() {double result = 0;Enumeration enumeration = rentals.elements();while (enumeration.hasMoreElements()) {Rental each = (Rental) rentals.elements();result += each.getCharge();}return result;}
}
运用多态取代与价格相关的条件逻辑
代码迁移Movie类
用户准备修改影片分类规则。费用计算和常客积分计算也会因此而发生改变。因为系统可能会加入新影片类型,不稳定,因此在Movie对象内计算费用。同样的手法处理常客积分函数。
public class Movie {.../*** 根据影片类型获取费用* @param daysRented* @return*/public double getCharge(int daysRented) {double result = 0;switch (getPriceCode()) {case Movie.REGULAR:result += 2;//优惠力度if (daysRented > 2) {result += (daysRented - 2) * 1.5;}break;case Movie.NEW_RELEASE://果然还是新书最贵啊result += daysRented * 3;break;case Movie.CHILDRENS:result += 1.5;if (daysRented > 3) {result += (daysRented - 3) * 1.5;}break;}return result;}public int getFrequentRenterPoints(int daysRented) {//如果是新书,另算积分呢if (getPriceCode() == Movie.NEW_RELEASE && daysRented >= 1) {return 2;} else {return 1;}}
}
class Rental {private Movie movie;private int daysRented;.../*** 金额计算* @return*/public double getCharge() {return movie.getCharge(daysRented);}/*** 计算常客积分* @return*/public int getFrequentRenterPoints() {return movie.getFrequentRenterPoints(daysRented);}
}
Price类 状态模式
多态取代switch语句,多态设计时不要直接继承Movie,而是通过Price间接去处理,一部影片可以在生命周期内修改自己的分类,但一个对象却不能再生命周期内修改自己所属的类,使用state模式
abstract class Price {abstract int getPriceCode();
}
public class ChildrenPrice extends Price{@Overrideint getPriceCode() {return Movie.CHILDRENS;}
}
public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}
}
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}
}
public class Movie {//三种片类型public static final int CHILDRENS = 2;public static final int REGULAR = 0;public static final int NEW_RELEASE = 1;private String title;private Price price;public Movie(String title, int priceCode) {this.title = title;setPriceCode(priceCode);}public int getPriceCode() {return price.getPriceCode();}public void setPriceCode(int arg) {switch (arg) {case Movie.REGULAR:price = new RegularPrice();break;case Movie.NEW_RELEASE:price = new NewReleasePrice();break;case Movie.CHILDRENS:price = new ChildrenPrice();break;default:throw new IllegalArgumentException("Incorrect Price Code");}}....}}
搬移函数
将Movie中的getCharge方法下沉至Price方法中。
abstract class Price {abstract int getPriceCode();/*** 根据影片类型获取费用* @param daysRented* @return*/public double getCharge(int daysRented) {double result = 0;switch (getPriceCode()) {case Movie.REGULAR:result += 2;//优惠力度if (daysRented > 2) {result += (daysRented - 2) * 1.5;}break;case Movie.NEW_RELEASE://果然还是新书最贵啊result += daysRented * 3;break;case Movie.CHILDRENS:result += 1.5;if (daysRented > 3) {result += (daysRented - 3) * 1.5;}break;}return result;}
}
public class Movie {private Price price;...public int getPriceCode() {return price.getPriceCode();}....
}
以多态取代表达式
次取出getPriceCode的一个case分支,在对应的类建立覆盖函数。同样的方法处理getFrequentRenterPoints方法
/*** 新建Price类,并提供类型相关的行为,为此,加入抽象函数,并在所有子类中加上对应的具体操作。*/
abstract class Price {/*** 获取影片类型code码* @return*/abstract int getPriceCode();/*** 根据影片类型获取费用* @param daysRented* @return*/abstract double getCharge(int daysRented);/*** 如果是新书,采用复写的方法,在超类中留下一个已定义的函数,使之成为一种默认行为。* @param daysRented* @return*/int getFrequentRenterPoints(int daysRented) {return 1;}
}
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}@Overridepublic double getCharge(int daysRented) {double result = 2;//优惠力度if (daysRented > 2) {result += (daysRented - 2) * 1.5;}return result;}
}
public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}@Overridepublic double getCharge(int daysRented) {//果然还是新书最贵啊return daysRented * 3;}@Overridepublic int getFrequentRenterPoints(int daysRented) {return (daysRented > 1) ? 2 : 1;}
}
public class ChildrenPrice extends Price{@Overrideint getPriceCode() {return Movie.CHILDRENS;}@Overridepublic double getCharge(int daysRented) {double result = 1.5;if (daysRented > 3) {result += (daysRented - 3) * 1.5;}return result;}
}
重构之后的类图

相关文章:
《重构》增强代码可读性
文章目录重构原则何为重构为何重构何时重构重构会影响性能吗实例原始类进行重构分解statements方法提取函数搬移函数提炼“积分计算”功能去除临时变量(以查询取代临时变量)运用多态取代与价格相关的条件逻辑代码迁移Movie类Price类 状态模式搬移函数以多…...
数据分析自学路线
数据分析作为近几年火起来的IT技术岗位,在大数据时代的浪潮下迅速发酵膨胀,席卷了众多互联网企业,漫延到了金融、教育、医疗、消费等传统行业,在新经济领域也有重要作用,比如人工智能、新能源、电子芯片、企业数字化服…...
蓝桥杯C++组怒刷50道真题
🌼深夜伤感网抑云 - 南辰Music/御小兮 - 单曲 - 网易云音乐 🌼多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 50题才停更,课业繁忙,有时间就更,2023/3/14/15:06写下 目录 👊填空题 🌼一…...
【期末小作业】HTML、CSS前端静态网页
分享一个可以“趁别人喝咖啡的功夫“”写的一个静态网页,纯纯练手小项目,适合前端刚入门的小白练练手。 前端练手静态页面 实现效果图展示 CSS代码 HTML 代码 环境:VScode编辑器 语言:HTML 、CSS 一、实现效果图 仅仅通过…...
Windows逆向安全(一)之基础知识(二)
反汇编分析C语言 空函数反汇编 #include "stdafx.h"//空函数 void function(){}int main(int argc, char* argv[]) {//调用空函数function();return 0; }我们通过反汇编来分析这段空函数 函数外部 12: function(); 00401048 call ILT5(func…...
Python 基础教程【2】:条件语句和循环语句
本文已收录于专栏🌻《Python 基础》文章目录1、流程控制语句1.1 顺序语句1.2 条件语句1.2.1 if语句注意事项1.2.2 三元运算符1.2.3 自动类型转换1.3 循环语句1.3.1 while 循环1.3.2 for-in 循环1.3.3 for...else 循环1.3.4 break 和 continue 的区别2、实践——猜数…...
【React避坑指南】useEffect 依赖引用类型
前言 如果你是一个入行不久的前端开发,面试中多半会遇到一个问题: 你认为使用React要注意些什么? 这个问题意在考察你对React的使用深度,因为沉浸式地写过一个项目就会发现,不同于一些替你做决定的框架,“…...
Android binder通信实现进程间通信
一.binder通信原理Binder 是 Android 系统中用于跨进程通信的一种机制,它允许一个进程中的组件与另一个进程中的组件进行通信,从而实现进程间通信 (IPC)。Binder 机制是基于 Linux 内核提供的进程间通信机制 (IPC) 实现的。在 Binder 机制中,…...
2023年BeijngCrypt勒索病毒家族最新变种之.halo勒索病毒
目录 前言:简介 一、什么是.halo勒索病毒? 二、.halo勒索病毒是如何传播感染的? 三、感染.halo后缀勒索病毒建议立即做以下几件事情 四、中了.halo后缀的勒索病毒文件怎么恢复? 五、加密数据恢复情况 六、系统安全防护措施建…...
【LeetCode】BM1 反转链表、NC21 链表内指定区间反转
作者:小卢 专栏:《Leetcode》 喜欢的话:世间因为少年的挺身而出,而更加瑰丽。 ——《人民日报》 BM1 反转链表 描述: 给定一个单链表的头结点pHead(该头节点是有值的,…...
拼多多24届暑期实习真题
1. 题目描述: 多多开了一家自助餐厅,为了更好地管理库存,多多君每天需要对之前的课流量数据进行分析,并根据客流量的平均数和中位数来制定合理的备货策略。 2. 输入输出描述: 输入描述: 输入共两行&#x…...
JS高级知识总结
文章目录1. this指向问题2. 对象进阶2.1 对象的定义和使用2.2 对象访问器2.2.1 Getter2.2.2 Setter2.3 对象构造器2.4 对象原型2.4.1 prototype属性2.4.2 \_\_proto\_\_ 属性2.4.3 constructor属性2.4.4 原型链2.5 Object对象2.5.1 管理对象2.5.2 保护对象3. 函数进阶3.1 函数的…...
Jenkins+Docker+Maven+gitlab实现自动构建、远程发布
前言 一个项目完整的生命周期是从开发的coding阶段和coding阶段的质量测试,再到多次发布投入使用。目前大部分的测试阶段并不是从coding结束后开始的,而是和coding同步进行的。可能今天早上coding完成一个功能,下午就要投入测试。在这期间&a…...
centos7克隆虚拟机完成后的的一些配置介绍
系列文章目录 centos7配置静态网络常见问题归纳_张小鱼༒的博客-CSDN博客 文章目录 目录 系列文章目录 前言 一、配置Hadoop要下载的压缩包 1、下载对应版本的Hadoop压缩包 2、我们如何查看自己电脑的端口号 3、下载jdk对应的版本 二、虚拟机centos7克隆虚拟机完成后的一些基本…...
C语言/动态内存管理函数
C程序运行时,内存将被划分为三个区域,而动态开辟的内存区间位于堆区。 文章目录 前言 一、内存划分 二、malloc函数 三、calloc函数 四、realloc函数 五、free函数 总结 前言 在使用C语言编写程序时,使用动态内存是不可避免的&#x…...
华为OD机试题,用 Java 解【任务调度】问题
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不要…...
河南农业大学2023春蓝桥杯赛前训练第一场
A 滑板上楼梯 贪心 要求最少次数,尽量多跳三阶的,不能连续跳三阶,三阶后面一定要跟着一个一阶,相当于直接跳四阶 每次跳四阶都是两步(3、1),如果 % 4 之后,正好剩下 3 ,…...
docker-dockerfile
1.常用保留字指令 FROM : 基础镜像MAINTAINER: 维护者姓名和邮箱RUN : Run ["可执行文件",参数1]; Run [shell命令]EXPOSE: 暴露出的端口号WORKDIR: 登录后的位置USER: 执行用户,默认是rootENV: 构建过程的环境变量ADD: 将宿主机的文件拷贝到…...
【JavaEE】浅识进程
一、什么是进程1.1 操作系统学习进程之前首先要了解我们的操作系统(OS),我们的操作系统实际上也是一款软件,属于系统软件的范畴,操作系统早期采用命令提示框与用户交互,我们启动某个软件,打开某…...
Java_Spring:1. Spring 概述
目录 1 spring 是什么 2 Spring 的发展历程 3 spring 的优势 4 spring 的体系结构 1 spring 是什么 Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspec…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
