结合模板设计模式讲讲我对泛型的一些看法

通常,注解,泛型,反射,在普通的开发中, 我们只是在用,但谈及怎么去实现,有些朋友可能还不太清楚,因为这些技术在程序开发的日常中,用不到。

但是这些技术又会给我们的开发带来很多的便利,所以我觉得应该了解一些。
今天就结合着模板设计模式来讲一下泛型在哪用,怎么用,以及哪些要注意的地方。

首先谈一谈我对模板模式的理解。

在一个系列的行为中,有一些是确定的,有一些是不明确的,我们把确定的行为定义在一个抽象类中,不确定的行为定义为抽象方法,由具体的子类去实现,这种不影响整个流程,但可以应对各种情况的方法就可以称之为模板模式。

比如说:我在前面文章提到的通用Adapter的设计,
由adapter的用法可分析到这样一个流程:


  • 要有一组数据源,这个数据源可以确定是一个List但不知道具体的对象是什么

  • getItem()这个只要有了数据源,就可以得到,

  • getCount()只要有了数据源,就可以得到,

  • getView()中,要什么样的view不确定,要怎么样填数据不确定

  • 但是如何复用convertView是可以确定的,这个复用又牵涉到viewholder的问题,要做成通用,

  • 我们没有办法去知道使用者会定义一个什么样的viewholder,所以必须强加一个约束。

对于不明确的对象,我们可以用泛型来代表
对于不明确的行为,我们要用抽象方法来隔离。比如:要什么样的view不确定,要怎么样填数据不确定
对于不确定的viewHolder,我们应该定义一个父类的规则,让子类去遵守,并用泛型去代表变化量

所以代码就成了这样:

public abstract class LBaseAdapter extends BaseAdapter {

    private Context context;
    private List dataSource = new ArrayList<>(); //初始化一个防止getCount()空指针

    public LBaseAdapter(Context context) {
        this.context = context;
    }

    public Context getContext() {
        return context;
    }

    //替换原有数据源
    public void setDataSource(List dataSource) {
        setDataSource(dataSource,true);
    }

    //如果isClear==true,则替换原有数据源,否则加到数据源后面
    public void setDataSource(List dataSource, boolean isClear) {
        if (isClear) this.dataSource.clear();
        this.dataSource = dataSource;
        notifyDataSetChanged();
    }

    //只加一个数据
    public void addData(E data) {
        this.dataSource.add(data);
        notifyDataSetChanged();
    }

    //通过下标移除一条数据
    public void removeData(int position) {
        this.dataSource.remove(position);
        notifyDataSetChanged();
    }

    //通过对象移除一条数据
    public void removeData(E data) {
        this.dataSource.remove(data);
        notifyDataSetChanged();
    }


    @Override
    public int getCount() {
        return this.dataSource.size();
    }


    @Override
    public E getItem(int position) {
        return this.dataSource.get(position);
    }


    @Override
    public long getItemId(int position) {
        return position;
    }


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        V viewHolder = null;
        if (convertView == null) {
            viewHolder = createViewHolder(position, parent);
            if (viewHolder == null || viewHolder.getRootView() == null) {
                throw new NullPointerException("createViewHolder不能返回null或view为null的实例");
            }
            convertView = viewHolder.getRootView();
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (V) convertView.getTag();
        }
        //给当前复用的holder一个正确的position
        viewHolder.setPosition(position);
        bindViewHolder(viewHolder,position,getItem(position));
        return viewHolder.getRootView();
    }

    protected abstract V createViewHolder(int position, ViewGroup parent);

    protected abstract void bindViewHolder(V holder,int position, E data);

    public static class BaseViewHolder {
        private View rootView;
        private SparseArray viewCache = new SparseArray<>();
        private int position = -1;

        public View getRootView() {
            return rootView;
        }

        void setPosition(int position) {
            this.position = position;
        }

        public int getPosition() {
            return position;
        }

        public BaseViewHolder(View rootView) {
            this.rootView = rootView;
        }

        public  R getView(@IdRes int viewID) {
            View cachedView = viewCache.get(viewID);
            if(null == cachedView) {
                cachedView = rootView.findViewById(viewID);
                viewCache.put(viewID, cachedView);
            }
            return (R) cachedView;
        }
    }
}

所有的公用行为以及常用行为都抽到一个父类中,
利用泛型解决了不明确类型的问题,
得用抽象方法解决了不明确行为的问题

这样对于子类来说,它只用关心到自己的逻辑,也就是,需要的实体,要创建的view,以及实体要如何绑定数据,
代码就不帖过来了,因为这篇我要讲的是泛形。

在比如,在安卓开发中, 我们常常要请求接口以获取数据,且通常会定义一个公用的返回格式,举个例子:

{
   "success":true,
   "error":"",
   "data":{
     "xx":"xx
   }
}

{
   "success":true,
   "error":"",
   "data":[{
     "xx":"xx
   }]
}

对于单数据,data会返回一个json对象,对于一个列表数据,data会代表一个json数组,这种情况下,如果把返回json转成一个类实例,应该怎么写呢?不用泛型的话:

//单个对象情况:
public class UserLoginBean {

    public boolean success;
    public String error;
    public User data;
    
    
    public static class User{
        public String userName;
        public String userId;
        public String userNickName;
    }
}
//多个对象情况:
public class ArticleListBean {

    public boolean success;
    public String error;
    public List
data; public static class Article{ public String articleTitle; public String articleContent; public String articleCreateTime; } }

这样写可以,但有这么几个问题

