我理解的 MVC

大年初三,实在无聊,想着前段时间写的 MVC 还有很多问题,所以重新写一遍,看看能不能查漏补缺。

什么是 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 的用法,我来试一试。

我们先来一个不使用 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 特性所致,没有办法。

总结一下:

  • M 层用来完成“页面的任务所需要的操作”
  • V 层用来收集用户的信息,以供 M 层去“操作”,以及将 M 层的“操作”结果展示出来
  • C 层用来沟通二者,将 V 层收集到的信息交给 M 层,将 M 成完成的结果交给 V

MVC 的优缺点

通过上面的例子,我们发现 MVC 并没有让我们的代码减少,相反还增加了一些,事实上,将我们的代码分层,并不会减少代码量,而是让我们的代码结构更清晰

优点:

  • 降低耦合性,可以看到上面的代码当中,在一定程度上分离了视图模块和业务模块,将来修改界面或者更改功能的时候,直接去修改 xml 或者 Model 当中代码就好了,不会对其他部分造成特别大的影像。
  • 重用性,Model 独立了出来,当其他页面需要使用相同通能时,直接调用就好,不需要重写

缺点:

  • 上面的例子就证明,增加了代码量(所以说,对于一些简单的界面使用 MVC 反而是画蛇添足)
  • V 层和 C 层紧密联系,V 层无法像 M 层一样可重用
  • 假如页面实现的功能很多,需要多次访问 M 层才可以获取到足够 V 想要显示的数据

一些思考

网上找了很多文章来看,不得不说,国内技术博客真的是鱼目混杂,单 MVC 三层之间的耦合关系,就众说纷纭,各种图示也是千变万化箭头随便乱指。

仔细想想也就释然了,毕竟 MVC 只是一个粗泛的概念,并没有一个特定的正确答案,人们根据自己对于业务的分层去理解,也正常。

有说 M 层和 V 层耦合的,我感觉就是那些 View 层控件直接去调用 M 层的返回数据,例如这种:

Model model = new Model();
text.setText(model.getString());

View 层的 TextView 可以直接使用 Model 层返回的数据。

也有说 Controller 层和 View 层有耦合,大概也就是说 Activity 和 View 当中的控件绑定在一起承担了一些 View 的初始化操作赋值操作之类的吧。

其实 MVC 只是一种思想,在这种思想上随便你怎么发挥,方向别错了就好,具有个人特色的 MVC!

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1