这是我在学习过程中总结的知识
目的是希望日后回来看或者需要用的时候可以 一目了然 # 的回顾、巩固、查缺补漏
不追求详细相当于书本的精简版或者说是导读(想看详细的直接对应翻书),但会尽力保证读者都能快速理解和快速使用(随理解加深会总结的更加精简),但必要时会附上一些较详细解释的链接
脚注是空白的:表示还没弄懂的知识,了解后会添加
文章目录
11.1 为什么要重构
重构的目标
是优化现有代码,也就是说我们要让现有的代码具有更好、更清晰的结构,提升它的可读性、可维护性、可拓展性
11.2 什么时候重构
1.在给软件添加新特性的时候
添加新特性时会去阅读之前的相关代码,如果难以理解就重构成容易理解的模样
2.在调试程序的时候
此时多半是为了代码的更具有可读性。
正是因为思路和代码不够清晰,才可能出现bug,这时候重构也有助于解决bug
3.在别人读你代码的时候
互相交流
11.3常用的重构手法
11.3.1 提取子函数
将大函数中的小函数提取出来
提取子函数又使我们为每个子函数命名,良好的命名会对这个函数又自解释作用
应该提取多长的函数?把界限明显的操作提取出来
11.3.2 上移函数到父类
顾名思义,就是将多个子类的相同或相近功能函数提取到父类中
当参与子类开发的是新人员又不清楚父类的具体实现时就会出现这种情况
11.3.3 下移函数到子类
子类继承父类时会获得父类全部功能,但有时候父类的功能并不是适用于所有子类的。
这时候就可以新建一个父类型单独这些功能让拥有这些功能的子类继承这个新父类
例子
父类Vehicle含有开关门、开车停车的功能函数,但不是所有交通工具都有门的比如:单车
这时候新建一个类Car继承Vehicle,单独实现开关门。然后把Vehicle中的开关门功能给删除
现在就可以让单车直接继承Vehicle,然汽车继承Car。问题解决
11.3.4 封装固定的调用逻辑
当我们要完成某个功能之前必须要执行ABC三个操作,比如获取输入的账号、获取输入的密码、判断成功后登录
我们可以把这三个功能合成为一个“登录”函数,这样在其他地方需要这些功能时只需要调用这个函数而不是重复这3个操作,就可以了
11.3.5 使用泛型去除重复逻辑
情况
一个类族的操作是一致的,只是操作的对象类型不同,这时候就应该使用泛型
泛型提供了操作多种类型的抽象,可以用同一个类的代码操作多种类型的对象
Android JDK的集合类都是泛型类,如List、Set、Map等
例子
创建一个存储int型的集合类,他有get、add、getSize的操作
public class IntArrayList {
private int[] dataSet;
public IntArrayList(int size) {
dataSet = new int[size];
}
public int get(int position) {
return dataSet[position];
}
public void add(int pos, int data) {
dataSet[pos] = data;
}
public int size() {
return dataSet.length;
}
}
再创建一个存储String型的集合类,他有get、add、getSize的操作
public class StringArrayList {
private String[] dataSet;
public StringArrayList(int size) {
dataSet = new String[size];
}
public String get(int position) {
return dataSet[position];
}
public void add(int pos, String data) {
dataSet[pos] = data;
}
public int size() {
return dataSet.length;
}
}
方法是一样的只是操作的类型不同,这时候我们可以创建一个抽象集合类
public class SimpleArrayList<T> {
//使用时只需要传入需要的类型就可以
private T[] dataSet;
public SimpleArrayList(int size) {
dataSet = (T[]) new Object[size];
}
public T get(int position) {
return dataSet[position];
}
public void add(int pos, T data) {
dataSet[pos] = data;
}
public int size() {
return dataSet.length;
}
}
在代码中使用效果
public class Test {
public static void main(String[] args) {
// 使用IntArrayList
IntArrayList intArr = new IntArrayList(2);
intArr.add(0, 3);
intArr.add(1, 55);
System.out.println(intArr.get(1));
// 使用StringArrayList
StringArrayList strArr = new StringArrayList(3);
strArr.add(0, "hello");
System.out.println(strArr.get(0));
// 使用SimpleArrayList存储整型
SimpleArrayList<Integer> intArrayList = new SimpleArrayList<Integer>(3);
intArrayList.add(0, 3);
intArrayList.add(1, 55);
System.out.println(intArrayList.get(1));
// 使用SimpleArrayList存储String
SimpleArrayList<String> strArrayList = new SimpleArrayList<String>(3);
strArrayList.add(0, "hello,SimpleArrayList");
System.out.println(strArrayList.get(0));
}
}
11.3.6 使用对象避免过多的参数
如果在一个函数中含有较多的参数,且部分类型是一样的。
我们可以运用一个对象封装所有这些参数,易于理解所传入的参数
例子
public class Test {
public static void main(String[] args) {
shareToMoment("平凡之路", "我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案", "http://www.xxx.com/images/thumb.png",
"http://www.xxx.com", "Jake");
shareToMoment("平凡之路", "我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案", null,
"http://www.xxx.com", null);
ShareData shareData = new ShareData();
shareData.title = "平凡之路";
shareData.content = "我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案";
shareData.targetUrl = "http://www.xxx.com";
shareToMoment(shareData);
}
//老方法,传入了多个参数
public static void shareToMoment(String title, String content, String thumbUrl,
String targetUrl,
String creator) {
System.out.println("分享到朋友圈: 文章标题为: " + title + ", 内容为 = " + content);
}
//新方法,传入了一个封装好参数的类
public static void shareToMoment(ShareData data) {
System.out.println("分享到朋友圈: 文章标题为: " + data.title + ", 内容为 = " + data.content);
}
}
11.3.7 重构的支柱——转移函数
转移函数是在不破坏原有功能的情况下,将一个函数从一个类移动到另一个类中,是重构理论的支柱
例子
一个班级类存储了所有学生的信息
public class AClass {
public List<Student> students = new ArrayList<Student>();
public void add(Student student) {
students.add(student);
}
}
学生类简单存储了学生的信息
public class Student {
public String id;
public String name;
public Student(String sId, String sName) {
this.id = sId;
this.name = sName;
}
判断一个学生是否属于某个班级
public boolean isBelongTo(AClass class1) {
for (Student stu : class1.students) {
if (stu.id.equals(this.id)) {
return true;
}
}
return false;
}
//@Deprecated
//public boolean isBelongTo(AClass aClass) {
// return aClass.contain(this);
//}
@Override
public String toString() {
return "Student [ id=" + id + ", name=" + name + " ]";
}
}
我们可以发现isBelongTo中核心的操作是遍历AClass中的student集合,这个方法更多的是与AClass进行交互
这时候我们应该把这个函数转移到AClass类中
public class AClass {
public List<Student> students = new ArrayList<Student>();
public void add(Student student) {
students.add(student);
}
public void showStudens() {
for (Student student : students) {
System.out.println("学生信息: " + student);
}
}
public boolean contain(Student student) {
for (Student stu : students) {
if (stu.id.equals(student.id)) {
return true;
}
}
return false;
}
}
这时候就算是转移完成了
关于那些划线的方法是怎么出来的
我们再看Student代码
public Student(String sId, String sName) {
this.id = sId;
this.name = sName;
}
//这个注释表示该函数已经不建议使用
@Deprecated
public boolean isBelongTo(AClass aClass) {
return aClass.contain(this);
}
@Override
public String toString() {
return "Student [ id=" + id + ", name=" + name + " ]";
}
}
如果我们的代码不是对外公布的库,也可以直接选择把isBelongTo方法直接删除。如果保留,就会在方法上有一条划线但仍然是可以使用的
转移复杂的函数很容易出错,请在转移前做好版本控制
11.3.8 将类型码的转为状态模式
当你有一个不可变的类型码,它影响类型的一些行为,这时候可以使用状态模式或者策略模式进行重构。一般遇到switch、if-else这种情况就可以使用,这使用了多态的方法
public abstract class Vehicle {
//类型码
public static final int STOP = 0;
public static final int START = 1;
public static final int DRIVING = 2;
private int mStatus = STOP;
public void setStatus(int status) {
this.mStatus = status;
}
public void start() {
//if-else
if (mStatus == STOP) {
System.out.println("启动车子");
mStatus = START;
}
}
public void stop() {
//switch
switch (mStatus) {
case START:
case DRIVING:
System.out.println("停车");
mStatus = STOP;
break;
}
}
public void speedup() {
if (mStatus != STOP) {
System.out.println("加速前进");
mStatus = DRIVING;
}
}
// 其他代码省略
}
这时,当我们要增加一种状态时,很可能要在每一个函数中修改代码,然后再修改每个状态对应的行为。
创建状态类
public abstract class VehicleState {
public void start() {
}
public void stop() {
}
public void speedup() {
}
// 其他函数
}
再通过多态的手法实现不同状态下的不同行为
public class StartState extends VehicleState {
@Override
public void start() {
System.out.println("启动汽车");
}
@Override
public void speedup() {
System.out.println("加速前进");
}
}
public class StopState extends VehicleState {
@Override
public void start() {
System.out.println("启动汽车");
}
}
public class DrivingState extends StartState {
}
然后修改Vehicle类
public abstract class Vehicle {
// 默认设置为停止状态,之前讲到的子类可以代表父类上场
private VehicleState mStatus = new StopState();
public void setStatus(VehicleState status) {
this.mStatus = status;
}
//停止状态下只有这个方法才有效
public void start() {
mStatus.start();
//启动后设置为启动状态
mStatus = new StartState();
}
public void stop() {
mStatus.stop();
mStatus = new StopState();
}
public void speedup() {
mStatus.speedup();
mStatus = new DrivingState();
}
// 其他代码省略
}
11.3.9 什么也不做的对象——NullObject模式
开发过程中,我们经常在使用某个对象前要对该对象进行判空,不为空才继续操作。为了避免这样的判空检查出现很多次,我们可以使用NullObject模式
NullObject模式就是创建一个什么也不做的对象,作为某些函数的默认返回值
简单来说就是把判空操作提取到上一层去解决,上一层再新建一个类来辅助
public class Config {
private StatisticsAPI mStatisticsAPI;
// 不会改变的Null Object
private static final StatisticsAPI sNotNullApi = new NullStatisticsAPI();
//之前的get方法
// public StatisticsAPI getStatisticsAPI() {
// return mStatisticsAPI;
// }
public void setStatisticsAPI(StatisticsAPI api) {
this.mStatisticsAPI = api;
}
public StatisticsAPI getStatisticsAPI() {
return mStatisticsAPI == null ? sNotNullApi : mStatisticsAPI;
}
//接口
public interface StatisticsAPI {
public void send(String newMsg);
}
//什么也不做的NullObject类
public class NullStatisticsAPI implements StatisticsAPI {
@Override
public void send(String newMsg) {
System.out.println("什么也不发,避免判空而已");
}
}
}
客户端代码
public class NewsApp {
Config mConfig;
public NewsApp(Config config) {
mConfig = config;
}
//被淘汰的方法
// public void userClick() {
// StatisticsAPI statisticsAPI = mConfig.getStatisticsAPI();
// if (statisticsAPI != null) {
// statisticsAPI.send("userClick");
// }
// }
//
// public void readNews() {
// StatisticsAPI statisticsAPI = mConfig.getStatisticsAPI();
// if (statisticsAPI != null) {
// statisticsAPI.send("readNews");
// }
// }
//新方法无需判空,避免出现NullPointException
public void userClick() {
mConfig.getStatisticsAPI().send("userClick");
}
public void readNews() {
mConfig.getStatisticsAPI().send("readNews");
}
// 代码省略
}
11.3.10 使类保持“苗条身材”——分解“胖”类型
解决的方法就是尽量让每个类型都符合单一职责,按照功能去分解
11.4 小结
设计模式为重构提供了目标
掌握常见的重构手法与设计模式是实现可扩展、可维护的软件系统的关键