关于重构的一些思想

简介:

DRY原则:Don't Repeat Yourself (摘自wikipedia


OOA和OOD的作用及其区别 http://blog.sina.com.cn/s/blog_72ed42d401015y5c.html


站在为程序员提供第3方服务SDK的高度来写代码


1.抽象方法和非抽象方法

如果在类中定义了抽象方法,就是强制非抽象子类去实现。这样写的好处就是可以提醒子类需要复

写哪些方法,但是抽象方法不易过多,不需要强制实现的方法不要抽象化。


如果在类中定义了非抽象方法,可以给一个默认的实现,子类可以选择不复写,采用父类默认的实

现,也可以复写自定义实现。

应用场景:

  抽取了一个带ListView的BaseListFragment,后来又有需求,需要一个带头的ListView的

Fragment,那么,就没有必要再写一个类继承BaseListFragement了,况且继承了ListView的初始化已经

完成了,没法再加一个头。

  所以直接在BaseListFragment里定义一个方法返回头视图,默认将方法返回值至为null,在listView

初始化的时候,将方法返回值添加到listview的头部,不为空就添加到头部。


2.关于抽象的方法在哪儿被调的问题

  既然抽了方法,肯定在父类里的某个地方要让其执行。  

  如果父类有自己的生命周期方法,会自动执行一些方法,在这些方法里可以抽出方法。

  如果父类没有自己的生命周期方法,那么调用抽取的方法的父类方法要记得手动去调用。


3.关于怎么抽取的问题

  情景1:抽象适配器,适配器的的抽象方法调用适配器所在父类的方法,父类的这个方法再抽象让

      子类去实现。如我的开源中国带indicator的框架就是这么抽取的。

      这种方式就是实现了方法的抽象和转移,将内部的抽象转移成父类的抽象


4.关于电脑模型

  其实编程和现实是息息相关的,如电脑和各个零部件就是抽取的具体体现,各个零部件相互配合,但

  是又没有焊死在一起,这就是降低了耦合度。


  程序的框架包括界面框架、网络加载框架,框架在抽取的时候应该降低它们之间的依赖性。


    


如果要写出资深的重构代码,必需要“精通”以下的知识:

  1.接口

    情景1:对几个列表按时间字段进行排序,但是几个列表的实体类之间没有任何的继承关系,得到

       时间的方法也不一样,所以对于每一个列表都要定义一个比较器。

       wKiom1bBeaDw_-roAAArrWgmsy0746.png

       我在想,同样比较的是时间,为什么要定义3个比较器呢?

        于是,我定义了一个接口:

       

1
2
3
     public  interface      DateInterface {
          String getDate();
     }

        让每个列表的实体类都去实现这个接口:

      

1
2
3
4
5
6
7
public  class  BidRecordSQL  extends  Entity  implements  DateInterface{
     //...
     @Override
     public  String getDate() {
        return  investTime;
     }
}
1
2
3
4
5
6
7
public  class  BaseDepositsHistoryDomain  extends  Entity  implements  DateInterface{
     //...
     @Override
     public  String getDate() {
        return  investTime;
     }
}

       然后定义一个时间比较器:

    

1
2
3
4
5
6
7
8
9
10
/**
  * 时间比较器-降序排序
  * Created by Zhang on 2016/2/15.
  */
public  class  DescendingDateComparator  implements  Comparator<DateInterface> {
     @Override
     public  int  compare(DateInterface lhs, DateInterface rhs) {
         return  - 1  * lhs.getDate().compareTo(rhs.getDate());
     }
}

      然后,使用Collections工具类对List进行排序:

    wKioL1bBe3uAhr67AAA9eY61VYk650.png   

  使用接口,本质的作用是让原本看似不相干的实体类之间产生了关系。也就是定义相同的行为,getDate, 然后比较器针对接口编程,而不是某个具体的类。

  

  情景2:ViewPager装了多个Fragment,只想在viewpager滑动到哪一页,就更新哪一页的数据。

     一般人的做法就是在每个Fragment定义一个加载数据的方法,然后在onPageChange方法里根据

     position得到对应的Fragment,然后调用fragment的对应方法。

      如果让每一个Fragment实现一个懒加载数据的接口,那么在onPageChange就不需要根据posit       ion去if-else了,直接将fragment强转成接口,调用接口的方法即可。



  2.反射

  情景1:字段过滤器

  

1
2
3
public  interface  IPropertyFilter {
       boolean  apply(Object object, String name, Object value);
}


