首页 > Web开发 > 详细

MVC,MVP设计模式

时间:2017-01-18 07:44:52      阅读:436      评论:0      收藏:0      [点我收藏+]

什么是MVP

  MVP是模型(Model)、视图(View)、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。

  模型(Model):负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;

  视图(View):负责界面数据的展示,与用户进行交互;

  主持人(Presenter):相当于协调者,是模型与视图之间的桥梁,将模型与视图分离开来。

  如下图所示,View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有Viwe层以及Model层的Interface的引用,而View层持有Presenter层Interface的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载完毕,最后Presenter层再调用View层的接口将加载后的数据展示给用户。这就是MVP模式的整个核心过程。

技术分享

  这样分层的好处就是大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。另一方面Model层可以封装复用,可以极大的减少代码量。当然,MVP还有其他的一些优点,这里不再赘述。下面看下MVP模式在具体项目中的使用。

MVP模式在项目中的使用

1、View层

  View层新闻展示模块的是组件是Fragment,里面有一个RecyclerView、SwipeRefreshLayout。布局代码如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <android.support.v4.widget.SwipeRefreshLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:app="http://schemas.android.com/apk/res-auto"
 5     android:id="@+id/swipe_refresh_widget"
 6     android:layout_width="match_parent"
 7     android:layout_height="match_parent">
 8     <android.support.v7.widget.RecyclerView
 9         android:id="@+id/recycle_view"
10         android:layout_width="match_parent"
11         android:layout_height="wrap_content"
12         android:scrollbars="vertical"
13         app:layout_behavior="@string/appbar_scrolling_view_behavior"
14         android:paddingTop="@dimen/card_margin">
15     </android.support.v7.widget.RecyclerView>
16 </android.support.v4.widget.SwipeRefreshLayout>

 新闻列表模块主要是展示从网络获取的新闻列表信息,View层的接口大概需要如下方法:

  (1)加载数据的过程中需要提示“正在加载”的反馈信息给用户

  (2)加载成功后,将加载得到的数据填充到RecyclerView展示给用户

  (3)加载成功后,需要将“正在加载”反馈信息取消掉

  (4)若加载数据失败,如无网络连接,则需要给用户提示信息

  根据上面描述,我们将View层的接口定义如下,分别对应上面四个方法:

1 public interface NewsView {
2     void showProgress();
3     void addNews(List<NewsBean> newsList);
4     void hideProgress();
5     void showLoadFailMsg();
6 }