1.对于懒癌晚期的我,不能忍受写这么多重复代码。
2.如果Article有只获取一篇文章的情况,那么又要复制过去再重写一个ArticleBean?
3.如果项目二期,或者后期,经过开会商量返回值里面的error不好,要改个名字,或者加几个字段,
比如一次请求的总条数,那么很悲剧。你要改掉所有的bean.

针对这种情况:

public class BaseRet {
    public boolean success;
    public String error;
    public T data;
}
        String json = "xxxx后台返回的json";
        BaseRet ret = new Gson().fromJson(json, new TypeToken>() {
        }.getType());
        if (ret != null) {
            if (ret.success) {
                User user = ret.data;
                // 填值
            } else {
                String error = ret.error;
                // Toast 显示error
            }
        }

        String json = "xxxx后台返回的json";
        BaseRet> ret = new Gson().fromJson(json, new TypeToken>>() {
        }.getType());
        if (ret != null) {
            if (ret.success) {
                List user = ret.data;
                // 填值
            } else {
                String error = ret.error;
                // Toast 显示error
            }
        }
    }

我为了演示方便,没有写getters和setters,这种写法并不好,应该这样

public class BaseRet {
    public boolean success;
    public String error;
    public T data;

    public boolean isSuccess() {
        return success;
    }
    public String getError() {
        return error;
    }
    public T getData() {
        return data;
    }
}

为什么只提供了getters,这是为了应对前面所说的,如果这个返回格式改了一个名字,我们可以这样应对,
比如:error改成msg

public class BaseRet {
    public boolean success;
    public String msg;
    public T data;

    public boolean isSuccess() {
        return success;
    }

    public String getError() {
        return msg;
    }

    public T getData() {
        return data;
    }

}

如果你之前的代码都用的isSuccess(),getError(),则不需要更改。当然这也是getters存在的意义,如果不存在这种情况,直接用public的属性,不通过方法的活,效率会更好一点。
以上是一点经验之谈,也是简单的模板模式的应用,对于这种情况,变化的是data的类型,用泛型解决就可。

泛型分为声明和使用,两步,
比如一个方法,我们经常遇到TextView txt = (TextView)findViewById(R.id.txt):


    public static  R findView(Activity v, @IdRes int viewId) {
        return (R) v.findViewById(viewId);
    }

//用法
        TextView txt = findView(this, R.id.txt);

        ImageView img = findView(this, R.id.img);

findView方法中,声明了一个泛型 , 并指定返回值为R , 编译器会在编译时检测调用时要的类型,从而把R替换成具体的类型,所以
TextView txt = findView(this, R.id.txt);
这时声明的R就被替换成了TextView,最终的结果就成了
TextView txt = (TextView)activity.findViewById(viewId);

在举一个例子,
我要实现一个把一种数据转成另一种数据的方法,

    public static  List map(List param) {
//声明一个R做为返回值,一个T做为参数,
//但是到这里的时候,我进行不下去了,因为我什么都不知道,对我来说,T,R都是未知的,我也同样不知道要抽什么属性到一个数组里面去,但是我知道怎么去循环param,
             List list = new ArrayList();
             for(T entity : param) {
                    String result = 某种转换之后
                    list.add(result);
             }
             return list;
    }

有了不确定的行为了,就需要抽象来帮忙,这个具体的行为只有使用者结合他的业务才知道,所以我抽象一个接口:
这个接口在类上做了两个声明,一个R做为返回值的代表,一个T做为传入值的代表

    public interface Func {
        public R apply(T t);
    }

于是可以:

    public static  List mapToList(Func func,List t) {
        List list = new ArrayList<>();
        for(T entity : t) {
            list.add(func.apply(entity));
        }
        return list;
    }
    

怎么用呢

        User user = new User("张三",22);
        User user1 = new User("李四",22);
        User user2 = new User("王五",22);
        User user3 = new User("赵六",22);
        List asList = Arrays.asList(user,user1,user2,user3);

        List names = mapToList(new Func() {

            @Override
            public String apply(User t) {
                return t.name;
            }
        },asList);
        
        System.out.println(names);

//lambda
        names = mapToList(it->it.name, asList);
        System.out.println(names);
    
        

[张三, 李四, 王五, 赵六]

剩下的还有边界的问题,还是举adapter的那个例子吧,
我需要一个子类的ViewHolder,但是我又需要子类的ViewHolder能有一个我知道的规范,比如提供view的方法,这样我才能在getView里面setTag做常规的复用逻辑,这种情况下,我需要强制子类继承我定的规范,所以我写了一个BaseViewHolder,但是怎么做约束呢?

public abstract class LBaseAdapter extends BaseAdapter 

这里面写到了 V extends LBaseAdapter.BaseViewHolder , 这是泛型里面做限定的一种写法,意味着 V这个类型必须是BaseViewHolder的子类,如果使用者传了一个不相干的类型,则开发工具会报错。

泛型的另一种好处可能就是运行时类型检查和去除强转吧,这个不说了,用的很多:
List list;
list.add(1) ; //会报错
String str = get(0);//正确
int object = get(0);会报错

java的泛型比如弱,而且简单,是一种基于jvm的伪泛型,所以能做的事情并不多,
但是如果结合了反射,会灵活一些,也麻烦些,这个在后面的文章中我会继续讲述。

如果这篇文章能给你带来新的认识,希望你能帮我顶起来,让更多的人看到
如果我的理解有误,或者用法不当,恳请指出

你可能感兴趣的:(结合模板设计模式讲讲我对泛型的一些看法)