《重构》增强代码可读性
文章目录
- 重构原则
- 何为重构
- 为何重构
- 何时重构
- 重构会影响性能吗
- 实例
- 原始类
- 进行重构
- 分解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…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...