為什么要重視程序的架構(gòu)設(shè)計(jì)
對(duì)程序進(jìn)行架構(gòu)設(shè)計(jì)的原因,歸根結(jié)底是為了提高生產(chǎn)力。通過設(shè)計(jì)是程序模塊化,做到模塊內(nèi)部的高聚合和模塊之間的低耦合(如依賴注入就是低耦合的集中體現(xiàn))。
這樣做的好處是使得程序開發(fā)過程中,開發(fā)人員主需要專注于一點(diǎn),提高程序開發(fā)的效率,并且更容易進(jìn)行后續(xù)的測(cè)試以及定位問題。
但是,設(shè)計(jì)不能違背目的,對(duì)于不同量級(jí)的工程,具體的架構(gòu)實(shí)現(xiàn)方式必然不同,不要為了設(shè)計(jì)而設(shè)計(jì),為了架構(gòu)而架構(gòu)。比如一個(gè)Android app如果只有幾個(gè)Java文件,那只需要做點(diǎn)模塊和層次的劃分就可以了。引入框架或者架構(gòu)增加了工作量,降低了生產(chǎn)力。
所以在開發(fā)的時(shí)候需要考慮:
1)當(dāng)前這個(gè)項(xiàng)目是否需要以快速度上線。比如有些創(chuàng)業(yè)公司,爭(zhēng)取的就是時(shí)間,公司老板是要拿著這個(gè)app是去找投資的。
2)如果這個(gè)項(xiàng)目開發(fā)周期還可以,個(gè)版本就可以把a(bǔ)pp架構(gòu)做好。因?yàn)橐粋€(gè)App肯定是朝著慢慢做大的方向去的,如果等業(yè)務(wù)到了一定程度了,再去重構(gòu)的話,成本就有點(diǎn)大。
什么是MVP?
MVP架構(gòu)由MVC發(fā)展而來。在MVP中,M代表Model,V代表View,P代表Presenter。
Model 負(fù)責(zé)獲取數(shù)據(jù),數(shù)據(jù)的來源可以是網(wǎng)絡(luò)或本地數(shù)據(jù)庫等;
View 負(fù)責(zé)界面數(shù)據(jù)的展示,與用戶進(jìn)行交互;
Presenter 是Model與View之間的通信的橋梁,將Model與View分離開來。
MVP架構(gòu)圖:
所以MVP的架構(gòu)有如下好處:
1)降低了View和Model的耦合,通過Presenter層來通信;
2)把視圖層抽象到View接口,邏輯層抽象到Presenter接口,提高了代碼的可讀性、可維護(hù)性;
3)Activity和Fragment功能變得更加單一,只需要處理View相關(guān)的邏輯;
4)Presenter抽象成接口,就可以有多種實(shí)現(xiàn),方便單元測(cè)試。
下面就來實(shí)際的體驗(yàn)一下MVP在項(xiàng)目中的使用吧!(用戶注冊(cè)和文章詳情兩個(gè)例子)
用戶注冊(cè)
功能界面如下圖所示(注冊(cè)、登錄):
下面是完整的代碼,主要實(shí)現(xiàn)了驗(yàn)證碼注冊(cè)、登錄的功能,可能代碼比較多:
Model部分
public class UserEngine extends BaseEngine { public static final int ID_LOGIN = 1; public static final int ID_REGISTER = 2; private UserApi userApi; private UserEngine(Callback callback, int... ids) { super(callback, ids);
userApi = ApiFactory.createService(UserApi.class, true);
} public static UserEngine getInstance(Callback callback, int... ids) { return new UserEngine(callback, ids);
} /**
* 用戶登錄
*
* @param phone 手機(jī)號(hào)碼
* @param password 密碼
*/ public void login(String phone, String password) { } /**
* 用戶注冊(cè)
*/ public void register(String phone, String password) { }
}
-
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
-
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
View
public interface BaseView { void showToast(String message); void showLoading(); void hideLoading();
} public interface ILoginView extends BaseView { void switchUiByActionType(int actionType); void setUsernameError(String errorMsg); void setCodeError(String errorMsg); void setPasswordError(String errorMsg); void loginSuccess(); void verifySmsCodeSuccess(Object data); void sendSmsCodeSuccess(Object data); void getSupportCountrySuccess(Object data);
} public class UserLoginActivity extends BaseActivity implements ILoginView
, MyCountDownTimer.CountDownCallback { private static final int ACTION_TYPE_LOGIN = 1; private static final int ACTION_TYPE_REGISTER = 2; private LoginPresenterImpl loginPresenter; private MyCountDownTimer countDownTimer; private int actionType; public static void launchForLogin(Context context) {
Intent intent = new Intent(context, UserLoginActivity.class);
intent.putExtra("actionType", ACTION_TYPE_LOGIN);
context.startActivity(intent);
} public static void launchForRegister(Context context) {
Intent intent = new Intent(context, UserLoginActivity.class);
intent.putExtra("actionType", ACTION_TYPE_REGISTER);
context.startActivity(intent);
}
@Override protected void initParams() {
super.initParams(); actionType = getIntent().getIntExtra("actionType", ACTION_TYPE_LOGIN);
loginPresenter = new LoginPresenterImpl(this);
countDownTimer = new MyCountDownTimer(60000, 1000, this);
}
@Override protected void initViews() {
super.initViews();
setTitle("登錄");
binding.btnLoginRegister.setOnClickListener(this);
binding.tvGetCode.setOnClickListener(this);
binding.tvToggleUi.setOnClickListener(this);
loginPresenter.initSMSSDK(this);
loginPresenter.switchUiByActionType(actionType);
}
@Override protected int getLayoutId() { return R.layout.activity_user_login_layout;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override public void onClick(View view) {
super.onClick(view); switch (view.getId()) { case R.id.tv_get_code:
loginPresenter.sendVerificationCode(binding.etUsername.getText().toString()); break; case R.id.btn_login_register: if (actionType == ACTION_TYPE_LOGIN) {
loginPresenter.login(xxx);
} else {
loginPresenter.submitVerificationCode(xxx);
} break; case R.id.tv_toggle_ui: if (VersionUtils.hasKITKAT()) {
TransitionManager.beginDelayedTransition(binding.llContainer);
}
loginPresenter.switchUiByActionType(
actionType == ACTION_TYPE_LOGIN ? ACTION_TYPE_REGISTER : ACTION_TYPE_LOGIN); break;
}
}
@Override public void showToast(String message) {
ToastUtils.showShortToast(this, message);
}
@Override protected void onDestroy() {
super.onDestroy(); if (countDownTimer != null) {
countDownTimer.cancel();
}
loginPresenter.destroy();
}
@Override public void switchUiByActionType(int actionType) { this.actionType = actionType; if (actionType == ACTION_TYPE_REGISTER) {
setTitle("注冊(cè)");
binding.tvToggleUi.setText(R.string.flag_login);
binding.rlPhoneCode.setVisibility(View.VISIBLE);
binding.btnLoginRegister.setText(R.string.btn_register);
} else {
setTitle("登錄");
binding.tvToggleUi.setText(R.string.flag_register);
binding.rlPhoneCode.setVisibility(View.GONE);
binding.btnLoginRegister.setText(R.string.btn_login);
}
}
@Override public void showLoading() {
showLoadingDialog();
}
@Override public void hideLoading() {
hideLoadingDialog();
}
@Override public void setUsernameError(String errorMsg) {
binding.etUsername.requestFocus();
binding.tilUsername.setError(errorMsg);
}
@Override public void setCodeError(String errorMsg) {
binding.etCode.requestFocus();
binding.tilPhoneCode.setError(errorMsg);
}
@Override public void setPasswordError(String errorMsg) {
binding.etPassword.requestFocus();
binding.tilPassword.setError(errorMsg);
}
@Override public void loginSuccess() {
finish();
}
@Override public void verifySmsCodeSuccess(Object data) {
loginPresenter.sendVerificationCode(binding.etCode.getText().toString());
}
@Override public void sendSmsCodeSuccess(Object data) {
binding.tvGetCode.setClickable(false);
countDownTimer.start();
}
@Override public void getSupportCountrySuccess(Object data) { Log.d("SMS", data.toString());
ArrayList> cs = (ArrayList) data; for (HashMap map : cs) {
Log.d("SMS", map.toString()); for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + "-" + entry.getValue());
}
}
}
@Override public void onTimerTick(long millisUntilFinished) {
binding.tvGetCode.setText(String.format(getString(R.string.count_down_timer)
, millisUntilFinished / 1000L));
}
@Override public void onTimerFinish() {
binding.tvGetCode.setClickable(true);
binding.tvGetCode.setText(R.string.get_varification_code);
}
}
-
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
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
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
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
Presenter
public interface BasePresenter { void destroy();
} public interface ILoginPresenter extends BasePresenter{ void switchUiByActionType(int actionType); void onUsernameError(String errorMsg); void onPasswordError(String errorMsg); void onCodeError(String errorMsg); void submitVerificationCode(String phone, String code, String password); void sendVerificationCode(String phone); void login(String username, String password); void register(String username, String password, String code); void loginSuccess(); void registerSuccess(); void loginFailed(String errorMsg); void registerFailed(String errorMsg); void verifySmsCodeSuccess(Object data); void sendSmsCodeSuccess(Object data); void getSupportCountrySuccess(Object data); void smsFailed(int event, Object data); void initSMSSDK(Context context);
} public class LoginPresenterImpl implements ILoginPresenter, BaseEngine.Callback, SMSCallback { private UserEngine userEngine; private ILoginView loginView; private SMSEventHandler smsEventHandler; public LoginPresenterImpl(ILoginView loginView) { this.loginView = loginView;
userEngine = UserEngine.getInstance(this, UserEngine.ID_LOGIN, UserEngine.ID_REGISTER);
} @Override public void initSMSSDK(Context context) {
SMSSDK.initSDK(context, "15da6511b04f5", "ec275402ed1402d13d37132c55ae90c0");
smsEventHandler = new SMSEventHandler(this); SMSSDK.registerEventHandler(smsEventHandler);
} public void switchUiByActionType(int actionType) { if (loginView != null) {
loginView.switchUiByActionType(actionType);
}
} @Override public void onUsernameError(String errorMsg) { if (loginView != null) {
loginView.setUsernameError(errorMsg);
}
} @Override public void onCodeError(String errorMsg) { if (loginView != null) {
loginView.setCodeError(errorMsg);
}
} @Override public void onPasswordError(String errorMsg) { if (loginView != null) {
loginView.setPasswordError(errorMsg);
}
} @Override public void sendVerificationCode(String phone) {
String error; if ((error = checkPhone(phone)) != null) {
onUsernameError(error); return;
} if (loginView != null) {
loginView.showLoading();
}
SMSSDK.getVerificationCode("86", phone.trim());
} @Override public void submitVerificationCode(String phone, String code, String password) {
String error; if ((error = checkPhone(phone)) != null) {
onUsernameError(error); return;
} else if ((error = checkCode(code)) != null) {
onCodeError(error); return;
} else if ((error = checkPassword(password)) != null) {
onPasswordError(error); return;
}
SMSSDK.submitVerificationCode("86", phone.trim(), code.trim());
} @Override public void register(String phone, String password, String code) {
String error; if ((error = checkPhone(phone)) != null) {
onUsernameError(error); return;
} else if ((error = checkCode(code)) != null) {
onCodeError(error); return;
} else if ((error = checkPassword(password)) != null) {
onPasswordError(error); return;
} if (loginView != null) {
loginView.showLoading();
} if (userEngine != null) {
userEngine.register(phone, password);
}
} @Override public void login(String phone, String password) {
String error; if ((error = checkPhone(phone)) != null) {
onUsernameError(error); return;
} if ((error = checkPassword(password)) != null) {
onPasswordError(error); return;
} if (loginView != null) {
loginView.showLoading();
} if (userEngine != null) {
userEngine.login(phone, password);
}
} @Override public void loginSuccess() { if (loginView != null) {
loginView.hideLoading();
loginView.showToast("登錄成功");
loginView.loginSuccess();
}
} @Override public void registerSuccess() { if (loginView != null) {
loginView.hideLoading();
loginView.showToast("登錄成功");
loginView.loginSuccess();
}
} public void loginFailed(String errorMsg) { if (loginView != null) {
loginView.hideLoading();
loginView.showToast(errorMsg);
}
} public void registerFailed(String errorMsg) { if (loginView != null) {
loginView.hideLoading();
loginView.showToast(errorMsg);
}
} @Override public void onSuccess(int id, Object data) { switch (id) { case UserEngine.ID_LOGIN:
loginSuccess(); break; case UserEngine.ID_REGISTER:
registerSuccess(); break;
}
} @Override public void onError(int id, int code, String msg) { switch (id) { case UserEngine.ID_LOGIN:
loginFailed(msg); break; case UserEngine.ID_REGISTER:
registerFailed(msg); break;
}
} /**
* 驗(yàn)證碼校驗(yàn)成功
*/ @Override public void verifySmsCodeSuccess(Object data) { if (loginView != null) {
loginView.hideLoading();
loginView.verifySmsCodeSuccess(data);
}
} /**
* 獲取驗(yàn)證碼成功
*/ @Override public void sendSmsCodeSuccess(Object data) { if (loginView != null) {
loginView.hideLoading();
loginView.showToast("驗(yàn)證碼發(fā)送成功,注意查收");
loginView.sendSmsCodeSuccess(data);
}
} /**
* 返回支持發(fā)送驗(yàn)證碼的國家列表
*/ @Override public void getSupportCountrySuccess(Object data) { if (loginView != null) {
loginView.hideLoading();
loginView.getSupportCountrySuccess(data);
}
} public void smsFailed(int event, Object data) { if (loginView != null) {
loginView.hideLoading();
String error = ((Throwable) data).getMessage(); try {
SmsError smsError = new Gson().fromJson(error, SmsError.class);
error = smsError.getDetail();
} catch (Exception e) {
e.printStackTrace();
}
loginView.showToast(error);
}
} @Override public void smsSuccess(int event, Object data) { if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {
verifySmsCodeSuccess(data);
} else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE) {
sendSmsCodeSuccess(data);
} else if (event == SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES) {
getSupportCountrySuccess(data);
}
} @Override public void smsError(int event, Object data) { if (data != null && (data instanceof Throwable)) {
smsFailed(event, data);
}
} @Override public void destroy() {
loginView = null; if (smsEventHandler != null) {
SMSSDK.unregisterEventHandler(smsEventHandler);
}
} private String checkCode(String code) { if (TextUtils.isEmpty(code)) { return ("請(qǐng)輸入驗(yàn)證碼");
} return null;
} private String checkPhone(String phone) { if (TextUtils.isEmpty(phone)) { return ("請(qǐng)輸入手機(jī)號(hào)");
} if (!StringUtils.isMobilePhone(phone.trim())) { return ("手機(jī)號(hào)格式有誤");
} return null;
} private String checkPassword(String password) { if (TextUtils.isEmpty(password.trim())) { return "密碼不能為空";
} else if (password.length() < 6) { return "密碼必須大于6位";
} return null;
} private static class SMSEventHandler extends EventHandler { SMSCallback callback; public SMSEventHandler(SMSCallback callback) { this.callback = callback;
} @Override public void afterEvent(final int event, int result, final Object data) { if (result == SMSSDK.RESULT_COMPLETE) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() {
callback.smsSuccess(event, data);
}
});
} else { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() {
callback.smsError(event, data);
}
});
((Throwable) data).printStackTrace();
}
}
}
}
-
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
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
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
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
上面的代碼有詳細(xì)的注釋,方法名定義的也比較形象,一看就知道干什么的。
如果明白了MVP架構(gòu)的流程圖,上面的代碼雖然有點(diǎn)長(zhǎng),但是非常很好理解:
Activity 實(shí)現(xiàn)了ILoginView接口,所以Activity就是View 層;
Model層呢,在這里就是請(qǐng)求網(wǎng)絡(luò)。
我們并沒有讓Activity和Model直接交互,而是通過Presenter層來作為溝通橋梁的。
所以Activity里組合了Presenter:
public class UserLoginActivity extends BaseActivity<UserLoginBinding> implements ILoginView , MyCountDownTimer.CountDownCallback { private static final int ACTION_TYPE_LOGIN = 1; private static final int ACTION_TYPE_REGISTER = 2; private ILoginPresenter loginPresenter;
在Presenter中組合了ILoginView
public class LoginPresenterImpl implements ILoginPresenter, BaseEngine.Callback, SMSCallback { private UserEngine userEngine; private ILoginView loginView; }
這樣Presenter和View之間包含了彼此,就可以彼此通信了。這樣就把Activity(View) 和 Model之間實(shí)現(xiàn)了低耦合。
使用MVP架構(gòu)需要注意的地方
1)釋放資源(在用戶離開界面的時(shí)候,記得釋放Presenter的資源)
@Override public void destroy() {
loginView = null;
userEngine.destroy(); if (smsEventHandler != null) {
SMSSDK.unregisterEventHandler(smsEventHandler);
}
}
2)方法命名/有些方法應(yīng)該放在IView接口里還是接口的實(shí)現(xiàn)者Activity里的問題
我也看了其他一些MVP的例子,很多也是基于登錄的例子,比如在登錄成功后,跳到主界面,在IView接口定了 goMain()或goMainActivity() 我覺得這樣是不妥的。
假設(shè)用戶登錄成功后的邏輯變了,不跳到主界面了而是其他的邏輯了,那在ILoginView頂層接口里定義goMain()或goMainActivity()就不合適了,因?yàn)橛貌坏搅耍偛荒茉趃oMain方法里不寫跳到主界面而是寫其他代碼的邏輯吧,這樣方法名和里面的代碼就不匹配了。如果更改方法名,那所以實(shí)現(xiàn)該接口的類都需要改方法名了,耦合又變大了。
我的建議是,既然是登錄成功的邏輯,那就在ILoginView接口里定義loginSuccess()方法,不管以后登錄成功做什么邏輯都可以在loginSuccess()里實(shí)現(xiàn)。而不應(yīng)該直接把業(yè)務(wù)邏輯和方法名綁定死了,并且還把綁定死的方法名放在ILoginView頂層接口里,因?yàn)闃I(yè)務(wù)邏輯是很容易發(fā)生變化的。所以應(yīng)該定義一個(gè)通用的方法名loginSuccess()。
有人說goMain()或goMainActivity(),你看多清晰,一看我就知道是干什么的。但是loginSuccess()不知道做什么邏輯,還得詳細(xì)看loginSuccess()里的代碼。 我覺得考慮的也有一定的道理,但是這不能作為把業(yè)務(wù)邏輯和方法名綁定死了放到頂層接口里的理由。
如果你出于這種考慮也很好解決的,直接在實(shí)現(xiàn)ILoginView的Activity里定義 goMain() 或 goMainActivity() 方法,然后在loginSuccess()里調(diào)用goMain()或goMainActivity()。
3)有些代碼可以放在Activity又可以放到Presenter,該怎么抉擇?
我們?cè)谑褂抿?yàn)證碼注冊(cè)的時(shí)候,當(dāng)用戶點(diǎn)擊獲取驗(yàn)證碼的時(shí)候,開發(fā)者可能就下意識(shí)的就在在Activity中調(diào)用SMSSDK的API來獲取驗(yàn)證碼,然后在Activity中處理獲取成功和失敗的邏輯。
乍一看功能實(shí)現(xiàn)沒問題,我們可以在Activity中調(diào)用SMSSDK的API來獲取驗(yàn)證碼,也可以在Presenter里調(diào)用SMSSDK的API來獲取驗(yàn)證碼。在使用MVP架構(gòu)的時(shí)候我們時(shí)常遇到這樣的抉擇,在哪里寫都可以實(shí)現(xiàn)功能,我們?cè)撛趺淳駬衲兀?
其實(shí)這個(gè)時(shí)候SMSSDK(只不過SMSSDK是第三方提供的API而已)就是充當(dāng)著Model的角色,因?yàn)镾MSSDK就是去請(qǐng)求網(wǎng)絡(luò),然后發(fā)送驗(yàn)證碼的,如果我們直接在Activity(View)中調(diào)用了,那么Model和View又耦合了,所以應(yīng)該放在Presenter中來做。假設(shè)有一天發(fā)送驗(yàn)證碼的SDK我們替換成了其他第三方的SDK了,在View層我們不需要修改一行代碼,只需要修改Presenter就可以了。
通過上面一個(gè)登錄的例子,有些讀者看到接口中這么多方法,瞬間感覺這個(gè)MVP不僅沒有幫我們解決問題,反而是問題的制造者。不是這樣的!假設(shè)我們使用熟悉的MVC開發(fā)的時(shí)候,我們可能是這樣的實(shí)現(xiàn)的:
public class UserLoginActivity extends BaseActivity { private UserEngine userEngine; private void requestLogin() {
showLoadingDialog();
userEngine.login(xxx);
} private void requestRegister() {
showLoadingDialog();
userEngine.register(xxx);
}
@Override public void onClick(View v) { switch (v.getId()) { case R.id.tv_user_login:
requestLogin(); break; case R.id.tv_register:
requestRegister(); break;
}
}
@Override public void onSuccess(int id, Object content) {
super.onSuccess(id, content); switch (id) { case userEngine.ID_LOGIN: break; case userEngine.ID_REGISTER: break;
}
}
@Override public void onError(int id, String msg) {
super.onError(id, msg);
}
}
-
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
-
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
上面的代碼直接Activity中操作Model,耦合變得比較大了。而且隨著業(yè)務(wù)的增多Activity的代碼會(huì)越來越臃腫。
其實(shí)MVP也就是在上面的代碼之上做出一些改良而已,上面我們?cè)赩iew中直接操作Model,現(xiàn)在呢?MVP只不過是View和Model不直接通信了,通過Presenter來通信,Activity(View)操作Presenter,Presenter來操作Model,這樣View和Model實(shí)現(xiàn)了間接的通信了。
聰明的讀者可能又有問題了,分層(View、Presenter)可以啊,但是也不至于每層都設(shè)計(jì)一個(gè)接口吧?那么以接口的形式給我們帶來什么好處了呢?
1)代碼更加清晰
這樣在一定程度上避免了開發(fā)者在一個(gè)方法里有太多的代碼,因MVP強(qiáng)制要求把各個(gè)業(yè)務(wù)都封裝在方法里然后調(diào)用。
2)做好業(yè)務(wù)的頂層設(shè)計(jì)
在開發(fā)的時(shí)候,當(dāng)產(chǎn)品原型出來通過了評(píng)審后,因?yàn)闃I(yè)務(wù)都出來了嘛,開發(fā)這塊就可以把View、Presenter接口的各個(gè)函數(shù)名全部設(shè)計(jì)好,做好頂層設(shè)計(jì),這也倒逼開發(fā)者對(duì)這個(gè)需求這塊一定要有著全面的理解。如果對(duì)業(yè)務(wù)不了解是無法設(shè)計(jì)出接口的。到時(shí)候往方法里填代碼就可以了。
3)解耦
我想大家應(yīng)該注意到了,在View一層我們使用Presenter的時(shí)候都是通過IPresenter接口來定義的,而不是該接口的實(shí)現(xiàn)著。這樣做方便后面替換,這也是面向接口編程的好處。到時(shí)候業(yè)務(wù)邏輯變了,我們只要重新實(shí)現(xiàn)IPresenter接口,然后在View中替換下IPresenter的實(shí)現(xiàn)者即可,而View層不需要修改代碼。
MVP模式實(shí)現(xiàn)文章詳情
如果對(duì)上面的注冊(cè)的例子理解了,實(shí)現(xiàn)這個(gè)功能就非常簡(jiǎn)單了。由于篇幅的原因,具體的代碼細(xì)節(jié)就不帖出來了。把代碼的接口設(shè)計(jì)出來。
該界面主要有加載文章、評(píng)論列表、評(píng)論、分享等功能.
public interface IArticleDetailView extends BaseView{ void onLoadArticleFailed(); void onLoadArticleSuccess(Article article); void onLoadCommentSuccess(Object data); void onLoadCommentFailed(String msg); void addCommentSuccess(Comment comment); void addCommentFailed(String errorMsg); void shareArticle();
} public interface IArticleDetailPresenter extends BasePresenter{ void loadArticle(Long id); void loadArticleSuccess(Article article); void loadArticleFailed(String error); void loadComments(int articleId); void loadCommentSuccess(Object data); void loadCommentFailed(String msg); void addComment(Comment comment); void addCommentSuccess(Comment comment); void addCommentFailed(String errorMsg);
}
-
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
-
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
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。