目录
1、案例
(1)原代码
//电影类
public class Movie {
//有三种类型的电影 0常规电影 1、新电影 2、儿童影片
public static final int REGULAR= 0;
public static final int NEW_RELEASE =1;
public static final int CHILDRENS=2;
//影片名和影片类型
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; }
}
//租赁类
public 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; }
}
//顾客类
public class Customer{
// 顾客名称 租赁的影片数组
private String name;
private List<Rental> list;
public Customer(String name){
this.name = name;
this.list = new ArrayList<>();
};
//添加要租赁的
public void addRental(Rental rental) {
list.add(rental);
}
public String getName () { return name; }
public String statement(){
//总金额
double totalAmount =0;
//积分
int frequentRenterPoints=0;
for (Rental rental : list) {
double thisAmount= 0;
switch(rental.getMovie().getPriceCode()){
case Movie.REGULAR:
//常规影片,固定价格2元,租赁超过2元,每天1.5元
thisAmount += 2;
if (rental.getDaysRented()>2){
thisAmount +=(rental.getDaysRented()-2)*1.5;
}
break;
case Movie.NEW_RELEASE:
//新出的影片 每天3元
thisAmount += rental.getDaysRented()*3;
break;
case Movie.CHILDRENS:
//儿童片 固定1,5元 租赁超过3元,每天1.5元
thisAmount += 1.5;
if (rental.getDaysRented()>3){
thisAmount +=(rental.getDaysRented()-3)*1.5;
}
break;
default:
break;
}
totalAmount+=thisAmount;
//积分计算,每组一部影片,积分+1
frequentRenterPoints++;
//如果租的是新电影,且租期大于1天的,每部新电影再给一个积分
if (rental.getMovie().getPriceCode()==Movie.NEW_RELEASE &&
rental.getDaysRented()>1){
frequentRenterPoints++;
}
}
String result = "顾客"+getName()+ "\n消费:"+totalAmount+"\n积分:"+frequentRenterPoints;
return result;
}
}
(2)优化思路
问题:
1、需求:会改变影片分类规则,会影响费用和积分计算,影片要支持多种扩展
2、statement太长,功能太多,需要分解重组(放到适合的类中,不要写在一起)
解决思路:
1、先建立可靠的测试环境,一步一测试,避免bug
2、不要一个循环把所有事情都做了,例如在一个循环中进行金额计算和积分计算,可以分成两个循环,抽成两个方法,一个专门算金额,一个专门算积分
3、金额计算和积分计算都与影片相关,不同的影片种类,租赁金额和积分方式不同。每种影片一个类,有个各自的金额和积分,使用多态
4、面对对象编程,不要面对过程编程
5、去除临时变量,变量命名通俗易懂
2、需要重构的迹象
事不过三,三则重构
- 见名知义
- 重复代码
- 提出方法,推入超类,设成模版,或者放入间接层,被调用,总之将重复代码合而为一
- 过长函数
- 通过注释,确定结构和范围,拆分方法(所以要打好注释啊,不然看起来好麻烦)
- 过大的类
- 拆分类,根据功能拆成同级类,也可以拆接口,抽象类,子类
- 过长参数列
- 参数太多,同一对象内的就直接使用对象,方便传输也方便增加参数。没有对象归属的,也可以制造出一个对象
- 发散式变化
- 一个类,能引发他改动的东西太多了,职责太多了。拆分,最好是单一职责。
- 霰弹式修改
- 一种变化引发多个类修改,需要修改的代码散布四处。把一系列相关行为放进同一个类
- 依恋情结
- 某个函数为了计算某个值,从另一个对象那里调用了几乎半打的值,显而易见,要将此函数移到适合的位置去,总之把依恋的部分要抽出来,移动到适合的地方去
- 数据泥团
- 相同的数据字段,可以抽出来,例如很多类都有的id,创建时间等
- 基本类型偏执
- 不愿意创建对自己的问题域有用的对象,如钱、坐标、范围等,而是全部使用基本类型。
- 重复的switch
- 冗余的类,未来可能用到的功能(但实际没能用到)该删就删
- 消息链太长,a.getB().getC().getD()
3、重构手法
(1)重新组织函数
- Extract Method(提炼函数)
- 提出方法,取名知义,就算函数名称比提炼出来的代码还长也无所谓
- Inline Method(内联函数)
- 方法本体太简单,没必要抽象成方法,直接使用吧
- Inline Temp(内联临时变量)
- 临时变量用的少,没必要定义,直接使用a.getB()也行
- Replace Temp with Query(以查询取代临时变量)
- 这样会不会影响性能,1、影响不了多少,2、就算影响了,重构后代码组织良好,方便找到优化方案 3、恢复原来的代码也方便
//案例1
private double getPrice(){
int basePrice = quantity * itemPrice;
double discountFactory;
if (basePrice>1000){
discountFactory=0.95;
}else {
discountFactory=0.98;
}
return basePrice*discountFactory;
}
//用方法代替basePrice
private double getPrice(){
double discountFactory;
if (getBasePrice()>1000){
discountFactory=0.95;
}else {
discountFactory=0.98;
}
return getBasePrice()*discountFactory;
}
private int getBasePrice() {
returnquantity * itemPrice;
}
//用方法代替discountFactory
private double getPrice(){
return getBasePrice()*getDiscountFactory();
}
private double getDiscountFactory() {
if (getBasePrice()>1000){
return 0.95;
}else {
return 0.98;
}
}
private int getBasePrice() {
return quantity * itemPrice;
}
- Introduce Explaining Variable(引入解释性变量)
- 比如if...else...中判断的表达式太长,先变成临时变量
- 比如计算价格不要一个长算式全部算出来了,多用几个临时变量一步步算清楚
- Split Temporary Variable(分解临时变量)
- 临时变量,它既不是循环变量,也不用于收集计算结果,还被多次赋值,说明他有多个作用,每次赋值创建不同的临时变量不行吗,保持单一功能
- Remove Assignments to Parameters(移除对参数的赋值)
- 对方法的传入参数进行操作可以,把参数改变指向另一个对象,有问题
- Replace Method with Method Object(以函数对象取代函数)
//原逻辑
class Account {
private int gamma(int inputVal, int quantity, int yearToDate) {
int importantValue1 = (inputVal * quantity) + delta();
int importantValue2 = (inputVal * quantity) + 100;
if ((yearToDate - importantValue1) > 100) {
importantValue2 -= 20;
}
int importantValue3 = importantValue2 * 7;
return importantValue3 - 2 * importantValue1;
}
private int delta() { return 200; }
}
//以函数对象取代函数
class Gamma{
private final Account account;
private int inputVal;
private int quantity;
private int yearToDate;
private int importantValue1;
private int importantValue2;
private int importantValue3;
public Gamma(Account account, int inputVal, int quantity, int yearToDate) {
this.account = account;
this.inputVal = inputVal;
this.quantity = quantity;
this.yearToDate = yearToDate;
}
int computer(){
importantValue1 = (inputVal * quantity) + account.delta();
importantValue2 = (inputVal * quantity) + 100;
if ((yearToDate - importantValue1) > 100) {
importantValue2 -= 20;
}
importantValue3 = importantValue2 * 7;
return importantValue3 - 2 * importantValue1;
}
}
class Account {
private int gamma(int inputVal, int quantity, int yearToDate) {
return new Gamma(this,inputVal,quantity,yearToDate).computer();
}
private int delta() {
return 200;
}
}
- Substitute Algorithm(替换算法)
- 如果有更优秀的实现方法,那就改
(2)在对象之间搬移特性
决定把责任放在哪
- Move Method(搬移函数)
- Move Field(搬移字段)
- Extract Class(提炼类)
- Inline Class(将类内联化)
- 一个类不再有单独存在的理由,并入另一个类,和提炼类相反
- Hide Delegate(隐藏“委托关系”)
class Person{
//员工所属部门
Department department ;
public Department getDepartment(){
return department;
}
public void setDepartment(Department arg){
department = arg;
}
}
class Department {
//部门经理
private Person manager;
public Department(Person manager){
this.manager = manager;
}
public Person getManager() {
return manager;
}
}
//要想获得员工上级经理
manager = person.getDepartment().getManager();
//隐藏这个关系,在person中增加获取经理的方法
public Person getManager() {
return department.getManager();
}
manager = person.getManager();
- Introduce Foreign Method(引入外加函数)
- 需要给某个类增加一个多次调用的函数,但无法修改这个类,只能外加函数
Date newstart = new Date (previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate()+1);
//给Date一个外加函数
Date newStart = nextDay(previousEnd);
private static Date nextDay(Date arg){
return new Date (arg.getYear(),arg.getMonth(), arg.getDate()+ 1);
}
- Introduce Local Extension(引入本地扩展)
- 要添加额外函数,但不能改原类--》建立原类的子类或包装类去添加额外函数
//包装类举例
class MyDateWrap{
private Date original;
public MyDateWrap(Date arg){
original = arg;
}
public MyDateWrap(String dataString){
original = new Date(dateString);
}
}
(3)重新组织数据
- Self Encapsulate Field(自封装字段)
- privite+设置函数 VS public+直接访问
- Replace Data Value with Object(以对象取代数据值)
- 比如订单类中有客户名称,客户可以单独提出一个对象,方便客户扩展
- Change Value to Reference(将值对象改为引用对象)
- 引用对象:可变的,每个对象都代表真实世界中的一个实物
- 值对象:不可变的,完全由其所含的数据值来定义,可以存在重复的成百上千个。类似日期、钱这样的东西。
- 有时候,从一个简单的值对象开始,其中保存不可修改的数据,而后,希望加入一些可修改的数据--》值对象变为引用对象
//现在一笔订单有一个对应的客户信息
public class Customer {
private String name;
public Customer (String name){ this.name = name; }
public String getName() { return name; }
}
public class Order {
private Customer customer;
public Order(String customerName) {
this.customer = new Customer(customerName);
}
public void setCustomer(String customerName) {
this.customer = new Customer(customerName);
}
public String getCustomerName() {
return customer.getName();
}
}
到目前为止,Customer对象还是值对象。就算多份订单属于同一客户,但每个order对象还是拥有各自的customer对象。
我希望改变这一现状,使得一旦同一客户拥有多份不同订单,代表这些订单的所有order对象就可以共享同一个Customer对象。
这就意味着:每一个客户名称只该对应一个customer对象。--》修改customers的构造方式--》工厂模式
- Change Referenceto Value(将引用对象改为值对象)
- Duplicate Observed Data(复制“被监视数据”)
- 将处理业务逻辑的和处理用户界面的区分开--》观察者模式extends Observable
- Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
//订单Order单向关联Customer
public class Order {
Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
//让customer反向关联,一个用户可以有多份订单
public class Customer {
private Set<Order> orders = new HashSet<>();
Set<Order> friendOrder(){
return orders;
}
}
//让哪个类来控制关联关系,如果是一对多,让1控制,如果是多对多,都可以
//让order来控制关联关系
public class Order {
Customer customer;
public void setCustomer(Customer customer) {
this.customer = customer;
customer.friendOrder().add(this);
}
}
- Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
- Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)
- 不要使用魔法值,定义成常量
- Encapsulate Field(封装字段)
- public封装成private
- Encapsulate Collection(封装集合)
- 同上,对集合搞个设值函数,add,remove之类的,不要直接暴露
- Replace Record with Data Class(以数据类取代记录)
- 创建类用来记一些数据
- Replace Type Code with Class(以类取代类型码)
public class PersonB {
public static final int O=0;
public static final int A=1;
public static final int B=2;
public static final int AB=3;
private int bloodGroup;
public PersonB() {
}
public int getBloodGroup() {
return bloodGroup;
}
public void setBloodGroup(int bloodGroup) {
this.bloodGroup = bloodGroup;
}
}
//以类取代类型码
public class BloodGroup {
public static final BloodGroup O = new BloodGroup(0);
public static final BloodGroup A = new BloodGroup(1);
public static final BloodGroup B = new BloodGroup(2);
public static final BloodGroup AB = new BloodGroup(3);
private static final BloodGroup[] valuse ={O,A,B,AB};
private final int code;
public BloodGroup(int code) {
this.code = code;
}
}
public class PersonB {
private BloodGroup bloodGroup;
public PersonB(BloodGroup bloodGroup) {
this.bloodGroup = bloodGroup;
}
public BloodGroup getBloodGroup() {
return bloodGroup;
}
public void setBloodGroup(BloodGroup bloodGroup) {
this.bloodGroup = bloodGroup;
}
}
- Replace Type Code with Subclasses(以子类取代类型码)
public class Employee {
private int type;
static final int ENGINEER =0;
static final int SALESMAN =1;
static final int MANAGER =2;
//构造工厂
static Employee create(int type){
switch (type){
case ENGINEER:
return new Enginerer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException("111");
}
}
int getType(){
return type;
}
}
- Replace Type Code with State/Strategy(以状态模式或者策略模式取代类型码)
public class Employee {
private int type;
static final int ENGINEER =0;
static final int SALESMAN =1;
static final int MANAGER =2;
int monthlySalary=10;
int comm=2;
int bou=5;
int payAmount(){
switch (type){
case ENGINEER:
return monthlySalary;
case SALESMAN:
return monthlySalary+comm;
case MANAGER:
return monthlySalary+bou;
default:
throw new IllegalArgumentException();
}
}
int getType(){
return type;
}
}
//重构后
public abstract class EmployeeType {
static final int ENGINEER =0;
static final int SALESMAN =1;
static final int MANAGER =2;
abstract int getTypeCode();
public static EmployeeType newType(int code) {
switch (code){
case ENGINEER:
return new Engineer();
case MANAGER:
return new Manager();
case SALESMAN:
return new Salesman();
default:
throw new IllegalArgumentException("code");
}
}
}
public class Engineer extends EmployeeType{
@Override
int getTypeCode() {
return ENGINEER;
}
}
public class Manager extends EmployeeType{
@Override
int getTypeCode() {
return MANAGER;
}
}
public class Salesman extends EmployeeType{
@Override
int getTypeCode() {
return SALESMAN;
}
}
public class Employee {
private EmployeeType type;
int monthlySalary=10;
int comm=2;
int bou=5;
int payAmount(){
switch (getType()){
case EmployeeType.ENGINEER:
return monthlySalary;
case EmployeeType.SALESMAN:
return monthlySalary+comm;
case EmployeeType.MANAGER:
return monthlySalary+bou;
default:
throw new IllegalArgumentException();
}
}
int getType(){
return type.getTypeCode();
}
public void setType(int arg) {
this.type = EmployeeType.newType(arg);
}
}
- Replace Subclass with Fields(以字段取代子类)
- 各个子类的唯一差别只在返回常量函数身上,就没有足够的存在价值,可以挪到父类销毁子类了
public abstract class Person {
abstract boolean isMale();
abstract char getCode();
}
public class Male extends Person {
@Override
boolean isMale() { return true; }
@Override
char getCode() { return 'M'; }
}
public class Female extends Person {
@Override
boolean isMale() { return false; }
@Override
char getCode() { return 'F'; }
}
//删除子类
public class Person1{
private final boolean isMale;
private final char code;
static Person1 createMale(){
return new Person1(true,'M');
}
static Person1 createFemale(){
return new Person1(false,'F');
}
private Person1(boolean isMale, char code) {
this.isMale = isMale;
this.code = code;
}
public boolean isMale() {
return isMale;
}
public char getCode() {
return code;
}
}
(4)简化条件表达式
- Decompose Conditional(分解条件表达式)
- 复杂的条件表达式分解成小块函数
- Consolidate Conditional Expression(合并条件表达式)
if(onVacation()){
if(lengthOfService()>10){
return 1;
}
}
return 0.5;
return (onVacation() && lengthOfService()>10)? 1:0.5;
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- 比如if...else的每个条件都执行了send()方法,那就可以把send()方法合并了提出来
- Remove Control Flag(移除控制标记)
- 比如while循环通过flag标记值的改变跳出循环,那我们可以不使用flag,直接在值改变时,break或者return
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式)
- 就是if..else中某条件能直接返回的就直接返回,不要if...else嵌套下去
- Replace Conditional with Polymorphism(以多态取代条件表达式)
- 就是switch变成多态,建一个抽象父类,几个子类,各有不同实现
- Introduce Nul Object(引入 Null 对象)
public class Site {
private Customer customer;
public Customer getCustomer() {
return customer;
}
}
public class Customer {
private String name;
private BillingPlan plan;
private PaymentHistory history;
public Customer() { }
public String getName() { return name; }
public BillingPlan getPlan() { return plan; }
public PaymentHistory getHistory() { return history; }
}
获取Customer中的一些数据总要判断从Site拿到的Customer是否为空
//建一个空对象
public class NullCustomer extends Customer{
@Override
public boolean isNull() { return true; }
@Override
public String getName() {
return "给个空对象应该对应的默认值";
}
@Override
public BillingPlan getPlan() {
return super.getPlan();
}
@Override
public PaymentHistory getHistory() {
return super.getHistory();
}
}
public class Customer {
private String name;
private BillingPlan plan;
private PaymentHistory history;
public Customer() { }
public static Customer newNull() {
return new NullCustomer();
}
public boolean isNull(){
return false;
}
...
}
public class Site {
private Customer customer;
public Customer getCustomer() {
return (customer==null)? Customer.newNull():customer;
}
}
- Introduce Assertion(引入断言)
- assert
(5)简化函数调用
- Rename Method(函数改名)
- Add Parameter(添加参数)
- Remove Parameter(移除参数)
- 程序员可能经常添加参数,却往往不愿意去掉它们
- Separate Query from Modifier(将查询函数和修改函数分离)
- 某个函数既修改对象状态值,又修改对象状态,就可以分开,一个负责修改,一个负责查询
- Parameterize Method(令函数携带参数)
- 若干函数做了类似的工作,但在函数本体中却包含了不同的值。建议单一函数,以参数表达哪些不同的值。
private Dollars baseCharge(){
double result = Math.min(lastUsage(),100)*0.3;
if (lastUsage()>100){
result += (Math.min(lastUsage(),200)-100)*0.05;
}
if (lastUsage()>200){
result += (lastUsage()-200)*0.07;
}
return new Dollars(result);
}
//抽象建立单一函数,找出重复的规律,不同的值通过传入参数
private Dollars baseCharge(){
double result = usageInRange(0,100)*0.3;
result += usageInRange(100,200)*0.05;
result += usageInRange(200,Integer.MAX_VALUE)*0.07;
return new Dollars(result);
}
private double usageInRange(int start, int end) {
if (lastUsage()>start){
return Math.min(lastUsage(),end)-start;
}else {
return 0;
}
}
- Replace Parameter with Explicit Methods(以明确函数取代参数)
private void setValue(String name,int value){
if ("height".equals(name)){
height=value;
return;
}
if ("weight".equals(name)){
weight=value;
return;
}
}
//优化后
private void setHeight(int arg){
height=arg;
}
private void setWeight(int arg){
weight=arg;
}
- Preserve Whole Object(保持对象完整)
- 将来自同一对象的若干数据作为参数传递给某个函数,改为将整个对象传过去
- Replace Parameter with Methods(以函数取代参数)
- 减少传递参数,如果可以通过方法直接内部拿,那就不要在外部拿了,然后又作为参数传进去
- Introduce Parameter Object(引入参数对象)
- 某些参数总数同时出现,比如开始时间和结束时间,传参的时候可以将这些一同出现的一组数组搞成对象来传
- Remove Setting Method(移除设值函数)
- 如果类中某个字段只在创建时被赋值,后面就不再改变了。那么就不要提供设值函数
- Hide Method(隐藏函数)
- 一些方法的可见度修改,比如取值和设值函数,不用就没必要放出来
- Replace Constructor with Factory Method(以工厂函数取代构造函数)
- Encapsulate Downcast(封装向下转型)
Object lastReading(){
return readings.lastElement();
}
Reading lastReading(){
return (Reading) readings.lastElement();
}
- Replace Error Code with Exception(以异常取代错误码)
private int withdraw(){
if (amount>banlance){
return -1;
}else {
banlance=amount;
return 0;
}
}
private void withdraw(int amount) throws BalanceException {
if (amount>banlance){
throw new BalanceException();
}
banlance = amount;
}
- Replace Exception with Test(以测试取代异常)
- 能先通过判断避免异常发生的,那就先判断,不要滥用异常。比如分母为0,就可以提前判断出来
(6)处理概括关系
- Pull Up Field(字段上移)
- 子类有相同的字段,移至超类
- Pul Up Method(函数上移)
- Pull Up Constructor Body(构造函数本体上移)
- 尽量用到父类的构造函数
- Push Down Method(函数下移)
- Push Down Field(字段下移)
- Extract Subclass(提炼子类)
- 类中某些行为只被一部分实例用到,其他实例用不到,建个子类吧
public class JobItem {
private int unitPrice; //单价
private int quantity; //数量
private Employee employee; //员工
private boolean isLabor; //是否按零件收费
public JobItem(int unitPrice, int quantity, Employee employee, boolean isLabor) {
this.unitPrice = unitPrice;
this.quantity = quantity;
this.employee = employee;
this.isLabor = isLabor;
}
public int getTotalPrice() { return getUnitPrice()*quantity; }
public int getUnitPrice() {
return isLabor? employee.getRate():unitPrice;
}
public int getQuantity() { return quantity; }
public Employee getEmployee() { return employee; }
}
public class Employee {
private int rate;
public Employee(int rate){
this.rate = rate;
}
public int getRate() { return rate; }
}
//优化后
public class JobItem {
private int unitPrice;
private int quantity;
public JobItem(int unitPrice, int quantity) {
this.unitPrice = unitPrice;
this.quantity = quantity;
}
public int getTotalPrice() { return getUnitPrice()*quantity; }
public int getUnitPrice() { return unitPrice; }
public int getQuantity() { return quantity; }
public boolean isLabor() { return false; }
}
public class LaborItem extends JobItem {
private Employee employee;
public LaborItem(int unitPrice, int quantity, Employee employee) {
super(unitPrice, quantity);
this.employee = employee;
}
@Override
public int getUnitPrice() { return employee.getRate(); }
@Override
public boolean isLabor() { return true; }
}
- Extract Superclass(提炼超类)
- 两个类以相同的方式做类似的事,建立超类
- Extract Interface(提炼接口)
- Collapse Hierarchy(折叠继承体系)
- 超类和子类之间无太大区别,将它们合为一体
- Form TemPlate Method(塑造模板函数)
- Replace Inheritance with Delegation(以委托取代继承)
- 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据
- Replace Delegation with inheritance(以继承取代委托)
(7)大型重构
- Tease ApartInheritance(梳理并分解继承体系)
- 某个继承体系同时承担两项责任,建立两个继承体系,并通过委托关系让其中一个可以调用另一个
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Separate Domain from Presentation将(领域和表述/显示分离)
- MVC(模型-视图-控制器)
- 将展示层和实现逻辑分离
- Extract Hierarchy(提炼继承体系)
- 某个类做了太多工作,其中一部分工作是以大量条件表达式完成的。建立继承体系,以一个子类表示一种特殊情况