在新闻列表Fragment中实现上述接口:

  1 package com.lauren.simplenews.news.widget;
  2 import android.content.Intent;
  3 import android.os.Bundle;
  4 import android.support.annotation.Nullable;
  5 import android.support.design.widget.Snackbar;
  6 import android.support.v4.app.ActivityCompat;
  7 import android.support.v4.app.ActivityOptionsCompat;
  8 import android.support.v4.app.Fragment;
  9 import android.support.v4.widget.SwipeRefreshLayout;
 10 import android.support.v7.widget.DefaultItemAnimator;
 11 import android.support.v7.widget.LinearLayoutManager;
 12 import android.support.v7.widget.RecyclerView;
 13 import android.view.LayoutInflater;
 14 import android.view.View;
 15 import android.view.ViewGroup;
 16 import com.lauren.simplenews.R;
 17 import com.lauren.simplenews.beans.NewsBean;
 18 import com.lauren.simplenews.commons.Urls;
 19 import com.lauren.simplenews.news.NewsAdapter;
 20 import com.lauren.simplenews.news.presenter.NewsPresenter;
 21 import com.lauren.simplenews.news.presenter.NewsPresenterImpl;
 22 import com.lauren.simplenews.news.view.NewsView;
 23 import com.lauren.simplenews.utils.LogUtils;
 24 import java.util.ArrayList;
 25 import java.util.List;
 26 /**
 27  * Description : 新闻Fragment
 28  * Author : lauren
 29  * Email  : lauren.liuling@gmail.com
 30  * Blog   : http://www.liuling123.com
 31  * Date   : 15/12/13
 32  */
 33 public class NewsListFragment extends Fragment implements NewsView, SwipeRefreshLayout.OnRefreshListener {
 34     private static final String TAG = "NewsListFragment";
 35     private SwipeRefreshLayout mSwipeRefreshWidget;
 36     private RecyclerView mRecyclerView;
 37     private LinearLayoutManager mLayoutManager;
 38     private NewsAdapter mAdapter;
 39     private List<NewsBean> mData;
 40     private NewsPresenter mNewsPresenter;
 41     private int mType = NewsFragment.NEWS_TYPE_TOP;
 42     private int pageIndex = 0;
 43     public static NewsListFragment newInstance(int type) {
 44         Bundle args = new Bundle();
 45         NewsListFragment fragment = new NewsListFragment();
 46         args.putInt("type", type);
 47         fragment.setArguments(args);
 48         return fragment;
 49     }
 50     @Override
 51     public void onCreate(@Nullable Bundle savedInstanceState) {
 52         super.onCreate(savedInstanceState);
 53         mNewsPresenter = new NewsPresenterImpl(this);
 54         mType = getArguments().getInt("type");
 55     }
 56     @Nullable
 57     @Override
 58     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 59         View view = inflater.inflate(R.layout.fragment_newslist, null);
 60         mSwipeRefreshWidget = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_widget);
 61         mSwipeRefreshWidget.setColorSchemeResources(R.color.primary,
 62                 R.color.primary_dark, R.color.primary_light,
 63                 R.color.accent);
 64         mSwipeRefreshWidget.setOnRefreshListener(this);
 65         mRecyclerView = (RecyclerView)view.findViewById(R.id.recycle_view);
 66         mRecyclerView.setHasFixedSize(true);
 67         mLayoutManager = new LinearLayoutManager(getActivity());
 68         mRecyclerView.setLayoutManager(mLayoutManager);
 69         mRecyclerView.setItemAnimator(new DefaultItemAnimator());
 70         mAdapter = new NewsAdapter(getActivity().getApplicationContext());
 71         mAdapter.setOnItemClickListener(mOnItemClickListener);
 72         mRecyclerView.setAdapter(mAdapter);
 73         mRecyclerView.setOnScrollListener(mOnScrollListener);
 74         onRefresh();
 75         return view;
 76     }
 77     private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
 78         private int lastVisibleItem;
 79         @Override
 80         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
 81             super.onScrolled(recyclerView, dx, dy);
 82             lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
 83         }
 84         @Override
 85         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
 86             super.onScrollStateChanged(recyclerView, newState);
 87             if (newState == RecyclerView.SCROLL_STATE_IDLE
 88                     && lastVisibleItem + 1 == mAdapter.getItemCount()
 89                     && mAdapter.isShowFooter()) {
 90                 //加载更多
 91                 LogUtils.d(TAG, "loading more data");
 92                 mNewsPresenter.loadNews(mType, pageIndex + Urls.PAZE_SIZE);
 93             }
 94         }
 95     };
 96     private NewsAdapter.OnItemClickListener mOnItemClickListener = new NewsAdapter.OnItemClickListener() {
 97         @Override
 98         public void onItemClick(View view, int position) {
 99             NewsBean news = mAdapter.getItem(position);
100             Intent intent = new Intent(getActivity(), NewsDetailActivity.class);
101             intent.putExtra("news", news);
102             View transitionView = view.findViewById(R.id.ivNews);
103             ActivityOptionsCompat options =
104                     ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
105                             transitionView, getString(R.string.transition_news_img));
106             ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
107         }
108     };
109     @Override
110     public void showProgress() {
111         mSwipeRefreshWidget.setRefreshing(true);
112     }
113     @Override
114     public void addNews(List<NewsBean> newsList) {
115         mAdapter.isShowFooter(true);
116         if(mData == null) {
117             mData = new ArrayList<NewsBean>();
118         }
119         mData.addAll(newsList);
120         if(pageIndex == 0) {
121             mAdapter.setmDate(mData);
122         } else {
123             //如果没有更多数据了,则隐藏footer布局
124             if(newsList == null || newsList.size() == 0) {
125                 mAdapter.isShowFooter(false);
126             }
127             mAdapter.notifyDataSetChanged();
128         }
129         pageIndex += Urls.PAZE_SIZE;
130     }
131     @Override
132     public void hideProgress() {
133         mSwipeRefreshWidget.setRefreshing(false);
134     }
135     @Override
136     public void showLoadFailMsg() {
137         if(pageIndex == 0) {
138             mAdapter.isShowFooter(false);
139             mAdapter.notifyDataSetChanged();
140         }
141         Snackbar.make(getActivity().findViewById(R.id.drawer_layout), getString(R.string.load_fail), Snackbar.LENGTH_SHORT).show();
142     }
143     @Override
144     public void onRefresh() {
145         pageIndex = 0;
146         if(mData != null) {
147             mData.clear();
148         }
149         mNewsPresenter.loadNews(mType, pageIndex);
150     }
151 }

