重构:运用Java反射加多态 “干掉” switch

前言:本篇文章主要描述我是如何通过Java的反射加多态干掉 swtich这个代码的坏味道


目录

  • 代码的坏味道
  • 《重构》曰
  • 遭遇switch
  • 利剑:多态加反射
  • 结束战斗

代码的坏味道

有这么一句话:普通的代码是写给机器看的,优秀的代码是写给人看的,在软件开发的过程中,不知道大家有没有遇到过if-else或switch满天飞,函数长的看都不想看这种情况,先不说软件的扩展性怎么样,这样的代码就算是我们自己写的,回头看时也会发现逻辑很乱,这些都属于“坏味道”,在我们进行开发中,如闻到这股“坏味道”,应该立刻解决,而不是放纵它发展,否则,后面会遇到其他很多的问题,比如需求改了代码不能改?增加新功能?又或者是改自己的代码发现看不懂了?添加注释,不是个好办法,如果我们能够通过其他手段来改善这种情况,能很清晰看清代码的结构和含义并加强它的拓展性,为什么还要大片的注释来解释它呢?
你可能会有问题,我用if-else或者switch,写一个长长的函数多简单,调用开销什么的都没了,这也是我原来的疑问,在开发过程中,代码的高质量才是我们首要追求的,性能不是这时考虑的,二八定理(原谅我又搬出来了- -),你的常常调用的代码也就全部的20%,性能瓶颈常常是在IO等其他方面,而且现在的编译器函数调用开销耗费不了多少性能,我们应该在将来测试时确定性能瓶颈在那里,再进行性能优化,如果瓶颈真的在这里,很简单展开就好。
代码的坏味道可不止这些,《重构》里大约说了近百种,感兴趣可以去看看这本书~


《重构》曰

《重构》里描述过:面向对象程序的一个最明显特征就是:少用switch(或case)语句,从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同地地点。如果要为它添加一个新的case子句,就必须找到所有的switch语句并修改它们,面向对象中的多态概念能可为此带来优雅的解决方案。多态最根本的好处就是:如果你需要根据对象的不同类型而采取不同的行为,多太使你不必编写明显的条件表达式

来看看我的经历!


遭遇 switch

最近和小伙伴写一个项目,客户端发请求,服务端根据请求的不同处理后返回给客户端,我在原先是这么干的,给客户端的每个请求一个整型值当作标记,服务端接受后,用switch根据标记值mark来判断请求的类型,接着对应的case处理,不要嘲笑我- -,相信很多人一开始都是这么干的,这样做的结果是什么?

当客户端再次增添请求时,我有些受不了了,一方面代码写的很长,逼迫我用了很多注释,每增添一个case我都要添加许多注释生怕自己下次看不懂,这样导致代码整体看着很乱,另一方面需求和需求之间耦合性太强,就在一个函数里。
这时代码没有什么面向对象思维,感觉就像c with class

