一、前言
本系列是设计模式系列,主要结合我工作上,怎么在特定场景来寻找适合的设计模式,这往往比再网上看十来几遍的设计模式概念记得牢很多,毕竟用过了才是自己的;同样给到读者相应的灵感,互相交流学习。
本文主要场景概述:程序接受设备不同业务类型的数据(比如设备注册、设备定位、设备拍照等等)进行业务处理。之前都是解析到不同的业务,然后通过多重if/else,或者是swich来进行业务的筛选,随着业务处理复杂度提升和业务的数量增多,那就会到时接受设备的类越来越冗余。最后通过策略模式来进行每个业务在用相应的业务处理器处理即可。具体的代码实现接下来会详细说明,以提供给读者以后遇到我这种情况,能够cv,也可以自行再优化。
本文主要是分为两大部分,第一部分,讲解策略模式,第二部分,就是我工作遇到的实例场景,还附带代码实现等,精彩内容不容错过:
二、策略模式
用什么东西,一定要对工具要有所理解,才能用起来为所欲为。要是都知道什么是策略模式,可以跳过本部分,直接看第三节。
2.1 策略模式主要是使用场景
一个对象有很多的行为,这些行为需要区分开并进行处理,那么就需要if-else或switch分支判断,这样往往不可控会造成代码冗长。
2.2 策略的定义
策略类的定义相对来说简单,包含一个策略接口和一组实现这个接口的策略类。
public interface Strategy {
void algorithm();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
// 具体算法
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
// 具体算法
}
}
2.3 策略的创建
因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节,可以根据type创建策略的逻辑抽离出来,放到工厂类中。实例代码如下所示:
public class StrategyFactory {
private static final Map<String, Strategy> strategies = new HashMap<>();
static {
strateies.put("A", new ConcreteStrategyA());
strateies.put("B", new ConcreteStrategyB());
}
public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new Exception();
}
return strategires.get(type);
}
}
要是使用if/else来实现多种行为,如下
String type = "??";
if (type.equals("A")) {
// 。。。A算法代码
}else if(type.equals("B")) {
// .....B算法代码
} else if (type.equals("C")) {
// 。。。。C算法代码
}
虽然看起来if/else的方式会比策略模式简单很多,但是你能保证以后会不会有D、E、....Z算法,而且要是各种算法实现起来的代码又很多,我就是再工作上遇到这种业务很多,而且很多业务的处理又很长,要修改或者添加代码,都怕动到其他业务的代码。你们觉得我说的是不是很有道理。
三、工作使用策略场景
正如前言所言,服务端需要接受设备不同业务,比如注册、位置信息、打卡状态等,如下所示:
/**
* 终端心跳
*/
public static final int HEART = 0x0002;
/**
* 位置信息
*/
public static final int POS = 0x0200;
/**
* 上班打卡
*/
public static final int PUNCH_IN = 0x0203;
/**
* 下班打卡
*/
public static final int PUNCH_OUT = 0x0204;
/**
* 定位数据批量上传
*/
public static final int BATCH_POS = 0x0704;
/**
* 点名
*/
public static final int NAMED = 0x8201;
/**
* 文本信息下发
*/
public static final int TEXT_MESSAGE = 0x8300;
协议解析出来,需要对每个业务进行不一样的逻辑处理(算法),那就可以使用策略模式,来让代码更美观(更装)。要是没有使用策略模式,以前的代码:
可以看出,一个方法里面,处理这些业务就已经占了5百多行,严重的一个代码冗长(现在还不优化,经理看到就要找你进小黑屋了)。所以赶紧用策略模式来解决下。
具体的思路如第二节设计:
3.1 定义一个处理业务的策略接口
/**
* @Description: 业务处理器抽象类,使用策略模式
**/
public interface AbstractHandler {
/**
* @Description: 处理方法
* @param jtData: 网络解析的对象存储类
* @return:
**/
public byte[] jtServerHandler(JTDataAnalysis jtData) throws Exception;
}
3.2 各种业务实现该接口
@AbstractHandler.DataUploadAnnotation(dataUploadType = JTMessageIdType.AUTH, desc = "终端鉴权")
public class AuthHandler implements AbstractHandler {
@Override
public byte[] jtServerHandler(JTDataAnalysis jtData) {
// 鉴权处理
}
}
/**
* @Description 心跳处理
**/
@Slf4j
@AbstractHandler.DataUploadAnnotation(dataUploadType = JTMessageIdType.HEART, desc = "终端心跳")
public class HeartHandler implements AbstractHandler {
@Override
public byte[] jtServerHandler(JTDataAnalysis jtData) {
// 心跳处理
}
}
// .......
3.3 把各种业务处理类的策略存储到一个map里面
这里跟上面的有不一样地方,咱们这里使用反射来把类put到Map里面,就不用一个个new对象然后put到map,如图所示:
/**
* 初始化工厂
*/
public static void initTcpJTServerFactory(){
map.put(1, new PosHandler());
map.put(2, new MediaHeadHandler());
map.put(3, new FtpConfigRequestHandler());
map.put(4, new FileUploadStatusNotifyHandler());
map.put(5, new Ftp1026ResultHandler());
map.put(6, new DriverComparisonResultHandler());
map.put(7, new DevFaultReportHandler());
map.put(8, new RetreatSignHandler());
map.put(9, new TaximeterInfoHandler());
map.put(10, new AdasDemarcateHandler());
map.put(11, new VideoCalendarQueryHandler());
map.put(12, new WhiteListHandler());
map.put(13, new OtherDataHandler());
}
这里每个业务对象的创建对应一行,因为设备偶尔会加一个业务,当某天有上百个,那这个初始化类将会有上百行,显然不是很美观。所以这类引入反射,把对应的策略实现对象放入到一个特定包中,用反射+注解生成我们所需要的类,这里使用注解有两方面的作用,第一就是标记策略类,标记@DataUploadAnnotation,就表示可以生成该策略类并加入到缓存中,第二个就是用于存储每个业务的代码标记,再反射生成对应的策略类时,同时获取到对应值,作为缓存map中的key。
这样做的好处就是增加某个设备业务,只需要创建该类并实现对应策略接口,然后标记上对应注解,具体代码实现如下
注解类:
/**
* @Description: 注解类
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataUploadAnnotation {
int dataUploadType(); // 上发类型值
String desc() default "无该描述";
}
策略实现类:
/**
* @Description 心跳处理
* @Date 2022/4/15 15:54
**/
@Slf4j
@AbstractHandler.DataUploadAnnotation(dataUploadType = JTMessageIdType.HEART, desc = "终端心跳")
public class HeartHandler implements AbstractHandler {
然后把全部业务处理类放入到一个包下:
接下来就是把对应策略类加入到缓存,供以后使用:
private Map<Integer, AbstractHandler> tcpServerHandler = new HashMap<>();
List<Class> classList = ClassUtils.getClassList("com.bsj.wcgateway.netty.business808.handler", AbstractHandler.DataUploadAnnotation.class);
for (Class<?> clazz: classList) {
AbstractHandler.DataUploadAnnotation annotation = clazz.getAnnotation(AbstractHandler.DataUploadAnnotation.class);
// 把注解了DataUploadAnnotation的类加入到处理器中
tcpServerHandler.put(annotation.dataUploadType(), (AbstractHandler) clazz.newInstance());
}
/**
* @Description: 根据注解,读取某个包下的所有Class对象
*/
public class ClassUtils {
/**
*
* @param packageName: 包的根目录
* @param annotationClass: 标记了该注解才生成对象返回
* @return
**/
public static List<Class> getClassList(String packageName, Class<? extends Annotation> annotationClass) {
List<Class> classList = getClassList(packageName);
classList.removeIf(next -> !next.isAnnotationPresent(annotationClass));
return classList;
}
public static List<Class> getClassList(String packageName) {
List<Class> classList = new LinkedList<>();
String path = packageName.replace(".", "/");
try {
Enumeration<URL> urls = ClassUtils.getClassLoader().getResources(path);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if (protocol.equals("file")) {
addClass(classList, url.toURI().getPath(), packageName);
} else if (protocol.equals("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
String className = entryName.substring(0, entryName.lastIndexOf(".")).replaceAll("/", ".");
addClass(classList, className);
}
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("Initial class error!");
}
return classList;
}
private static void addClass(List<Class> classList, String packagePath, String packageName) {
try {
File[] files = new File(packagePath).listFiles(file -> (file.isDirectory() || file.getName().endsWith(".class")));
if (files != null)
for (File file : files) {
String fileName = file.getName();
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (packageName != null) {
className = packageName + "." + className;
}
addClass(classList, className);
} else {
String subPackagePath = fileName;
if (packageName != null) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (packageName != null) {
subPackageName = packageName + "." + subPackageName;
}
addClass(classList, subPackagePath, subPackageName);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void addClass(List<Class> classList, String className) {
classList.add(loadClass(className, false));
}
public static Class loadClass(String className, boolean isInitialized) {
try {
return Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
}
3.4 使用
// 查询对应业务的处理器
AbstractHandler businessHandler = tcp809BussinessFactory.getBusinessHandler(jtDataAnalysis.getMessageType());
byte[] responseBody = null; // 应答终端数据
if (!Objects.isNull(businessHandler)) {
responseBody = businessHandler.jtServerHandler(jtDataAnalysis);
}
这就是工作中用到策略模式,希望能给看完的读者们一些灵感。