2、Model层

  新闻模块的model主要负责从服务器获取新闻列表信息,接口代码如下:

1 public interface NewsModel {
2     void loadNews(String url, int type, NewsModelImpl.OnLoadNewsListListener listener);
3     ......
4 }

实现如下:

 1 package com.lauren.simplenews.news.model;
 2 import com.lauren.simplenews.beans.NewsBean;
 3 import com.lauren.simplenews.beans.NewsDetailBean;
 4 import com.lauren.simplenews.commons.Urls;
 5 import com.lauren.simplenews.news.NewsJsonUtils;
 6 import com.lauren.simplenews.news.widget.NewsFragment;
 7 import com.lauren.simplenews.utils.OkHttpUtils;
 8 import java.util.List;
 9 /**
10  * Description : 新闻业务处理类
11  * Author : lauren
12  * Email  : lauren.liuling@gmail.com
13  * Blog   : http://www.liuling123.com
14  * Date   : 15/12/19
15  */
16 public class NewsModelImpl implements NewsModel {
17     /**
18      * 加载新闻列表
19      * @param url
20      * @param listener
21      */
22     @Override
23     public void loadNews(String url, final int type, final OnLoadNewsListListener listener) {
24         OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() {
25             @Override
26             public void onSuccess(String response) {
27                 List<NewsBean> newsBeanList = NewsJsonUtils.readJsonNewsBeans(response, getID(type));
28                 listener.onSuccess(newsBeanList);
29             }
30             @Override
31             public void onFailure(Exception e) {
32                 listener.onFailure("load news list failure.", e);
33             }
34         };
35         OkHttpUtils.get(url, loadNewsCallback);
36     }
37     ......
38     /**
39      * 获取ID
40      * @param type
41      * @return
42      */
43     private String getID(int type) {
44         String id;
45         switch (type) {
46             case NewsFragment.NEWS_TYPE_TOP:
47                 id = Urls.TOP_ID;
48                 break;
49             case NewsFragment.NEWS_TYPE_NBA:
50                 id = Urls.NBA_ID;
51                 break;
52             case NewsFragment.NEWS_TYPE_CARS:
53                 id = Urls.CAR_ID;
54                 break;
55             case NewsFragment.NEWS_TYPE_JOKES:
56                 id = Urls.JOKE_ID;
57                 break;
58             default:
59                 id = Urls.TOP_ID;
60                 break;
61         }
62         return id;
63     }
64     private String getDetailUrl(String docId) {
65         StringBuffer sb = new StringBuffer(Urls.NEW_DETAIL);
66         sb.append(docId).append(Urls.END_DETAIL_URL);
67         return sb.toString();
68     }
69     public interface OnLoadNewsListListener {
70         void onSuccess(List<NewsBean> list);
71         void onFailure(String msg, Exception e);
72     }
73     ......
74 }

