对Java枚举与静态变量扩展,以及异步回调处理思考

版权声明:本文章原创于 RamboPan ,未经允许,请勿转载。



源码基于 Retrofit 2.6.0

变量扩展

因为最近负责开发几个应用需要加上一个功能:通过访问服务器端,对比服务器端上软件的版本号与当前应用的版本号,如果版本号大于当前应用就进行升级。

既然是要几个应用都需要这个功能,那我们想的是肯定首先做成一个通用型的,每个应用都拷贝一份,然后调用不同的配置就好。

那按照我们常规的思路,就可以这样,虚拟下代码。

静态变量

	//声明一个检查类
	public class UpdateChecker{

	    //应用1
	    public static final String TYPE_APP1 = "xxx1";
	    //应用2
	    public static final String TYPE_APP3 = "xxx3";
	    //应用3
	    public static final String TYPE_APP2 = "xxx2";
	 
	 	//针对不同的升级类型,进行调用
	    public void startCheck(String type){
	        switch (type){
	            
	            case TYPE_APP1:
	                break;
	
	            case TYPE_APP2:
	                break;
	
	            case TYPE_APP3:
	                break;
	
	            default:
	                
	        }
	    }
		……
    }
    

当然此处的的类型也可以换成 int ,我们在查找对应应用是否需要升级时调用对应的类别就行。

这种如果是自己用的话,肯定也是没有问题的,如果要把程序交给别人使用,怎么让别人一眼就看到有哪些值可以进行调用。

那接手的人可以尝试输入 UpdateChecker. 之后,对应编程软件弹出对应的静态属性或者方法提示,从中寻找看着比较像的选项。

更负责任的做法,就是在对应的方法上,加上注释,并且使用 {@link } 进行标注,这样一看注释,就可以有相关链接,跳转到对应的参数,查看对应参数的注释。


	/**
     * 传入不同的应用类型进行对应版本升级检测。
     * 可以使用这些值。
     *
     * {@link #TYPE_APP1} 应用1
     * {@link #TYPE_APP2} 应用2
     * {@link #TYPE_APP3} 应用3
     *
     * @param type 需要检测的类型
     */
    public void startCheck(String type){
        switch (type){

            case TYPE_APP1:
                break;

            case TYPE_APP2:
                break;

            case TYPE_APP3:
                break;

            default:

        }
    }

使用带链接注释很容易让使用者快速找到相关的类,特别是如果此时的 TYPE 是在另一个专用的常量类里,那不进行链接注释,怕是不好找了。

当然此处也可能存在传入值不适当的情况,比如传了一个 abc ,那么肯定是不合适的。


虽然这种事情发生概率极极极小,但是很多 Java 书中仍然推荐使用枚举。因为你只能选那几个,随便输入值是不行的。

枚举

	public class UpdateChecker{

	    public void startCheck(AppType type){
	        switch (type){
	            
	            case TYPE_APP1:
	                break;
	
	            case TYPE_APP2:
	                break;
	
	            case TYPE_APP3:
	                break;
	
	            default:
	                
	        }
	    }
		……
    }
    
	public enum AppType {
		//应用1
		TYPE_APP1,
		//应用2
		TYPE_APP2,
		//应用3
		TYPE_APP1;
	}

其实从这看,也没什么差别,无法从枚举中看出有什么优势,不过如果我们如果需要把某些固定的信息和对应的类型进行绑定时。

如果是用静态变量进行扩展时,那就需要加更多的静态变量,在前缀或者后缀上进行区别,多了还是会有眼花的风险;那如果换成枚举就很简单了。

比如我们需要绑定每个类别的名字和网络地址。


	public enum AppType {
		//需要使用的应用类型
	    //TYPE_APP1
	    TYPE_APP1(AppName.TYPE_APP1,AppUrl.AppUrl1),
	    //TYPE_APP2
	    TYPE_APP2(AppName.TYPE_APP2,AppUrl.AppUrl2),
	    //TYPE_APP3
	    TYPE_APP3(AppName.TYPE_APP3,AppUrl.AppUrl3);

		//需要存储的对应信息
	    private final String name;
	    private final String url;
	    
	    //构造函数中进行配置
	    AppType(String name,Strng url){
	        this.name = name;
	        this.url = url;
	    }

	    String getName(){
	        return name;
	    }
		
		String getUrl(){
			return url;
		}
	
		//名字常量类
	    public static class AppName{
	        public final static String AppType1 = "AppType1";
	        public final static String AppType2 = "AppType2";
	        public final static String AppType3 = "AppType3";
	    }
	    
	    //网址常量类
	    public static class AppUrl{
	        public final static String AppUrl1 = "AppUrl1";
	        public final static String AppUrl2 = "AppUrl2";
	        public final static String AppUrl3 = "AppUrl3";
	    }
	}

