《重构》增强代码可读性
文章目录
- 重构原则
- 何为重构
- 为何重构
- 何时重构
- 重构会影响性能吗
- 实例
- 原始类
- 进行重构
- 分解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…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...