网络请求使用开源项目OkHttp,OkHttpUtils是对其的封装,具体代码如下:

  1 package com.lauren.simplenews.utils;
  2 import android.os.Handler;
  3 import android.os.Looper;
  4 import com.google.gson.internal.$Gson$Types;
  5 import com.squareup.okhttp.Callback;
  6 import com.squareup.okhttp.FormEncodingBuilder;
  7 import com.squareup.okhttp.OkHttpClient;
  8 import com.squareup.okhttp.Request;
  9 import com.squareup.okhttp.RequestBody;
 10 import com.squareup.okhttp.Response;
 11 import java.io.IOException;
 12 import java.lang.reflect.ParameterizedType;
 13 import java.lang.reflect.Type;
 14 import java.net.CookieManager;
 15 import java.net.CookiePolicy;
 16 import java.util.List;
 17 import java.util.concurrent.TimeUnit;
 18 /**
 19  * Description : OkHttp网络连接封装工具类
 20  * Author : lauren
 21  * Email  : lauren.liuling@gmail.com
 22  * Blog   : http://www.liuling123.com
 23  * Date   : 15/12/17
 24  */
 25 public class OkHttpUtils {
 26     private static final String TAG = "OkHttpUtils";
 27     private static OkHttpUtils mInstance;
 28     private OkHttpClient mOkHttpClient;
 29     private Handler mDelivery;
 30     private OkHttpUtils() {
 31         mOkHttpClient = new OkHttpClient();
 32         mOkHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
 33         mOkHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);
 34         mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
 35         //cookie enabled
 36         mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
 37         mDelivery = new Handler(Looper.getMainLooper());
 38     }
 39     private synchronized static OkHttpUtils getmInstance() {
 40         if (mInstance == null) {
 41             mInstance = new OkHttpUtils();
 42         }
 43         return mInstance;
 44     }
 45     private void getRequest(String url, final ResultCallback callback) {
 46         final Request request = new Request.Builder().url(url).build();
 47         deliveryResult(callback, request);
 48     }
 49     private void postRequest(String url, final ResultCallback callback, List<Param> params) {
 50         Request request = buildPostRequest(url, params);
 51         deliveryResult(callback, request);
 52     }
 53     private void deliveryResult(final ResultCallback callback, Request request) {
 54         mOkHttpClient.newCall(request).enqueue(new Callback() {
 55             @Override
 56             public void onFailure(Request request, final IOException e) {
 57                 sendFailCallback(callback, e);
 58             }
 59             @Override
 60             public void onResponse(Response response) throws IOException {
 61                 try {
 62                     String str = response.body().string();
 63                     if (callback.mType == String.class) {
 64                         sendSuccessCallBack(callback, str);
 65                     } else {
 66                         Object object = JsonUtils.deserialize(str, callback.mType);
 67                         sendSuccessCallBack(callback, object);
 68                     }
 69                 } catch (final Exception e) {
 70                     LogUtils.e(TAG, "convert json failure", e);
 71                     sendFailCallback(callback, e);
 72                 }
 73             }
 74         });
 75     }
 76     private void sendFailCallback(final ResultCallback callback, final Exception e) {
 77         mDelivery.post(new Runnable() {
 78             @Override
 79             public void run() {
 80                 if (callback != null) {
 81                     callback.onFailure(e);
 82                 }
 83             }
 84         });
 85     }
 86     private void sendSuccessCallBack(final ResultCallback callback, final Object obj) {
 87         mDelivery.post(new Runnable() {
 88             @Override
 89             public void run() {
 90                 if (callback != null) {
 91                     callback.onSuccess(obj);
 92                 }
 93             }
 94         });
 95     }
 96     private Request buildPostRequest(String url, List<Param> params) {
 97         FormEncodingBuilder builder = new FormEncodingBuilder();
 98         for (Param param : params) {
 99             builder.add(param.key, param.value);
100         }
101         RequestBody requestBody = builder.build();
102         return new Request.Builder().url(url).post(requestBody).build();
103     }
104     /**********************对外接口************************/
105     /**
106      * get请求
107      * @param url  请求url
108      * @param callback  请求回调
109      */
110     public static void get(String url, ResultCallback callback) {
111         getmInstance().getRequest(url, callback);
112     }
113     /**
114      * post请求
115      * @param url       请求url
116      * @param callback  请求回调
117      * @param params    请求参数
118      */
119     public static void post(String url, final ResultCallback callback, List<Param> params) {
120         getmInstance().postRequest(url, callback, params);
121     }
122     /**
123      * http请求回调类,回调方法在UI线程中执行
124      * @param <T>
125      */
126     public static abstract class ResultCallback<T> {
127         Type mType;
128         public ResultCallback(){
129             mType = getSuperclassTypeParameter(getClass());
130         }
131         static Type getSuperclassTypeParameter(Class<?> subclass) {
132             Type superclass = subclass.getGenericSuperclass();
133             if (superclass instanceof Class) {
134                 throw new RuntimeException("Missing type parameter.");
135             }
136             ParameterizedType parameterized = (ParameterizedType) superclass;
137             return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
138         }
139         /**
140          * 请求成功回调
141          * @param response
142          */
143         public abstract void onSuccess(T response);
144         /**
145          * 请求失败回调
146          * @param e
147          */
148         public abstract void onFailure(Exception e);
149     }
150     /**
151      * post请求参数类
152      */
153     public static class Param {
154         String key;
155         String value;
156         public Param() {
157         }
158         public Param(String key, String value) {
159             this.key = key;
160             this.value = value;
161         }
162     }
163 }

 将网络请求进行封装可以减少很多的代码量,并且后期如果我不想用okhttp了,想换成其它的库,修改起来也方便。