这样看起来是不是清爽很多,首先在内部对不同属性定义一个静态类,然后添加不同的常量变量在类中。

在枚举类中声明需要存储的对应 final 变量(因为我们不希望这个值在后期进行变化),构造函数中声明需要配置的一些变量,此处例子就是一个名字和网址。

那我们在声明不同的枚举类型时,加入不同的变量进行配置。

我们 UpdateChecker.startCheck() 时只需要传入一个类型,在需要使用名字的时候使用 type.getName() ,在需要使用网址时,使用 type.getUrl() ,那么就很简单的把各种类型进行绑定。有效减少了填错值的情况。

当然也可以试着用一个普通类来完成这种枚举类的形式,在此就不写了。


请求网络检测的时候使用的 Retrofit ,因为注解看着很华丽,哈哈。

一般处理网络回复分为异步和同步,其实 … 写法最后也差不多。

我们就分析异步的情况。进行请求时,需要将逻辑处理的回调作为参数进行传入。先来看看这个回调。


	public interface Callback<T> {
	 
		void onResponse(Call<T> call, Response<T> response);
	
		void onFailure(Call<T> call, Throwable t);
		
	}
	

onResponse() 方法是拿到回复了,但是不一定是成功的,而且 response.body() 有可能是空,那么我们就拆成三部分,分为请求失败,回复失败,回复成功,我们新建一个类来实现。

封装

	public abstract class BaseCallback<T> implements Callback<T> {
		

	    @Override
	    public void onResponse(Call<T> call, Response<T> response) {
	    	//如果返回成功,并且body 也不为空。那么就算成功,其他算回复失败。
	        if(response.isSuccessful() && response.body() != null){
	            responseSuccess(call, response);
	        }else{
	            responseFail(call, response);
	        }
	    }
	
	    @Override
	    public void onFailure(Call<T> call, Throwable t) {
	        requestFail(call, t);
	    }
	
	    public abstract void responseSuccess(Call<T> call, Response<T> response);
	
	    public abstract void responseFail(Call<T> call, Response<T> response);
	
	    public abstract void requestFail(Call<T> call, Throwable t);
	
	}

看着好像还阔以,虽然只是封装了一小步 …

接下来在使用这个抽象类,肯定需要返回结果进行一些日志的打印,或者是发出对应的事件。比如究竟是没网,还是 json 解析失败。


	private BaseCallback<UpdateKey> updateCallback =  
			new BaseCallback<UpdateKey>() {

                @Override
                public void responseSuccess(Call<UpdateKey> call,
                                            Response<UpdateKey> response) {
                                            
                    ……
                    mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
                }

                @Override
                public void responseFail(Call<UpdateKey> call,
                                         Response<UpdateKey> response) {
                                         
					……
                    mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
                }

                @Override
                public void requestFail(Call<UpdateKey> call,
                                        Throwable t) {
					……
                    mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
                }
            };

	public interface OnUpdateListener{

	    void onUpdateCheck(Result errorCode);
	
	    void onDownloadProgress(int progress);
	    
	}
	

这我们做了一个监听,对请求结果进行回调,此处的 Result 类也是定义的一个枚举类。用来通知不同的请求结果。其实刚开始还行,就定义了几个 Result 类型,然后在 responseSuccess() 对数据进行一些更细的校验时,那么就对应需要返回更多的 Result 类型。

我想到了,如果是给的 Java 源码还好,你可以直接在 Result 类进行扩展,那如果是拿到的库文件,那想加都没办法了,只能选择一个 Result 类型使用。

这样又不能准确的传达出想要表达的信息,这样看来使用枚举在这种情况下还是有一点的缺陷。

那么想来想去,可以把 Result 类型从 enum 换成 interface 类型。


	public interface Result {
	
	    String getMsg();
	
	    Result NO_INTERNET_PERMISSION = new Result() {
	        @Override
	        public String getMsg() {
	            return MSG_NO_INTERNET_PERMISSION;
	        }
	    };
	
	    Result NO_WRITE_FILE_PERMISSION = new Result() {
	        @Override
	        public String getMsg() {
	            return MSG_NO_WRITE_FILE_PERMISSION;
	        }
	    };
	
	    Result CHECK_RESPONSE_JSON_FAIL = new Result() {
	        @Override
	        public String getMsg() {
	            return MSG_CHECK_RESPONSE_JSON_FAIL;
	        }
	    };
	
	    static final String MSG_NO_INTERNET_PERMISSION = "没有获取网络权限";
	    static final String MSG_NO_WRITE_FILE_PERMISSION = "没有获取读写权限";
    	static final String MSG_CHECK_RESPONSE_JSON_FAIL = "JSON 字符解析失败";
	
	}

