【架构设计】01-重构代码
【架构设计】01-重构代码
Last edited 2022-9-28
date
Sep 23, 2022
type
Post
status
Published
slug
framework-restructure-design-1
summary
概要:1 通用架构设计;2 重构技巧;3 设计模式运用;
tags
架构设计
重构技巧
代码实战
category
技术分享
password
Property
Sep 28, 2022 07:00 AM
icon
notion image

前言:重构是什么?

重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
本段说明摘抄自 重构(术语) - 百度百科
废话不多说,所以重构技能是包含了设计模式、架构设计等多方面内容。下面我将用 美滋滋汽修公司 场景和示例代码,来给大家分享一次重构的过程,祝大家早日踏上架构师之列。

一、小美的美滋滋汽修公司

小美在北京昌平区有一家汽车修理公司,名字叫 美滋滋汽修,公司的核心功能是赚钱(修车) 😂
@Slf4j public class MazeAutoRepair { //小美会修车 private static final Worker WORKER = new Worker(); /** * 核心修车方法,都是钱,美滋滋 * * @param ctx 上下文 * @param car 车 */ public void makeMoney(CarRepairContext ctx, Car car) { if (ctx == null || car == null) { log.warn("裤子都脱了,你不修了!!!气人。。。"); return; } log.info("接单啦!汽车牌子:{}, 汽车:{}", car.getClass().getName(), car); if (car instanceof BmwCar) { BmwCar bm = (BmwCar) car; //检查宝马车情况 WORKER.checkBmwStatus(bm); WORKER.doRepair(); WORKER.finish(); } else if (car instanceof BenzCar) { BenzCar bm = (BenzCar) car; //检查奔驰车情况 WORKER.checkBenzStatus(bm); WORKER.doRepair(); WORKER.finish(); } else { log.warn("抱歉,该车型:【{}】我们公司修不了!", car.getClass().getName()); return; } log.info("恭喜进账100元!当前订单信息:car:{}, 修车信息:{}", car.getClass().getName(), ctx.toString()); } }

二、重构业务代码

有一天,来了一辆劳斯莱斯,可惜小美没有修劳斯莱斯的技能,money就这样擦肩而过!小美很不爽,所以决定学习修理其他各种牌子汽车的技能;

1 增加劳斯莱斯修车业务

//判断如果是劳斯莱斯车辆 else if (car instanceof RollsRoyce) { RollsRoyce royce = (RollsRoyce) car; //检查劳斯莱斯车情况 WORKER.checkRoyceStatus(royce); WORKER.doRepair(); WORKER.finish(); }

2 继续优化架构:

修车过程都是相似的,可以抽成通用的方法,合理利用Idea的重构功能,如下图:
notion image
notion image
//优化成私有方法,私有方法有很多共同点,都是修车逻辑 public void makeMoney(CarRepairContext ctx, Car car) { if (ctx == null || car == null) { log.warn("裤子都脱了,你不修了!!!气人。。。"); return; } log.info("接单啦!汽车牌子:{}, 汽车:{}", car.getClass().getName(), car); if (car instanceof BmwCar) { repairBmw(ctx, (BmwCar) car); } else if (car instanceof BenzCar) { repairBenz(ctx, (BenzCar) car); } else if (car instanceof RollsRoyce) { repairRolls(ctx, (RollsRoyce) car); } else { log.warn("抱歉,该车型:【{}】我们公司修不了!", car.getClass().getName()); return; } log.info("恭喜进账100元!当前订单信息:car:{}, 修车信息:{}", car.getClass().getName(), ctx.toString()); } private void repairRolls(CarRepairContext ctx, RollsRoyce car) { RollsRoyce royce = car; WORKER.checkRoyceStatus(royce); WORKER.doRepair(); WORKER.finish(); System.out.println(ctx.getInfo()); } private void repairBenz(CarRepairContext ctx, BenzCar car) { BenzCar bm = car; WORKER.checkBenzStatus(bm); WORKER.doRepair(); WORKER.finish(); System.out.println(ctx.getInfo()); } private void repairBmw(CarRepairContext ctx, BmwCar car) { BmwCar bm = car; WORKER.checkBmwStatus(bm); WORKER.doRepair(); WORKER.finish(); System.out.println(ctx.getInfo()); }

3 招修车师傅

小美打算扩招,招聘修各种牌子车辆的师傅,自己做管理;专人干专事儿,每个师傅只修一个牌子的车,来业务后,根据车的牌子安排对应的师傅;
public void makeMoney(CarRepairContext ctx, Car car) { if (ctx == null || car == null) { log.warn("裤子都脱了,你不修了!!!气人。。。"); return; } log.info("接单啦!汽车牌子:{}, 汽车:{}", car.getClass().getName(), car); if (car instanceof BmwCar) { new BmwWorker().repair(ctx, (BmwCar) car); } else if (car instanceof BenzCar) { new BenzWorker().repair(ctx, (BenzCar) car); } else if (car instanceof RollsRoyce) { new RollsRoyceWorker().repair(ctx, (RollsRoyce) car); } else { log.warn("抱歉,该车型:【{}】我们公司修不了!", car.getClass().getName()); return; } log.info("恭喜进账100元!当前订单信息:car:{}, 修车信息:{}", car.getClass().getName(), ctx.toString()); }
//奔驰修车师傅,其他牌子同理只负责修对应牌子的车 public class BenzWorker { /** * 修理奔驰车 * * @param ctx 汽车信息 * @param car 洗车 */ public void repair(CarRepairContext ctx, BenzCar car) { } }

4 招聘职业经理

美滋滋汽修厂越做越大,小美忙不过来,所以打算招聘职业经理,帮自己管理。
/** * 修车规范 * * @version : 1.0 */ public interface IWorker<C extends Car> { /** * 修车 * * @param ctx ctx * @param car car */ void repair(CarRepairContext ctx, C car); } /** * 奔驰修理师傅 * * @version : 1.0 */ public class BenzWorker implements IWorker<BenzCar> { public void repair(CarRepairContext ctx, BenzCar car) { } } /** * 宝马修理师傅 * * @version : 1.0 */ public class BmwWorker implements IWorker<BmwCar>{ public void repair(CarRepairContext ctx, BmwCar car) { } } /** * 劳斯莱斯修理师傅 * * @version : 1.0 */ public class RollsRoyceWorker implements IWorker<RollsRoyce> { public void repair(CarRepairContext ctx, RollsRoyce car) { } }
职业经理管理修车师傅
/** * 工厂类 * */ @Slf4j public final class WorkerFactory { private WorkerFactory() { } public static IWorker<? extends Car> create(Car car) { if (car instanceof BmwCar) { return new BmwWorker(); } else if (car instanceof BenzCar) { return new BenzWorker(); } else if (car instanceof RollsRoyce) { return new RollsRoyceWorker(); } else { log.warn("抱歉,该车型:【{}】我们公司修不了!", car.getClass().getName()); return null; } } }

5 核心修车逻辑重构

/** * 核心修车入扣,都是钱,美滋滋 * * @param ctx 上下文 * @param car 车种类 */ public void makeMoney(CarRepairContext ctx, Car obj) { if (ctx == null || obj == null) { log.warn("裤子都脱了,你不修了!!!气人。。。"); return; } log.info("接单啦!汽车牌子:{}, 汽车:{}", obj.getClass().getName(), obj); IWorker<? extends Car> iWorker = WorkerFactory.create(obj); if (iWorker == null) { return; } //iWorker.repair(ctx, obj); iWorker.repair(ctx, cast(obj)); log.info("恭喜进账100元!当前订单信息:car:{}, 修车信息:{}", obj.getClass().getName(), ctx.toString()); } //需要注意的是,worker.repair(ctx, car);里的car类型需要做一下处理; /** * 欺骗编译器的小技能。注意jdk版本,最好配成1.8及以上,默认的1.5编译不通过 */ private <C extends Car> C cast(Car car) { if (car==null){ return null; } return (C)car; }

6 职业经理工作优化

工厂里if判断看着还是不好看,使用map做缓存
@Slf4j public final class WorkerFactory { private static final Map<Class<?>, IWorker<? extends Car>> MAP = new HashMap<>(); //初始化工厂MAP static { MAP.put(BmwCar.class, new BmwWorker()); MAP.put(BenzCar.class, new BenzWorker()); MAP.put(RollsRoyce.class, new RollsRoyceWorker()); } private WorkerFactory() { } /** * 工厂create方法 */ public static IWorker<? extends Car> create(Class<?> clazz) { return MAP.get(clazz); } } //小美对接工厂的核心代码 IWorker<? extends Car> iWorker = WorkerFactory.create(obj.getClass());

三、反射优化

职业经理新招一个新牌子的汽车维修师傅,需要主动去工厂报备一下,才能正常工作。小美希望继续优化工作:

1 首先介绍一个包工具类

主要功能:可以获取指定包路径下接口的所有子类实现。
PackageUtil
package com.yi.demo.restructure.util; import java.io.File; import java.io.FileInputStream; import java.net.URL; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; /** * 名称空间实用工具 */ public final class PackageUtil { /** * 类默认构造器 */ private PackageUtil() { } /** * 列表指定包中的所有子类 * * @param packageName 包名称 * @param recursive 是否递归查找 * @param superClazz 父类的类型 * @return 子类集合 */ static public Set<Class<?>> listSubClazz( String packageName, boolean recursive, Class<?> superClazz) { if (superClazz == null) { return Collections.emptySet(); } else { return listClazz(packageName, recursive, superClazz::isAssignableFrom); } } /** * 列表指定包中的所有类 * * @param packageName 包名称 * @param recursive 是否递归查找? * @param filter 过滤器 * @return 符合条件的类集合 */ static public Set<Class<?>> listClazz( String packageName, boolean recursive, IClazzFilter filter) { if (packageName == null || packageName.isEmpty()) { return null; } // 将点转换成斜杠 final String packagePath = packageName.replace('.', '/'); // 获取类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 结果集合 Set<Class<?>> resultSet = new HashSet<>(); try { // 获取 URL 枚举 Enumeration<URL> urlEnum = cl.getResources(packagePath); while (urlEnum.hasMoreElements()) { // 获取当前 URL URL currUrl = urlEnum.nextElement(); // 获取协议文本 final String protocol = currUrl.getProtocol(); // 定义临时集合 Set<Class<?>> tmpSet = null; if ("FILE".equalsIgnoreCase(protocol)) { // 从文件系统中加载类 tmpSet = listClazzFromDir( new File(currUrl.getFile()), packageName, recursive, filter ); } else if ("JAR".equalsIgnoreCase(protocol)) { // 获取文件字符串 String fileStr = currUrl.getFile(); if (fileStr.startsWith("file:")) { // 如果是以 "file:" 开头的, // 则去除这个开头 fileStr = fileStr.substring(5); } if (fileStr.lastIndexOf('!') > 0) { // 如果有 '!' 字符, // 则截断 '!' 字符之后的所有字符 fileStr = fileStr.substring(0, fileStr.lastIndexOf('!')); } // 从 JAR 文件中加载类 tmpSet = listClazzFromJar( new File(fileStr), packageName, recursive, filter ); } if (tmpSet != null) { // 如果类集合不为空, // 则添加到结果中 resultSet.addAll(tmpSet); } } } catch (Exception ex) { // 抛出异常! throw new RuntimeException(ex); } return resultSet; } /** * 从目录中获取类列表 * * @param dirFile 目录 * @param packageName 包名称 * @param recursive 是否递归查询子包 * @param filter 类过滤器 * @return 符合条件的类集合 */ static private Set<Class<?>> listClazzFromDir( final File dirFile, final String packageName, final boolean recursive, IClazzFilter filter) { if (!dirFile.exists() || !dirFile.isDirectory()) { // 如果参数对象为空, // 则直接退出! return null; } // 获取子文件列表 File[] subFileArr = dirFile.listFiles(); if (subFileArr == null || subFileArr.length <= 0) { return null; } // 文件队列, 将子文件列表添加到队列 Queue<File> fileQ = new LinkedList<>(Arrays.asList(subFileArr)); // 结果对象 Set<Class<?>> resultSet = new HashSet<>(); while (!fileQ.isEmpty()) { // 从队列中获取文件 File currFile = fileQ.poll(); if (currFile.isDirectory() && recursive) { // 如果当前文件是目录, // 并且是执行递归操作时, // 获取子文件列表 subFileArr = currFile.listFiles(); if (subFileArr != null && subFileArr.length > 0) { // 添加文件到队列 fileQ.addAll(Arrays.asList(subFileArr)); } continue; } if (!currFile.isFile() || !currFile.getName().endsWith(".class")) { // 如果当前文件不是文件, // 或者文件名不是以 .class 结尾, // 则直接跳过 continue; } // 类名称 String clazzName; // 设置类名称 clazzName = currFile.getAbsolutePath(); // 清除最后的 .class 结尾 clazzName = clazzName.substring(dirFile.getAbsolutePath().length(), clazzName.lastIndexOf('.')); // 转换目录斜杠 clazzName = clazzName.replace('\\', '/'); // 清除开头的 / clazzName = trimLeft(clazzName, "/"); // 将所有的 / 修改为 . clazzName = join(clazzName.split("/"), "."); // 包名 + 类名 clazzName = packageName + "." + clazzName; try { // 加载类定义 Class<?> clazzObj = Class.forName(clazzName); if (null != filter && !filter.accept(clazzObj)) { // 如果过滤器不为空, // 且过滤器不接受当前类, // 则直接跳过! continue; } // 添加类定义到集合 resultSet.add(clazzObj); } catch (Exception ex) { // 抛出异常 throw new RuntimeException(ex); } } return resultSet; } /** * 从 .jar 文件中获取类列表 * * @param jarFilePath .jar 文件路径 * @param packageName 包名称 * @param recursive 是否递归查询子包 * @param filter 类过滤器 * @return 符合条件的类集合 */ static private Set<Class<?>> listClazzFromJar( final File jarFilePath, final String packageName, final boolean recursive, IClazzFilter filter) { if (jarFilePath == null || jarFilePath.isDirectory()) { // 如果参数对象为空, // 则直接退出! return null; } // 结果对象 Set<Class<?>> resultSet = new HashSet<>(); try { // 创建 .jar 文件读入流 JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath)); // 进入点 JarEntry entry; while ((entry = jarIn.getNextJarEntry()) != null) { if (entry.isDirectory()) { continue; } // 获取进入点名称 String entryName = entry.getName(); if (!entryName.endsWith(".class")) { // 如果不是以 .class 结尾, // 则说明不是 JAVA 类文件, 直接跳过! continue; } // // 如果没有开启递归模式, // 那么就需要判断当前 .class 文件是否在指定目录下? // // 获取目录名称 String tmpStr = entryName.substring(0, entryName.lastIndexOf('/')); // 将目录中的 "/" 全部替换成 "." tmpStr = join(tmpStr.split("/"), "."); if (!recursive) { if (!packageName.equals(tmpStr)) { // 如果不是我们要找的包, continue; } } else { if (!tmpStr.startsWith(packageName)) { // 如果不是子包, continue; } } String clazzName; // 清除最后的 .class 结尾 clazzName = entryName.substring(0, entryName.lastIndexOf('.')); // 将所有的 / 修改为 . clazzName = join(clazzName.split("/"), "."); // 加载类定义 Class<?> clazzObj = Class.forName(clazzName); if (null != filter && !filter.accept(clazzObj)) { // 如果过滤器不为空, // 且过滤器不接受当前类, // 则直接跳过! continue; } // 添加类定义到集合 resultSet.add(clazzObj); } // 关闭 jar 输入流 jarIn.close(); } catch (Exception ex) { // 抛出异常 throw new RuntimeException(ex); } return resultSet; } /** * 类名称过滤器 * * @author hjj2019 */ @FunctionalInterface static public interface IClazzFilter { /** * 是否接受当前类? * * @param clazz 被筛选的类 * @return 是否符合条件 */ boolean accept(Class<?> clazz); } /** * 使用连接符连接字符串数组 * * @param strArr 字符串数组 * @param conn 连接符 * @return 连接后的字符串 */ static private String join(String[] strArr, String conn) { if (null == strArr || strArr.length <= 0) { return ""; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < strArr.length; i++) { if (i > 0) { // 添加连接符 sb.append(conn); } // 添加字符串 sb.append(strArr[i]); } return sb.toString(); } /** * 清除源字符串左边的字符串 * * @param src 原字符串 * @param trimStr 需要被清除的字符串 * @return 清除后的字符串 */ static private String trimLeft(String src, String trimStr) { if (null == src || src.isEmpty()) { return ""; } if (null == trimStr || trimStr.isEmpty()) { return src; } if (src.equals(trimStr)) { return ""; } while (src.startsWith(trimStr)) { src = src.substring(trimStr.length()); } return src; } }
 

2 使用反射初始化工厂的MAP

public static void init() { //获取包名称 String name = WorkerFactory.class.getPackage().getName(); //获取所有IWorker的子类 Set<Class<?>> classSet = PackageUtil.listSubClazz(name, true, IWorker.class); for (Class<?> clazz : classSet) { if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0) { // 如果是抽象类, continue; } Class<?> carType = null; Method[] methodArray = clazz.getDeclaredMethods(); for (Method method : methodArray) { if (method == null || !method.getName().equals("repair")) { continue; } //获取参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); //不成立条件判断:1 参数个数小于2;2 第二个参数是Car.class; 3 第二个参数不是Car.class类型 if (parameterTypes.length < 2 || parameterTypes[1] == Car.class || !Car.class.isAssignableFrom(parameterTypes[1])) { continue; } //找到实现类 carType = parameterTypes[1]; break; } if (carType == null) { continue; } try { //创建修车师傅管理者 IWorker<?> newWorker = (IWorker<?>) clazz.newInstance(); MAP.put(carType, newWorker); log.info("关联修车师傅成功:{} <===> {}", carType.getName(), clazz.getName()); } catch (Exception e) { log.error(e.getMessage(), e); } } log.info("完成工厂MAP初始化"); } //从此,来新师傅,不需要主动去工厂报备了,工厂开工自动扫描所有师傅,并完成初始化。

3 公司扩展业务简单化

只需要添加对应的car和worker即可,其他的交给框架处理,如下图:
notion image
新增新业务类即可
/** * 特斯拉修车师傅 * */ public class TeslaWorker implements IWorker<TeslaCar>{ @Override public void repair(CarRepairContext ctx, TeslaCar car) { //维修特斯拉汽车.. } } //引进特斯拉车配件 /** * 特斯拉汽车 */ @Data public class TeslaCar extends Car{ private String tsl; }
 
从此,小美的公司生意越来越好,越来越美滋滋! 不过小美对于架构优化不想止步于此,在酝酿自己的终极武器:反射之终极实战-字节码编程(javassist)
感谢阅读,我们下期见! 😊
 
  • 架构设计
  • 重构技巧
  • 代码实战
  • 【代码这么写】Tree-两行代码返回一颗树【商城实战】12-进阶篇-秒杀服务