3、Presenter层

  View层需要调用Presenter层加载新闻信息,所以Presenter需要提供加载新闻信息的接口:

1 public interface NewsPresenter {
2     void loadNews(int type, int page);
3 }

NewsPresenterImpl的构造函数中需要传入View层的接口对象NewView,并且需要创建一个NewsModel对象。Presenter的具体实现:

 1 package com.lauren.simplenews.news.presenter;
 2 import com.lauren.simplenews.beans.NewsBean;
 3 import com.lauren.simplenews.commons.Urls;
 4 import com.lauren.simplenews.news.model.NewsModel;
 5 import com.lauren.simplenews.news.model.NewsModelImpl;
 6 import com.lauren.simplenews.news.view.NewsView;
 7 import com.lauren.simplenews.news.widget.NewsFragment;
 8 import com.lauren.simplenews.utils.LogUtils;
 9 import java.util.List;
10 /**
11  * Description :
12  * Author : lauren
13  * Email  : lauren.liuling@gmail.com
14  * Blog   : http://www.liuling123.com
15  * Date   : 15/12/18
16  */
17 public class NewsPresenterImpl implements NewsPresenter, NewsModelImpl.OnLoadNewsListListener {
18     private static final String TAG = "NewsPresenterImpl";
19     private NewsView mNewsView;
20     private NewsModel mNewsModel;
21     public NewsPresenterImpl(NewsView newsView) {
22         this.mNewsView = newsView;
23         this.mNewsModel = new NewsModelImpl();
24     }
25     @Override
26     public void loadNews(final int type, final int pageIndex) {
27         String url = getUrl(type, pageIndex);
28         LogUtils.d(TAG, url);
29         //只有第一页的或者刷新的时候才显示刷新进度条
30         if(pageIndex == 0) {
31             mNewsView.showProgress();
32         }
33         mNewsModel.loadNews(url, type, this);
34     }
35     /**
36      * 根据类别和页面索引创建url
37      * @param type
38      * @param pageIndex
39      * @return
40      */
41     private String getUrl(int type, int pageIndex) {
42         StringBuffer sb = new StringBuffer();
43         switch (type) {
44             case NewsFragment.NEWS_TYPE_TOP:
45                 sb.append(Urls.TOP_URL).append(Urls.TOP_ID);
46                 break;
47             case NewsFragment.NEWS_TYPE_NBA:
48                 sb.append(Urls.COMMON_URL).append(Urls.NBA_ID);
49                 break;
50             case NewsFragment.NEWS_TYPE_CARS:
51                 sb.append(Urls.COMMON_URL).append(Urls.CAR_ID);
52                 break;
53             case NewsFragment.NEWS_TYPE_JOKES:
54                 sb.append(Urls.COMMON_URL).append(Urls.JOKE_ID);
55                 break;
56             default:
57                 sb.append(Urls.TOP_URL).append(Urls.TOP_ID);
58                 break;
59         }
60         sb.append("/").append(pageIndex).append(Urls.END_URL);
61         return sb.toString();
62     }
63     @Override
64     public void onSuccess(List<NewsBean> list) {
65         mNewsView.hideProgress();
66         mNewsView.addNews(list);
67     }
68     @Override
69     public void onFailure(String msg, Exception e) {
70         mNewsView.hideProgress();
71         mNewsView.showLoadFailMsg();
72     }
73 }

 当用户切换到NewsListFragment界面之后,界面需要展示新闻列表信息给用户。首先NewsListFragment会调用NewsPresenter的loadNews方法,NewsPresenter 的loadNews方法中又会调用NewsModel中的loadNews方法。NewsModel中的loadNews方法中就是加载数据的核心,通过Okhttp请求服务器接口获取数据,无论数据获取成功与否,都会通过OnLoadNewsListener接口回调给NewsPresenter 。如果获取成功,NewsPresenter 会调用NewsView的addNews方法将获取的新闻列表信息展示到RecyclerView。如果获取失败,则调用NewsView的showLoadFialMsg方法向用户提示失败信息。

  以上就是新闻列表请求的整个过程。

  源码地址:https://github.com/liuling07/SimpleNews

 

