大年初三,实在无聊,想着前段时间写的 MVC 还有很多问题,所以重新写一遍,看看能不能查漏补缺。
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
上面那句话是从别的地方复制过来的,感觉国内每篇写 MVC 的文章中都会写上面那一句,但实际上你看完上面那段话,还是一脸懵逼。我看看能不能通俗的解释一下。
我们在日常的开发当中,不管是 WEB 开发还是移动开发,不管是网站还是 APP 都是由若干个页面组成的,每个页面有着不同的功能,这些页面组成了我们的网站或者 APP。
不管是网页还是 APP 页面,功能都可以大致归纳为:收集用户操作和向用户展示信息。就拿人们常举的注册功能的例子来说,用户输入用户名和密码,然后页面向服务器提交、服务器做响应的验证该用户是否存在并且返回结果,页面根据服务器返回的结果来向用户展示不同的界面。
想象一下,如果我们的页面是一个十分复杂的界面,上面功能有很多,逻辑也有很多,我们不管三七二十一将其实现了,然后随着时间的推移,业务逻辑也发生改变,我们再去原来的页面当中去寻找响应的地方去修改就会比较费事儿,因为你不知道哪一段代码究竟负责什么功能,是不是在其他地方有调用,你修改一处代码导致其他功能不能正常使用了,也就是牵一发而动全身。
显然这种情况发生过很多次,广大程序员们也深受其苦,于是逐渐的探索出一条道路,他们通过观察发现,不管是网站页面还是 APP 页面,都可以大致的归纳为接受用户的数据,经过相应的处理,再反馈给用户:
这就是所谓的 MVC,视图部分就是 View 层,逻辑部分就是 M 层,控制部分就是 C 层。
MVC 说白了,是一种编程技巧,或者说是一种思想,我们在这种思想的指导下去编写我们的代码,让它变得更清晰,更易读,同时也更加便于后期维护。
使用 Java WEB 开发举一个例子,JSP 就是我们的 View 层,Servlet 就是我们的 C 层,而 JavaBean 就是我们的 Model 层。
现在思想有了,但是该怎么将思想运用到实际当中去呢?
看了一些文章,也是玄玄乎乎的一大堆理论,然后给一个例子,似乎并不能很透彻的解释 MVC 的用法,我来试一试。
我们先来一个不使用 MVC 的例子:
我们要实现一个功能,输入城市名,然后点击按钮获取到该地的天气状况。API 使用的是 和风天气,因为是免费的哈哈。
先看界面:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_below="@id/edit"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_below="@id/button"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
就是简单的输入框来获取用户需要查询的城市,点击按钮,下面的文本框显示天气。
接着写 Activity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edit;
private TextView text;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = findViewById(R.id.edit);
button = findViewById(R.id.button);
text = findViewById(R.id.text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
String cityName = edit.getText().toString();
String requestUrl = "https://free-api.heweather.com/s6/weather/now?location=" + cityName + "&key=5d520eb0********b270522641a7e387";
// 使用 asynctask 将结果更新到 UI 当中
MyAsyncTask asyncTask = new MyAsyncTask();
asyncTask.execute(requestUrl);
break;
default:
}
}
class MyAsyncTask extends AsyncTask<String, Integer, String> {
@Override
protected String doInBackground(String... strings) {
String result = null;
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(strings[0]).build();
Response response = client.newCall(request).execute();
String responseJson = response.body().string();
Gson gson = new Gson();
if (response.isSuccessful()) {
WeatherBean weatherArr = gson.fromJson(responseJson, WeatherBean.class);
result = weatherArr.getHeWeather6().get(0).getBasic().getLocation() + " 的天气是 " + weatherArr.getHeWeather6().get(0).getNow().getCond_txt();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
return result;
}
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
text.setText(s);
}
}
}
篇幅问题 Json Bean 就不贴了,反正就是 JSON 数据的那些个字段。因为需要异步更新 UI,这里使用了 Asynctask,因为 Android 主线程当中不允许连接网络嘛。
初学者往往都是这么写的,看起来似乎也没什么问题,但是我们仔细想一想,如果我们的需求有很多呢?这单单一个查询天气的功能就几十行代码,假如我们的页面十分复杂,我们按照这种方法一股脑的将我们的逻辑全部放到 Activity 或者 Fragment 当中,随着我们版本的更新,那么 Activity 当中的代码会越来越多,这样不光其他同事看不懂你的代码,时间长了,你自己也记不清了。
使用 MVC 思想来重新优化一下我们的代码:
先是 Model 层
我们一般在该层当中处理业务数据,不管是网络访问、数据库访问等等页面功能都是在这里进行,就以上个例子来将,页面功能就是“获取天气”,那么我们可以这样写:
public class WeatherModel {
public void getWeather(String cityName, final OnWeatherListener listener) {
String requestUrl = "https://free-api.heweather.com/s6/weather/now?location=" + cityName + "&key=5d520eb089e646acb270522641a7e387";
OkHttpClient client = new OkHttpClient();
final Request request = new Request.Builder().url(requestUrl).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
listener.failed(e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseJson = response.body().string();
Gson gson = new Gson();
WeatherBean weatherArr = gson.fromJson(responseJson, WeatherBean.class);
listener.success(weatherArr.getHeWeather6().get(0).getBasic().getLocation() + " 的天气是 " + weatherArr.getHeWeather6().get(0).getNow().getCond_txt());
}
}
});
}
}
当然我们一般是先写接口,这里就省略了。可以看到,在 Model 当中完成了向服务器发起请求的动作,然后通过一个监听器接口将结果返回。
接下来是 V 层了
其实在 Android 当中 V 层的概念很模糊,它并没有一个像 JSP 这种特定的收集/展示信息的技术,大多数情况下,我们把页面当中的控件(例如 ImageView、Button)当中是 V 层,所以通常说的 Android 当中的 V 层就指 XML 布局文件了:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_below="@id/edit"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_below="@id/button"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Controller 层
在 Android 当中 C 层的概念也相当的模糊,在 MVC 概念当中,C 层的的作用是用来沟通 M 层和 V 层,但 Android 当中并没有类似于 WEB 开发当中 Servlet,通常情况下,我们把 Activity/Fragment 当作是 C 层:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText edit;
private TextView text;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = findViewById(R.id.edit);
button = findViewById(R.id.button);
text = findViewById(R.id.text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
WeatherModel model = new WeatherModel();
model.getWeather(edit.getText().toString(), new OnWeatherListener() {
@Override
public void success(String result) {
Message msg = new Message();
msg.obj = result;
handler.sendMessage(msg);
}
@Override
public void failed(String error) {
Message msg = new Message();
msg.obj = error;
handler.sendMessage(msg);
}
});
break;
default:
}
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
text.setText((String) msg.obj);
}
};
}
事实上,Activity/Fragment 也承担着一部分的 V 层的角色,Android 特性所致,没有办法。
总结一下:
通过上面的例子,我们发现 MVC 并没有让我们的代码减少,相反还增加了一些,事实上,将我们的代码分层,并不会减少代码量,而是让我们的代码结构更清晰
优点:
缺点:
网上找了很多文章来看,不得不说,国内技术博客真的是鱼目混杂,单 MVC 三层之间的耦合关系,就众说纷纭,各种图示也是千变万化箭头随便乱指。
仔细想想也就释然了,毕竟 MVC 只是一个粗泛的概念,并没有一个特定的正确答案,人们根据自己对于业务的分层去理解,也正常。
有说 M 层和 V 层耦合的,我感觉就是那些 View 层控件直接去调用 M 层的返回数据,例如这种:
Model model = new Model();
text.setText(model.getString());
View 层的 TextView 可以直接使用 Model 层返回的数据。
也有说 Controller 层和 View 层有耦合,大概也就是说 Activity 和 View 当中的控件绑定在一起承担了一些 View 的初始化操作赋值操作之类的吧。
其实 MVC 只是一种思想,在这种思想上随便你怎么发挥,方向别错了就好,具有个人特色的 MVC!