原先的代码(我删掉了逻辑,不需要仔细看

public class ParseJson {
    /* 待解析的json对象 */
    JSONObject js;

    /* 构造函数,获取json */
    ParseJson(JSONObject json) {
        this.js  = json;
    }

    /* 解析json并获取相应的结果 */
    String Parse() throws SQLException {
        /* 结果 */
        String result = "";
        int iret = 0;
        int mark = 0;
        /* 获得mark,创建对应的Executesql对象 */
        try {
            mark = js.getInt("mark");
        }catch (Exception e){
            result = "{\"error\":0, \"status\":\"success\", \"date\":\"2015-08\", " +
                    "\"result\":{\"requestPhoneNum\":\"\", \"IsSuccess\":\"failure\"," +
                    "\"mark\":0, \"ResultINFO\":\"json解析失败,您的输入有误\"}}";
            return result;
        }

        switch (mark){
            /* mark == 1 检测此帐号是否正确且被注册过 */
            case 1:
                break;

            /* mark == 2  帐号注册*/
            case 2:
                break;


            /* mark == 3 忘记密码 注意:应该验证密宝后返回用户一个key值,mark4根据key值来改新密码 */
            case  3:
                break;

            /* mark ==4 用户更新密码 */
            case 4:
                break;

            /* mark ==5 用户更新信息
             * 自己的name,头像等
             * 一般用户点击详细信息会有更新操作 */
            case 5:
                break;


            /* mark ==6
             * 登记新的联系方式
             * 需要修改UserFriend 好友and me 的 is update
             * 每次好友登录需要检查好友的isupdate
             * 看谁换联系方式了 */
            case 6:
                break;

            /* mark == 7
            *  说明本地没有数据,客户端需要发送全部数据 */
            case 7:
                break;

            /* mark == 8
            *  说明本地有数据,只更新发送好友的数据
            *  相当于下拉刷新 */
            /*
            *  先通过account 获得用户 uid
            *  uid 获取 friendId
            *  friendId and uid 获取不为0的 isUpdate数据
            *  分析isUpdate数据,找出标记,组装好友更新的数据,返回
            */
            case 8:
                break;

            /* mark == 9
            *  添加联系人,UserFriend中添加数据,注意要返回好友的信息 */
            case 9:
                break;

            /* mark == 10
            *  生成二维码加好友 */
            case 10:
                break;

            /* mark == 11
            *  生成二维码
            *  客户端生成一张二维码发送给服务器服务器保存 */
            case 11:
                break;

            /* mark ==12
             * 登录*/
            case 12:
                break;
            /* mark == 13
            *  删除联系人 */
            case 13:
                break;
            /* mark == 14
            * 用户发送所有的个人信息,保存到数据库
            * */
            case 14:
                break;
        }
        return result;
    }
}

怎么样,大大的switch加上大片注释,看上去很难受,而且原本的类中ParseJson函数更长,大约1000行!
来看看缺点

客户端请求类型很多,switch所在的函数很长

客户端新增加请求时,必须去改switch,增加case语句

代码重复,请求中总有一些类似的

变量满天飞,变量名词不达意

看看怎么解决


利剑:多态加反射

相信很多人包括我也一样,觉得“多态”是很高贵的词,其实多态的好处就是:如果你需要根据对象的不同类型采取不同的行为,多态使你不必编写明显的条件表达式

改变

首先,我提取出一个公共的基类,因为所有的请求都有类似的地方,比如每个请求都要响应函数。
代码仅用来举例

class request{
    /* 构造函数 */
    public request(int _mark, String _account){
        mark = _mark;
        account = _account;
    }
    /* 响应函数 */
    public String response(){
        ...
    }
    ...
    /* 共有字段 */
    int mark;
    String account;
}

接着我拆分了这个大大的switch,把它每个case语句换成了一个对象,对象继承基类request

class AccountRegister extends request{
    /* 构造函数 */
    public AccountRegister(){}
    /* 覆盖response()函数 */
    public String reponse(){
        ...
    }
    ...
    /* 属于自己的字段 */
    String secret;
    ...
}
class AddFriendByAccount extends request{
    /* 构造函数 */
    public AddFriendByAccount(){}
    /* 属于自己的response()函数 */
    public String reponse(){
        ...
    }
    ...
    /* 属于自己的字段 */
    String FriendId;
    ...
}

还有很多

这样,每种请求变成一个对象,对象名字就是请求内容,非常清晰,不需要要大片的注释,公共的部分在基类,减少了代码重复,扩展性变得很强,客户端要增加新请求?没关系,我新添加一个类继承基类就好

放一张修改后的类
这里写图片描述

每个请求是一个类,是不是清爽了很多?,每个类都不长,根据类名就能清楚的看懂类含义。

代码是清爽了,我怎么用呢?原先根据switch值后case就好了,现在全是对象,我怎么知道客户端的请求对应是哪个对象呢,也就是说没办法选择了。

别急,反射来了!!
不熟悉反射的小伙伴可以了解下 Thinking in Java – 类型信息RTTI
我这篇文章很简单的描述了下。

简单的说就是我可以通过类的名字来实例化类对象,这么棒!!通过名字就可以创建类对象?是的,就是这么简单。
反射例子

/* 基类 */
public class aaa {
    public void func(){
        return;
    }

    public static void main(String[] args) throws Exception {
        /* 反射,通过类的名字 */
        Class c = Class.forName("tryCode.ccc");
        /* 实例化对象 */
        aaa b = (aaa) c.newInstance();
        /* output: bbb */
        b.func();
    }
}

/* 派生类bbb */
public class bbb extends aaa {
    public void func(){
        System.out.println("bbb");
    }
}

/* 派生类bbb */
public class ccc extends aaa {
    public void func(){
        System.out.println("ccc");
    }
}

用反射的结果呢?不再需要魔幻数字1, 2, 3…外加长长的注释来标记这是干什么的,名字很清楚的表达了我们的请求,客户端发来的登录请求不是 1 了,而是LogIn,我们仅需要几行代码就动态创建了客户端请求对应的对象,接着调用对象方法,编译器会根据派生类对象来执行对应的response, 太神奇了!!!

于是我的代码变成了这样

这里写图片描述

除去注释,看看它有几行吧~


结束战斗

呼呼~,战斗结束了,不得不说Java反射和多态真的非常有用,我这里也仅是一个参考,或许大家有更好的方法来改进。也有可能我们根本不需要做这样的改进(也许客户端这辈子不会增加请求了呢?代码写完就交给别人了呢- -这样不对)
但我想要说的是:从开始我们就这样做岂不是更好?别让代码的坏味道弥漫了你整个程序



By XiyouLinuxGroup –wwh

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页