《重构》增强代码可读性
文章目录
- 重构原则
- 何为重构
- 为何重构
- 何时重构
- 重构会影响性能吗
- 实例
- 原始类
- 进行重构
- 分解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…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...
02.运算符
目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&:逻辑与 ||:逻辑或 !:逻辑非 短路求值 位运算符 按位与&: 按位或 | 按位取反~ …...
海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...
react菜单,动态绑定点击事件,菜单分离出去单独的js文件,Ant框架
1、菜单文件treeTop.js // 顶部菜单 import { AppstoreOutlined, SettingOutlined } from ant-design/icons; // 定义菜单项数据 const treeTop [{label: Docker管理,key: 1,icon: <AppstoreOutlined />,url:"/docker/index"},{label: 权限管理,key: 2,icon:…...