1
2
3
4
5
6
7
public  interface  IGetFieldMap {
     Map getFieldMap();            //所有字段名,拼出map。
     Map getFieldMap(String ...fieldNames);               //根据字段名,拼出map。
     Map getFieldMapExcept(String ...exceptFieldNames);    //除了指定的几个字段名,拼出map。
     Map getFieldMap(List<String> fieldNames);            //根据字段名,拼出map。
     Map getFieldMap(IPropertyFilter propertyFilter);     //根据过滤器,拼出map。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public  class  BaseRequestBean  implements  IGetFieldMap {
     @Override
     public  Map getFieldMap() {
         return  getFieldMap( new  IPropertyFilter() {
             @Override
             public  boolean  apply(Object object, String name, Object value) {
                 return  true ;
             }
         });
     }
 
     @Override
     public  Map getFieldMap(String... fieldNames) {
         return  getFieldMap(Arrays.asList(fieldNames));
     }
 
     @Override
     public  Map getFieldMapExcept( final  String... exceptFieldNames) {
         return  getFieldMap( new  IPropertyFilter() {
             @Override
             public  boolean  apply(Object object, String name, Object value) {
                 for  (String item : exceptFieldNames){
                     if (name.equals(item)){
                         return  false ;
                     }
                 }
                 return  true ;
             }
         });
     }
 
     @Override
     public  Map getFieldMap(List<String> fieldNames) {
         Map<String, Object> result =  new  HashMap();
         Class mClass = getClass();
         Field[] declaredFields = mClass.getDeclaredFields();
         for  (Field field : declaredFields) {
             String fieldName = field.getName();
             if  (!field.isAccessible()) {
                 field.setAccessible( true );
             }
             try  {
                 Object fieldValue = field.get( this );
                 if  (fieldValue ==  null continue ;
                 if  (!fieldNames.contains(fieldName))  continue ;
 
                 result.put(fieldName, fieldValue);
             catch  (IllegalAccessException e) {
                 e.printStackTrace();
             }
         }
         return  result;
     }
 
     @Override
     public  Map getFieldMap(IPropertyFilter propertyFilter) {
         Map<String, Object> result =  new  HashMap();
         Class mClass = getClass();
         Field[] declaredFields = mClass.getDeclaredFields();
         for  (Field field : declaredFields) {
             String fieldName = field.getName();
             if  (!field.isAccessible()) {
                 field.setAccessible( true );
             }
             try  {
                 Object fieldValue = field.get( this );
                 if  (!propertyFilter.apply( this , fieldName, fieldValue))   continue ;
 
                 result.put(fieldName, fieldValue);
             catch  (IllegalAccessException e) {
                 e.printStackTrace();
             }
         }
         return  result;
     }
 
}

 

  3.注解

  4.泛型

   Java泛型详解

   http://blog.csdn.net/jinuxwu/article/details/6771121

   

   获取泛型的Class  

   http://www.cnblogs.com/onlysun/p/4539472.html

 

 4-5.枚举(优化代码可读性)

   http://blog.csdn.net/lmj623565791/article/details/79278864

 


  5.自定义

   自定义View或者类有多种形式:

   1)完全自定义(复写onDraw 、onLayout)

   2)组合自定义

   3)包裹自定义(在自定义属性绑定需要操作的子View的id,在onFinishInflate方法里find处理相关的逻辑,Google的很多框架级的原生组件用的就是这个)

    如DrawerLayout,抽屉控件等。

   4)工具类中封装view,利用已有的view实现统一的业务接口。

   5)复写Android自定义的类(要求研究源码,然后才能随心所欲的复写哈。)

    实际场景1:使用ArrayAdapter这个类填充Spinner,如果ArrayAdapter的泛型是一个对象的话,最终Spinner显示的是对象的哈希值。而我真正想展示在

          Spinner上的只是ArrayAdapter泛型的某个字段而已。

    最笨的解决方法就是遍历List<T>,得到想要展示的字符串集合List<String>,再将List<String>设置给适配器。但是这样一来的话,在spinner里点击事件

    里往往又会用到List<T>,这样就容易造成混乱。

    


    于是研究了一下ArrayAdapter这个类的源码,看看它的view是如何生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
       public  View getView( int  position, View convertView, ViewGroup parent) {
     return  createViewFromResource(mInflater, position, convertView, parent, mResource);
}
 
