玖叶教程网

前端编程开发入门

设计模式(一):策略模式+反射+注解

一、前言

本系列是设计模式系列,主要结合我工作上,怎么在特定场景来寻找适合的设计模式,这往往比再网上看十来几遍的设计模式概念记得牢很多,毕竟用过了才是自己的;同样给到读者相应的灵感,互相交流学习。

本文主要场景概述:程序接受设备不同业务类型的数据(比如设备注册、设备定位、设备拍照等等)进行业务处理。之前都是解析到不同的业务,然后通过多重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);
}

这就是工作中用到策略模式,希望能给看完的读者们一些灵感。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言