前言:本篇文章主要描述我是如何通过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