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:对几个列表按时间字段进行排序,但是几个列表的实体类之间没有任何的继承关系,得到
时间的方法也不一样,所以对于每一个列表都要定义一个比较器。
我在想,同样比较的是时间,为什么要定义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进行排序:
使用接口,本质的作用是让原本看似不相干的实体类之间产生了关系。也就是定义相同的行为,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.泛型
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中
在构造PaperListRequester这个类的时候,我将Context和接口需要的参数pid传递进去。定义了
一个监听器ParseListener,监听内部数据解析的结果。通过一个方法getData,并传递一个监听器返回结果。
这是调用PaperListRequester这个类请求接口的代码:
将请求、解析接口的逻辑封装在PaperListRequester这个类中,一下代码就看得顺眼了。这也是Google及一些开源库常用的工具类封装手法。
2)工程中有个ActivityA,作用是取一个数据,但是这个取数据的过程,与activity里的handler相关联。代码量也比较多。这时候有个ActivityB,需要和
ActivityA重复一样的工作,但是ActivityB本身类里逻辑比较复杂,如果直接把ActivityA里的代码粘贴过来,会严重影响这个类的结构。所以这里
就需要新建一个类C.java,用来封装ActivityA的逻辑,通过接口返回数据。在ActivityB里调用C,只管请求和取数据,中间的过程交由C.java去实现。
实现解耦。