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模式在具体项目中的使用。
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即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个类之间的示意图如下:
原文:http://www.cnblogs.com/Im-Victor/p/6295329.html