private  View createViewFromResource(LayoutInflater inflater,  int  position, View convertView,
         ViewGroup parent,  int  resource) {
     View view;
     TextView text;
 
     if  (convertView ==  null ) {
         view = inflater.inflate(resource, parent,  false );
     else  {
         view = convertView;
     }
 
     try  {
         if  (mFieldId ==  0 ) {
             //  If no custom field is assigned, assume the whole resource is a TextView
             text = (TextView) view;
         else  {
             //  Otherwise, find the TextView field within the layout
             text = (TextView) view.findViewById(mFieldId);
         }
     catch  (ClassCastException e) {
         Log.e( "ArrayAdapter" "You must supply a resource ID for a TextView" );
         throw  new  IllegalStateException(
                 "ArrayAdapter requires the resource ID to be a TextView" , e);
     }
 
     T item = getItem(position);
     if  (item  instanceof  CharSequence) {
         text.setText((CharSequence)item);
     else  {
         text.setText(item.toString());
     }
 
     return  view;
}

   通过源码发现,如果ArrayAdapter的泛型是字符串,那么spinner展示的是字符串;如果ArrayAdapter的泛型是一个对象的话,返回的是这个对象的toString方法的返回值。

   解决方案1:复写ArrayAdapter的getView的相关方法。

      此解决方案的核心是将ArrayAdapter展示Spinner内容部分的具体代码抽象化成方法,从而使ArrayAdapter亦抽象化。

     但是此方式有一个弊端:每有一个泛型类,就得新建一个对应的Adapter类,太浪费资源。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
  * Created by 陈章 on 2017/12/19.
  * 适配器
  */
public  abstract   class  CZArrayAdapter<T>  extends  ArrayAdapter{
     public  CZArrayAdapter(Context context,  List<T> objects) {
         super (context, android.R.layout.simple_spinner_item, objects);
     }
     
     @NonNull
     @Override
     public  View getView( int  position, View convertView, ViewGroup parent) {
         return  createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
     }
 
     @Override
     public  View getDropDownView( int  position, View convertView, ViewGroup parent) {
         return  createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
     }
 
     protected  abstract  String getText(T t);
 
     private  View createViewFromResource(LayoutInflater inflater,  int  position, View convertView,
                                         ViewGroup parent) {
         T item = (T) getItem(position);
         View view;
         TextView text;
 
         if  (convertView ==  null ) {
             view = inflater.inflate(android.R.layout.simple_spinner_item, parent,  false );
         else  {
             view = convertView;
         }
         text = (TextView) view;
         text.setText(getText(item));
         return  view;
     }
 
}

  

 解决方案2:复写ArrayAdapter的getView的泛型类的toString方法

      复写泛型类的toString方法,返回想在spinner上展示的字段。如果泛型类的toString方法没有在其它地方有特殊的引用,这种解决方法是最快最简单的。 


  

  6.其它,肯定有,尚未总结。  

     

怎么解耦的问题:

  1.解耦

    1)View的解耦:一般如详情页面、带有Banner图的页面,里面的View可能会有很多。可以将

     大的View细分为小的View。

     

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  abstract  class  BasePart<T> {
    /**
     * 获取当前模块的View对象
     * @return
     */
    public  abstract  View getView();
    
    /**
     * 处理逻辑和数据
     * @param t
     */
    public  abstract  void  setData(T t);
 
    /**
     * startActivity with bundle
     *
     * @param clazz
     * @param bundle
     */
    protected  void  readyGo(Class<?> clazz, Bundle bundle) {
       Intent intent =  new  Intent(CommonHelper.context(), clazz);
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);     //Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag.
       if  ( null  != bundle) {
          intent.putExtras(bundle);
       }
       CommonHelper.context().startActivity(intent);
    }
}

  只要在activity里initview()的时候,new PartView(),将子View添加到Activity的ContainerView中

 ,请求到数据之后再partView.setData();


  但是PartView是一个很简单的类,如果需要Activity的参数(如调用Activity的方法等),可以在构造函数里传入Activity.



对于别人重构的框架,应该如何去用的问题:

  1.抽象类,肯定是要实现它示实现的方法。写逻辑就是对抽象方法的具体实现。


将一些公共的libs和基类放在依赖库中

  1)出现的问题:将工程的layout下的布局文件写到依赖库里,那么相关的资源color、style看着是

    明明定义了,但就是can't resolve.

  



重构的实例场景

  1)一个网络请求接口(传一个paperid得到试卷的题目)要在程序的多处调用,之前离职的程序员写

   的代码也没有封装:

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
  * 从网络获取ListView的数据
  */
private  void  getListData(String p_id) {
     if  (!NetWorkStatusUtil.isNetworkAvailable(context)) {
         ToastUtil.showShort(context,  "请先链接网络" );
         return ;
     }
     LoadingDialog.showProgress( this "题目正在加载中,请稍等..." true );
     String url = HttpConfig.QUESTION;
     RequestParams params =  new  RequestParams();
     // params.addBodyParameter("token", MD5Encoder.getMD5());
     KLog.e( "试卷页面p_id:"  + p_id);
     params.addQueryStringParameter( "token" , MD5Encoder.getMD5());
     params.addQueryStringParameter( "p_id" , p_id);
     //p_id=2是单选;
     //http://app.haopeixun.org/index.php?g=apps&m=paper&a=getExams&token=75c824f486b5fa5b60330697bdb03842&p_id=8
     //
     HttpUtils httpUtils =  new  HttpUtils();
     httpUtils.send(HttpMethod.GET, url, params,  new  RequestCallBack<String>() {
 
         @Override
         public  void  onSuccess(ResponseInfo<String> responseInfo) {
             jsonString = responseInfo.result;
             Log.i(TAG,  "考题答案: " );
             KLog.json(jsonString);
             if  (!TextUtils.isEmpty(jsonString)) {
                 JSONObject jsonObject;
                 try  {
                     jsonObject =  new  JSONObject(jsonString);
                     int  resultCode = Integer.parseInt(jsonObject.getString( "result" ));
                     KLog.e( "结果码:"  + resultCode);
                     switch  (resultCode) {
                         case  0 :
                             if  (jsonString.isEmpty()) {
                                 showEmptyView();
                                 return ;
                             }
                             parserData(jsonString);
                             break ;
                         default :
                             showEmptyView();
                     }
                 catch  (JSONException e) {
                     showEmptyView();
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 finally  {
                     LoadingDialog.dismissProgressDialog();
                 }
             }
         }
 
         @Override
         public  void  onFailure(HttpException arg0, String arg1) {
             KLog.e(arg1);
             LoadingDialog.dismissProgressDialog();
             ToastUtil.showLong(context,  "请求考题失败" );
         }
     });
}
 
 
private  void  parserData(String jsonString) {
     // ***********给user赋值******************
     qustionItems.clear();
 
     try  {
         JSONObject jsonObject =  new  JSONObject(jsonString);
         JSONArray dataArray = jsonObject.getJSONArray( "data" );
         totalScore = dataArray.length();
         Log.i(TAG,  "总共题目数量:"  + totalScore);
         for  ( int  i =  0 ; i < dataArray.length(); i++) {
             QuestionBean questionBean =  new  QuestionBean();
             JSONObject jsonObject2 = dataArray.getJSONObject(i);
             questionBean.addtime = jsonObject2.getString( "addtime" );
             questionBean.e_analyze = jsonObject2.getString( "e_analyze" );
             questionBean.e_answer_count = jsonObject2.getString( "e_answer_count" );
             // *********
             JSONObject jsonObject3 = jsonObject2.getJSONObject( "e_answer" ); // 题选项对象
             questionBean.eanswer = questionBean. new  Eanswer();
             questionBean.eanswer.setA(jsonObject3.getString( "A" ));
             questionBean.eanswer.setB(jsonObject3.getString( "B" ));
             String answerC = jsonObject3.optString( "C" );
             if  (answerC !=  null  && !TextUtils.isEmpty(answerC)) {
                 questionBean.eanswer.setC(answerC);
             }
 
             String answerD = jsonObject3.optString( "D" );
             if  (answerD !=  null  && !TextUtils.isEmpty(answerD)) {
                 questionBean.eanswer.setD(answerD);
             }
 
             switch  (Integer.parseInt(questionBean.e_answer_count)) {
                 case  5 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     break ;
                 case  6 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     questionBean.eanswer.setF(jsonObject3.getString( "F" ));
                     break ;
                 case  7 :
                     questionBean.eanswer.setE(jsonObject3.getString( "E" ));
                     questionBean.eanswer.setF(jsonObject3.getString( "F" ));
                     questionBean.eanswer.setG(jsonObject3.getString( "G" ));
                     break ;
             }
             // **********
 
             questionBean.e_classid = jsonObject2.getString( "e_classid" );
             questionBean.e_exam_type = jsonObject2.getString( "e_exam_type" );
             questionBean.e_fenzhi = jsonObject2.getString( "e_fenzhi" );
             questionBean.e_good = jsonObject2.getString( "e_good" );
             questionBean.e_id = jsonObject2.getString( "e_id" );
             questionBean.e_probability = jsonObject2.getString( "e_probability" );
             questionBean.e_result = jsonObject2.getString( "e_result" );
             questionBean.e_status = jsonObject2.getString( "e_status" );
             questionBean.e_title = jsonObject2.getString( "e_title" );
             questionBean.e_typeid = jsonObject2.getString( "e_typeid" );
             questionBean.examid = jsonObject2.getString( "examid" );
             questionBean.id = jsonObject2.getString( "id" );
             questionBean.paperid = jsonObject2.getString( "paperid" );
             qustionItems.add(questionBean);
             StringBuilder sb =  new  StringBuilder();
             //sb.append("");
             uAnswerList.add(sb);
         }
     catch  (JSONException e) {
         e.printStackTrace();
     finally  {
         LoadingDialog.dismissProgressDialog();
     }
     showDataView();
     KLog.e( "集合大小"  + qustionItems.size());
     if  (qustionItems.size() >  0 ) {
         tv_total_questions.setText( "1 / 共"  + qustionItems.size() +  "道题" );
     }
     questionPagerAdapter.set(qustionItems, uAnswerList);
     uAnswerStateAdapter.set(getAnswerCard());
}

   同样的接口,我觉得没有必要再往新的activity复制一遍,于是我将这个接口单独封装成一个类

  PaperListRequester中

    wKiom1fY3ayzeppZAATxixA3XCk492.png    在构造PaperListRequester这个类的时候,我将Context和接口需要的参数pid传递进去。定义了

一个监听器ParseListener,监听内部数据解析的结果。通过一个方法getData,并传递一个监听器返回结果。

   这是调用PaperListRequester这个类请求接口的代码:

 wKiom1fY3rugzIqDAAJwJDZPips216.png   将请求、解析接口的逻辑封装在PaperListRequester这个类中,一下代码就看得顺眼了。这也是Google及一些开源库常用的工具类封装手法。

   2)工程中有个ActivityA,作用是取一个数据,但是这个取数据的过程,与activity里的handler相关联。代码量也比较多。这时候有个ActivityB,需要和

     ActivityA重复一样的工作,但是ActivityB本身类里逻辑比较复杂,如果直接把ActivityA里的代码粘贴过来,会严重影响这个类的结构。所以这里    

    就需要新建一个类C.java,用来封装ActivityA的逻辑,通过接口返回数据。在ActivityB里调用C,只管请求和取数据,中间的过程交由C.java去实现。

    实现解耦。

   





      本文转自屠夫章哥  51CTO博客,原文链接:http://blog.51cto.com/4259297/1699714,如需转载请自行联系原作者



相关文章
|
设计模式 算法 Java
设计模式第十五讲:重构 - 改善既有代码的设计(下)
设计模式第十五讲:重构 - 改善既有代码的设计
286 0
|
3月前
|
开发者
软件设计与架构复杂度问题之McCabe圈复杂度的定义如何解决
软件设计与架构复杂度问题之McCabe圈复杂度的定义如何解决
|
3月前
|
程序员
软件设计与架构复杂度问题之战略编程与战术编程的主要区别如何解决
软件设计与架构复杂度问题之战略编程与战术编程的主要区别如何解决
|
设计模式 算法
重构,避免重构误区
重构,避免重构误区
42 0
|
设计模式 Java 测试技术
设计模式第十五讲:重构 - 改善既有代码的设计(上)
设计模式第十五讲:重构 - 改善既有代码的设计
327 0
|
移动开发 网络虚拟化
【五讲四美】之“讲思想”
发挥一点工匠精神,对一个技术组内小运营需求的精进优化过程。
88 0
【五讲四美】之“讲思想”
|
设计模式 前端开发 Java
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
204 0
|
数据处理
《重构2》第六章-重构基础
《重构2》第六章-重构基础
307 0
|
程序员 测试技术
《重构2》第十章-简化条件逻辑
《重构2》第十章-简化条件逻辑
340 0
|
SQL Cloud Native 数据可视化
模块化思想在实践中的应用
各种编程语言中的函数,数据仓库的标签体系,甚至于数据中台的核心理念,都是把模块化的思想发挥到了极致,避免了我们重复造轮子,消除了数据烟囱,用最小的投入获得了最大的产出。
模块化思想在实践中的应用