什么是MVC

MVC即Model-View-Controller。M:逻辑模型,V:视图模型,C:控制器。

  MVC模式下,系统框架的类库被划分为3种:模型(Model)、视图(View)、控制器(Controller)。模型对象负责建立数据结构和相应的行为操作处理。视图对象负责在屏幕上渲染出相应的图形信息展示给用户看。控制器对象负责截获用户的按键和屏幕触摸等事件,协调Model对象和View对象。

  用户与视图交互,视图接收并反馈用户的动作;视图把用户的请求传给相应的控制器,由控制器决定调用哪个模型,然后由模型调用相应的业务逻辑对用户请求进行加工处理,如果需要返回数据,模型会把相应的数据返回给控制器,由控制器调用相应的视图,最终由视图格式化和渲染返回的数据,对于返回的数据完全可以增加用户体验效果展现给用户。

  一个模型可以有多个视图,一个视图可以有多个控制器,一个控制器也可以有多个模型。

MVC模式结构如下:

技术分享

图1-1  MVC模式组件类型的关系和功能

  模型(Model):封装的是数据源和所有基于对这些数据的操作。在一个组件中,Model往往表示组件的状态和操作状态的方法。
  视图(View):封装的是对数据源Model的一种显示。一个模型可以由多个视图,而一个视图理论上也可以同不同的模型关联起来。
  控制器(Control):封装的是外界作用于模型的操作。通常,这些操作会转发到模型上,并调用模型中相应的一个或者多个方法。一般Controller在Model和View之间起到了沟通的作用,处理用户在View上的输入,并转发给Model。这样Model和View两者之间可以做到松散耦合,甚至可以彼此不知道对方,而由Controller连接起这两个部分。
  MVC应用程序总是由这三个部分组成。Event(事件)导致Controller改变Model或View,或者同时改变两者。只要Controller改变了Model的数据或者属性,所有依赖的View都会自动更新。类似的,只要Controller改变了View,View会从潜在的Model中获取数据来刷新自己。MVC模式最早是smalltalk语言研究团提出的,应用于用户交互应用程序中。
  在设计模式中,MVC实际上是一个比较高层的模式,它由多个更基本的设计模式组合而成,Model-View的关系实际上是Observer模式,模型的状态和试图的显示相互响应,而View-Controller则是由Strategy模式所描述的,View用一个特定的Controller的实例来实现一个特定的响应策略,更换不同的Controller,可以改变View对用户输入的响应。而其它的一些设计模式也很容易组合到这个体系中。比如,通过Composite模式,可以将多个View嵌套组合起来;通过FactoryMethod模式来指定View的Controller,等等。在GOF书的 Introduction中,有一小节是“Design Patterns in Smalltalk MVC”即介绍在MVC模式里用到的设计模式。它大概向我们传达了这样的信息:合成模式+策略模式+观察者模式约等于MVC模式(当然MVC模式要多一些 东西)。
  使用MVC的好处,一方面,分离数据和其表示,使得添加或者删除一个用户视图变得很容易,甚至可以在程序执行时动态的进行。Model和View能够单独的开发,增加了程序了可维护性,可扩展性,并使测试变得更为容易。另一方面,将控制逻辑和表现界面分离,允许程序能够在运行时根据工作流、用户习惯或者模型状态来动态选择不同的用户界面。因此,MVC模式广泛用于Web程序、GUI程序的架构
  这里实现一个Java应用程序。当用户在图形化用户界面输入一个球体的半径时,程序将显示该球体的体积与表面积。我们首先利用基本MVC模式实现以上程序,然后利用不同数量的模型、视图、控制器结构来扩展该程序。
  Model与View的交互使用Observer模式。Model类必须继承Observable类,View类必须实现接口Observer。正是由于实现了上述结构,当Model发生改变时(Controller改变Model的状态),Model就会自动刷新与之相关的View。Controller类主要负责新建Model与View,将view与Mode相关联,并处理触发模型值改变的事件。

