Java 重构 & 设计模式
2022-06-24 本文已影响0人
bowen_wu
重构 Refactor
对软件的内部结构进行的调整
- 不改变软件的对外功能
- 提高可读性和可维护性,降低维护成本
优点
- 提高可读性
- 找到 bug
- 提高后续的开发效率
原则
- 如果没有自动化测试,不要重构
- 重构要小步进行 => 每进行一次重构,运行自动化测试保证没有错误
- 事不过三,三则重构
方法
- 重复代码 => 提炼代码 => Do not Repeat Yourself
- 过长函数(超过50行 || 严重影响可读性和维护效率)
- 代码越小越容易理解
- 代码越小越不容易出错
- 代码越小越容易复用
- 代码越小越容易实现模板方法
- 删除临时变量 => 不要让临时变量在方法中跳来跳去 => 创建函数去返回
- 使用多态取代 if | else | switch
- 多态是虚拟机提供的隐藏的 if | else 功能
- 使用多态 + 策略模式重构 if | else | switch
- 使用枚举 + 方法引用重构 if | else | switch
- 过大的类 => 一个类做了太多的事情 => 降低维护效率 & 破坏封装 => 使用继承或组合
- 明确类的职责,将工作提取到父类或者委托类中
- 越小的类越方便测试、维护和复用
- 缺点:逻辑散落在很多地方
- 太多注释 => 没有注释不是最可怕的,过时的、有误导性的注释才是最可怕的 => 每当你想要写注释时,请先尝试重构,让注释显得多余 => 使用良好的命名代替注释
设计模式
- 人们在长久的生产实践中总结出来的一套方法论
- 提高开发效率
- 降低维护成本
单例模式 Singleton
在整个 JVM 中只存在某个类的一个实例 => 这个实例或对象是全局的,不可变的,唯一的
- static
- lazy init
- 多线程 + 双锁检测
- 枚举
public class MySingleton {
private static MySingleton instance = null;
private MySingleton {
}
public static MySingleton getInstance() {
if (instance == null) { // 多线程并发问题 => 两个线程各自创建一个 => 在单个线程内只有一个实例
instance = new MySingleton();
}
return instance;
}
}
public class World {
// public static final World SINGLETON_INSTANCE = new World();
private static final World SINGLETON_INSTANCE = new World();
public static World getInstance() {
return SINGLETON_INSTANCE;
}
private World() {
}
}
工厂模式 Factory
根据传入的参数动态产生出相应的产品
静态工厂模式
- 使用静态工厂方法代替构造器
-
当使用静态工厂方法时,把构造器私有化 =>
private Cat() {}
=> 封装 => 使用者只能通过使用静态工厂方法进行创建实例,之后构造器内部的细节可以随意更改,只要对外接口不变外界就不需要改动代码
优点
- 有方法名,可以描述方法在做什么
- 可能不需要创造新的对象,可以返回
null
或者之前创造好的对象 => 省时间 + 省内存 - 可以返回当前声明类型的子类型,以提高静态工厂方法的灵活性
- 根据传入的参数动态创建相应的对象
- 静态工厂方法返回的对象可以不存在 => 动态加载 => 运行时动态加载子类 + 加载未来才会编写好的类
缺点
- 没有办法被子类化 => 如果只提供静态工厂方法,那么该类不能被继承 => Java 世界中规定了一个没有
public
|protected
构造方法的类不能被继承 - 静态工厂方法不容易被找到
抽象工厂模式
- 工厂和产品都是接口 => 抽象工厂模式 => 使用一个抽象的工厂来生产抽象的产品
- 优点:使用产品时不需要关心实现,工厂可以动态的通过具体的情况决定提供的某些特定产品的实现
- 实例:MultimapBuilder.build()
建造者模式 Builder
- 当一个类里面有很多属性时,此时实例化的时候需要传递很多参数,即使使用抽象工厂模式,也需要创建很多方法
- 使用 IDEA 的 Builder Generator plugin,一键创建 builder,通过静态工厂方法创建实例,通过
withX
(X 为属性名) 进行链式调用(return this
),有选择性的设置属性,最后通过.build()
结束。通过有名字的方法清楚地告知使用者在设置哪个属性 - 隐藏创建对象的建造过程和细节
- 仍可以直接创建复杂的对象
- 实例:HttpClientBuilder | MultimapBuilder
// 使用 builder 的静态工厂方法
person = PersonBuilder.createPerson()
.withFirstName("")
.withLastName("")
.withAddress("")
.build();
代理模式 Proxy
- 为其他对象提供一种代理以控制对这个对象的访问
- 当调用一个接口的方法时,JDK 使用动态代理,将这个接口的方法转发给其中的代理 => MyBatis
- java.lang.reflect.Proxy
- 实例:RPC 框架 => Remote Procedure call => 远程过程调用 => dubbo
适配器模式 Adapter
- 作为两个不兼容的接口之间的桥梁
- 实例:Spock JUnitFilterAdapter
- org.apache.ibatis.logging.Log.java
装饰器模式 Decorator
- 动态的为一个对象增加功能,但是不改变其结构 => 不需要改之前类的任何代码
- 向一个现有的对象添加新的功能,同时又不改变其结构
- 实例:Collections.synchronizedList
- org.apache.ibatis.executor.CachingExecutor.java => Cache + delegate(委托 | 代表)
// DataService interface
// DateServiceImpl class
// Main.java
// LogDecorator class
// CacheDecorator class
适合场景
- 面向接口
- 每个 Decorator 都需要写重复的代码 => AOP 解决这个问题
享元模式 Flyweight
- 使用共享对象减少创建对象的数量 => 减少内存占用和提供性能
- 实例:String.intern()
String a = new String('a').intern(); String b = new String('a').intern(); String c = new String('a').intern(); System.out.pringln(a == b); // true System.out.pringln(a == c); // true
组合模式
- 用于把一组相似的对象当做一个单一的对象
- 实例:JUnit TestSuite | TestCase
模板模式
- 提供一个模板,具体实现可以覆盖模板的全部或者部分 => 父类提供一些模板方法的实现,子类通过覆盖某些模板方法从而实现丰富多彩的功能 => 面向对象之继承
- 一个抽象类公开定义了执行它的方法的模板
- 子类可以按需求重写方法实现
- 实例:AbstractApplicationContext.refresh()
// 模板方法
public class BookWriter {
public void writeBook() {
writeTitle();
writeContent();
writeEnding();
}
public void writeTitle() {
System.out.println("标题");
}
public void writeContent() {
System.out.println("内容");
}
public void writeEnding() {
System.out.println("谢谢大家");
}
}
// 实现 => 多态
public class MyBookWriter extends BookWriter {
public static void main(String[] args) {
new MyBookWriter().writeBook();
}
@Override
public void writeTitle() {
// 如果想在模板的 writeTitle 方法中添加自己的方法
super.writeTitle();
System.out.println("My Title!");
}
// 覆盖模板的部分 => writeEnding
@Override
public void writeEnding() {
// 如果想在模板的 writeEnding 方法中添加自己的方法
System.out.println("Thank everyone!");
}
}
策略模式
- 一个类的行为或其算法可以在运行时更改
- 实例:线程池 RejectedExecutionHandle
class User {
private boolean vip;
public boolean isVip() {
return vip;
}
}
class PriceCalculator {
public int calculate(DiscountStrategy discountStrategy, int price, User user) throws IllegalAccessException {
return discountStrategy.discount(price, user);
// switch (strategy) {
// case "NoDiscount":
// return price;
// case "95":
// return (int) (price * 0.95);
// case "VIP":
// if (user.isVip()) {
// return (int) (price * 0.85);
// }
// return (int) (price * 0.95);
// // ...
// default:
// throw new IllegalAccessException();
// }
}
}
class DiscountStrategy {
public int discount(int price, User user) {
return price;
}
}
class NoDiscountStrategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
return price;
}
}
class Discount95Strategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
return (int) (price * 0.95);
}
}
class VipDiscountStrategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
if (user.isVip()) {
return (int) (price * 0.85);
}
return (int) (price * 0.95);
}
}
门面模式 Facade
知识点
- 《重构改善既有代码的设计》Martin Fowler
- TODO: search 多线程 + 双锁检测 单例模式