这样,除了自己定义的一些类型,如果后期打包给别人用,别人使用时,直接 new 一个接口类,并且复写 getMsg() 就可以定义出自己想要的 Result 类。

枚举与接口对比

那什么情况下使用枚举合适,什么时候使用接口合适 ?

我的想法是:如果你是需要封装一个东西,但是又不希望人家进行改动或者扩展时,使用枚举。如果你希望人家可以在此基础上进行扩展,那么就可以使用接口,并且写一些相关的类,给他人参考用。


回调收尾

完成了请求过程中关于信息的传输,我们肯定需要把监听,或者引用的一些其他资源置空,防止内存泄漏。我们可以这么操作 …


	private BaseCallback<UpdateKey> updateCallback =  
			new BaseCallback<UpdateKey>() {

                @Override
                public void responseSuccess(Call<UpdateKey> call,
                                            Response<UpdateKey> response) {
                                            
                    ……
                    mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
                    clear();
                }

                @Override
                public void responseFail(Call<UpdateKey> call,
                                         Response<UpdateKey> response) {
                                         
					……
                    mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
                    clear();
                }

                @Override
                public void requestFail(Call<UpdateKey> call,
                                        Throwable t) {
					……
                    mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
                    clear();
                }
            };
	
	private void clear(){ 
		……
		mListener = null;
	}
	

这样虽然没什么问题,但是一点也不优雅,还要加三个地方,而且以后新的 BaseCallback 都要加,那我们就把这个 clear() 放在基类里。


	public abstract class BaseCallback<T> implements Callback<T> {
		
	    @Override
	    public void onResponse(Call<T> call, Response<T> response) {
	    	//如果返回成功,并且body 也不为空。那么就算成功,其他算回复失败。
	        if(response.isSuccessful() && response.body() != null){
	            responseSuccess(call, response);
	            clear();
	        }else{
	            responseFail(call, response);
	            clear();
	        }
	    }
	
	    @Override
	    public void onFailure(Call<T> call, Throwable t) {
	        requestFail(call, t);
	        clear();
	    }
	    
		……
		
		public abstract void clear();
		
	}
	

那么在 new 之后,我们需要多复写一个 clear() 方法。

现在问题来了,如果按照现在的逻辑,在执行中会不会有什么风险 ?

当然 ! 因为我们 listener 在使用时并没有进行 != null 判断,如果在执行 responseSuccess() , responseFail() , requestFail() 回调时,进行了异步的操作,那么很可能会出现空指针异常

除了在进行非空判断以后,还有一个小的技巧,就是调整三个方法的返回值,让使用者来决定是否需要执行 clear()

此处用 responseSuccess() 进行演示,因为其他情况失败了,一般默认是输出信息,不会进行异步操作,所以暂时就不用那两个方法演示。


	public abstract class BaseCallback<T> implements Callback<T> {

	    @Override
	    public void onResponse(Call<T> call, Response<T> response) {

	        if(response.isSuccessful() && response.body() != null){
	        	//如果返回 true ,证明没有异步操作,直接进行清除。
	            if(responseSuccess(call, response ,this)){
	            	clear();
	            }else{
					//如果返回 false 的话,需要使用者自己找时机清除。
					//可以把此 baseCallback 对象传到异步方法等执行完调用clear()
					//不过稍微有点麻烦.
				}
	        }else{
	            responseFail(call, response);
	            clear();
	        }
	    }
	
	    @Override
	    public void onFailure(Call<T> call, Throwable t) {
	        requestFail(call, t);
	        clear();
	    }
	
		//如果返回 true,表明可以直接调用 clear ,如果返回 false ,则表明剩下还有异步操作。
		//传入 BaseCallback 是方便异步方法中仍然拿到 callback 引用。
	    public abstract boolean responseSuccess(Call<T> call,
	    										Response<T> response,
	    										BaseCallback callback);
		……
	}
	

responseSuccess() 通过返回 true 或者 false ,来决定是否需要 clear() ,如果是同步操作,那么顺序执行就可以执行 clear() ,反之就不执行。

如果其他地方有持有该 BaseCallback 对象时,也可以在异步操作完成后调用 clear() ,而不需要在 responseSuccess() 把此对象进行传递。

至于 responseFail()requestFail() ,我们可以考虑做一个通用的日志输出方法,并且方法为 protected ,那么 new BaseCallback() 时只需要复写一个方法,如果需要更改的话,再进行复写,那么不会看着比较臃肿。

此分析纯属个人见解,如果有不对之处或者欠妥地方,欢迎指出一起讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值