表驱动法在Android下的一个实现

一、上下文
1.1 我为什么要修改我的代码
    这是一个我正在做的项目中遇到的问题:在应用中每个模块会有一个用于处理网络请求的类,类中会有多个请求方法,如“我的”模块中就会有消息列表、消息状态修改为已读、全部消息置为已读、清空消息列表、上传头像、获取分享信息等请求。这时就要在类中对不同的请求进行分发,之前的代码是这样的:
public class MineBusiness extends BaseBusiness {
    public static final String MINE_MESSAGE = "mine_message";
    public static final String MINE_MESSAGE_READ = "mine_message_read";
    ...
    public void setParams(Object[] params) {
        this.params = params;
    }
    public void doBusiness() {
        String type = params[0];
        switch(type) {
            case MINE_MESSAGE:
                mineMessage();
                break;
            case MIME_MESSAGE_READ:
                mineMessageRead();
                break;
            ...
            default:break;
        }
    }
    private void mineMessage() {
        // 消息列表的请求
    }
    private void mineMessageRead() {
        // 消息置为已读的请求
    }
}
这样的处理对于业务来说并没有任何问题,但是,维护却变得更为艰难,逻辑不够清晰,更重要的是,这并不符合开闭原则:如果在“我的”模块中需要加一个请求,比如消息收藏,除了直接去修改代码以为没有任何办法。
Martin Fowler在《重构 改善既有代码的设计》中说:
面向对象程序的一个最明显特征就是:少用switch(或case)语句。从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同地点。如果要为它添加一个新的case语句,就必须找到所有switch语句并修改它们。面向对象中的多态概念可为此带来优雅的解决办法。
1.2 我为什么要写这段文字
    要替换掉switch-case或if-else语句,首先想到的是表驱动法。但是查找了许多资料,说明中的表驱动法都仅仅是“点到为止”,介绍一个不同月份的天数的demo或者打印不同的几个String。想要真正移植到项目中还是困难度多。
    恰好这个版本任务不重,于是我尝试着实现了一个表驱动的逻辑处理。一则为了自己做下笔记,二则也为有同样需求的兄弟们提供一个参考,开始动手写这个文章。

二、关于表

最初一个版本的实现,是使用一个map来存储整个表:
private static final HashMap<String, BaseBusiness> map = new HashMap<>();
static {
    map.put(MINE_MESSAGE, new MineMessage());
    map.put(MINE_MESSAGE_READ, new MineMessageRead());
}
public void doBusiness(String type, Object[] params) {
    if (map.containsKey(type)) {
        map.get(type).doBusiness(params);
    }
}

直接在map中存进要执行的请求的对象,调用时传递type来指定具体的业务类并传递相关参数就可以了。但是这样就使得每一种请求都需要一个单独的类,而且,可能登陆、注册这样的方法用户可能基本用不到,也加载到内存中了。如果我想要扩展一种方法,还是要到这一个分发的类中来给map添加一个键值对。

    有没有一种能够存储键值对的资源呢?我想到了Java中的Properties资源。

#系统相关
system=SystemBusiness
system_register=register
system_login=login
#我的相关
mine=MineBusiness
mine_message=mineMessages
mine_message_read=mineMessageRead
    Properties继承自HashTable<Object, Object>,调用了load(InputStream)后就能得到一个键值对的资源。

三、通过表实现不同业务
这里使用一个枚举来与Properties相关联:
public enum Mode {
    SYSTEM("system"), SYSTEM_REGISTER("system_register"), SYSTEM_LOGIN("system_login"),
    MINE("mine"), MINE_MESSAGE("mine_message"), MINE_MESSAGE_READ("mine_message_read");
    private String value;
    Mode(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
}
使用时,就通过枚举的值来指定表中的key,从而获取到相对应的业务类名和需要调用的方法名:
public void setParameters(Mode moduleMode, Mode methodMode, Object params) {
    this.moduleMode = moduleMode;
    this.methodMode = methodMode;
    this.params = params;
}
最后通过反射获取到具体的Method对象,调用对应的业务方法。
public void dispense() {
    if (moduleMode != null && methodMode != null) {
        if (properties != null && properties.containsKey(methodMode.getValue())) {
            try {
                BaseBusiness business;
                Method method;
                //1.若缓存中不存在对应的key,则需要反射出指定对象,然后存入缓存中
                if (!moduleClasses.containsKey(moduleMode.getValue())) {
                    //2. 从表中获取到指定的类名
                    String className = properties.getProperty(moduleMode.getValue());
                    //3. 拼接上包名
                    Class<?> businessClazz = null;
                    for (String prefix : sClassPrefixList) {
                        try {
                            businessClazz = Class.forName(prefix + className);
                            break;
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    //4. 获取指定对象
                    if (businessClazz != null) {
                        Constructor<?> constructor = businessClazz.getConstructor();
                        business = (BaseBusiness) constructor.newInstance();
                        moduleClasses.put(moduleMode.getValue(), business);
                    }
                }
                business = moduleClasses.get(moduleMode.getValue());
                business.initializeParameters();
                    
                //5. 若method缓存中不存在指定Method,则需要反射出指定Method对象,然后存入缓存
                if (!methodClasses.containsKey(methodMode.getValue())) {
                    Class<? extends BaseBusiness> methodClazz = business.getClass();
                    method = methodClazz.getDeclaredMethod(properties.getProperty(methodMode.getValue()), Object[].class);
                    methodClasses.put(methodMode.getValue(), method);
                }
                method = methodClasses.get(methodMode.getValue());
                method.invoke(business, params);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}
使用时,只要获取到用于分发发Dispense类对象,设置参数后调用dispense()方法即可:
//调用注册方法
Object[] params = {Mode.SYSTEM_REGISTER};//具体请求参数
dispense.setParameters(Mode.SYSTEM, Mode.SYSTEM_REGISTER, params);
dispense.dispense();
//调用登录方法
params = new Object[]{Mode.SYSTEM_LOGIN};//具体请求参数
dispense.setParameters(Mode.SYSTEM, Mode.SYSTEM_LOGIN, params);
dispense.dispense();
//调用消息列表方法
params = new Object[]{Mode.MINE_MESSAGE};//具体请求参数
dispense.setParameters(Mode.MINE, Mode.MINE_MESSAGE, params);
dispense.dispense();
//调用消息置为已读方法
params = new Object[]{Mode.MINE_MESSAGE_READ};//具体请求参数
dispense.setParameters(Mode.MINE, Mode.MINE_MESSAGE_READ, params);
dispense.dispense();

需要注意的是:

1.枚举中的值必须与properties文件中定义的key相同。

2.properties的value必须和业务类的类名/方法名相同。

具体代码可访问github/table-driven





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值