技术分享
 1 import java.util.Observable;  
 2   
 3 //Sphere.java:Model类  
 4 //必须继承Observable,在Observable类中,方法addObserver()将视图与模型相关联  
 5 class Sphere extends Observable {  
 6   
 7     private double myRadius;  
 8       
 9     public void setRadius(double r) {  
10         myRadius = r;  
11         this.setChanged();         //指示模型已经改变  
12         this.notifyObservers();    //通知各个视图,从父继承的方法  
13     }  
14     //......  
15 }  
 1 import java.util.Observable;  
 2 import java.util.Observer;  
 3 import javax.swing.JPanel;  
 4   
 5 //TextView.java:View视图类  
 6 //当模型Sphere类的状态发生改变时,与模型相关联的视图中的update()方法  
 7 //就会自动被调用,从而实现视图的自动刷新  
 8 public class TextView extends JPanel implements Observer {  
 9   
10     @Override  
11     public void update(Observable o, Object arg) {  
12         Sphere balloon = (Sphere) o;  
13         radiusIn.setText("" + f3.format(balloon.getRadius()));  
14         volumeOut.setText("" + f3.format(balloon.volume()));  
15         surfAreaOut.setText("" + f3.format(balloon.surfaceArea()));  
16     }  
17     //......  
18 }  
 1 import java.awt.Container;  
 2 import java.awt.event.ActionEvent;  
 3 import javax.swing.JFrame;  
 4 import javax.swing.JTextField;  
 5   
 6 // SphereWindow.java:Controller类  
 7 // 它主要新建Model与View,将view与Mode相关联,并处理事件  
 8 public class SphereWindow extends JFrame {  
 9   
10     public SphereWindow() {  
11         super("Spheres: volume and surface area");  
12         model = new Sphere(0, 0, 100); //新建Model  
13         TextView view = new TextView(); //新建View  
14         model.addObserver(view); //将View与Model相关联  
15         view.update(model, null); //初始化视图,以后就会根据Model的变化自动刷新          
16         view.addActionListener(this);  
17         Container c = getContentPane();  
18         c.add(view);  
19     }  
20   
21     //处理事件:改变Model的状态  
22     public void actionPerformed(ActionEvent e) {  
23         JTextField t = (JTextField) e.getSource();  
24         double r = Double.parseDouble(t.getText());  
25         model.setRadius(r);  
26     }  
27     //......  
28 }  

这种MVC模式的程序具有极其良好的可扩展性。它可以轻松实现一个模型的多个视图;可以采用多个控制器;可以实现当模型改变时,所有视图自动刷新;可以使所有的控制器相互独立工作。
  比如实现一个模型、两个视图和一个控制器的程序。当用户在图形化用户界面输入一个球体的半径,程序除显示该球体的体积与表面积外,还将图形化显示该球体。该程序的4个类之间的示意图如下:

 

技术分享
   图1-2  一个模型、两个视图和一个控制器的基本结构

  

MVC的优点:
  (1)最重要的是应该有多个视图对应一个模型的能力。
在目前用户需求的快速变化下,可能有多种方式访问应用的要求。例如,订单模型可能有本系统的订单,也有网上订单,或者其他系统的订单,但对于订单的处理都是一样,也就是说订单的处理是一致的。按MVC设计模式,一个订单模型以及多个视图即可解决问题。这样减少了代码的复制,即减少了代码的维护量,一旦模型发生改变,也易于维护。 其次,由于模型返回的数据不带任何显示格式,因而这些模型也可直接应用于接口的使用。
  (2)由于一个应用被分离为三层,因此有时改变其中的一层就能满足应用的改变。一个应用的业务流程或者业务规则的改变只需改动MVC的模型层。
  (3)控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起完成不同的请求,因此,控制层可以说是包含了用户请求权限的概念。
  (4)它还有利于软件工程化管理。由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化产生管理程序代码。
  MVC的不足体现在以下几个方面:
  (1)增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
  (2)视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
  (3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
  (4) 目前,一般高级的界面工具或构造器不支持MVC模式。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。

MVC,MVP设计模式

原文:http://www.cnblogs.com/Im-Victor/p/6295329.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!