MVC(model-view-controller)模式在web应用的开发上很受欢迎,但是尽管你从没开发过web应用,我确信你至少听说过MVC.
今天我们将开发一个星星等级的简单应用:
当你每次点击屏幕时,星星的数量将会增加一,如果有五颗星星了,星星的数量将会变为0。
测试程序的源代码将在文章末尾,你能用它来做任何你想做的。
总览:
我将基于mvc模式开发这个应用(以一个简单的方式),可以这样形容:
1. 首先,由数据模型(model)以一种符合逻辑的方式,来运行所有的业务逻辑和数据。model不关心数据怎样显示给用户和用户行为怎样改变数据。通常,定义model的类是一种POJO(plain old java object)。(译者注:即没有引用任何平台API).这样的数据模型(model)通常使用一种观察者模式(Observerpattern)来允许监听器,例如view,为了更新view,虽然model不知道任何的关于view的信息,但是也可以维持清晰的工作流程。我们将在后面的代码看见。
2. 然后就是视图(view)了,视图负责将定义在model中的数据和数据的状态呈现给用户。将view和model分隔开来使得我们可以以各种各样的方式显示相同数据。例如:我们可以显示单个星星和星星的数目在它的右边,或者以垂直的方式显示星星。
3. 再者就是控制器(controller)了,controller管理由view提供的未经处理的事件,并且将时间转换成为数据模型(model)改变时的操作。例如:当你轻击星星等级控制时,显示星星的view得到一个MotionEvent(译者注:也就是未经处理的事件)说使用轻击了屏幕。view自身并不根据轻击事件修改modle,而是通知controller有用户轻击事件发生。然后,controller决定增加星星的等级。有controller并不仅仅在这种简单的情况下有好处,而且如果你想要替换或者改变组件的行为会很方便,并不仅仅是好看的。
接下来让我们来详细回顾每一个部分
M:Model
这是一个很好的想法开始开发你的MVC应用的model。这个方法能够确保你专注于你想要的业务逻辑然后根据业务逻辑来实现view和controller,而不是其他的一些方式。
实现model的时候,目标是抽象地描述组件的逻辑,不管ui的设计。在很多情况下,这意味这仅仅只是存储所有组件代表的数据,但是我们也想要确认数据和其他逻辑的限制。在目前的情况下,我们仅仅存储星星的等级值,添加getter和setter方法。以下是完整的代码:
public final class StarRatingModel {
public static final int MAX_STARS = 5;
public interface Listener {
void handleStarRatingChanged(StarRatingModel sender);
}
private int stars = 1;
private List<Listener> listeners = new ArrayList<Listener>();
public StarRatingModel() {
}
public int getStars() {
return stars;
}
public void setStars(int stars) {
if (stars > MAX_STARS) {
stars = MAX_STARS;
} else if (stars < 0) {
stars = 0;
}
if (stars != this.stars) {
this.stars = stars;
for (Listener listener : listeners) {
listener.handleStarRatingChanged(this);
}
}
}
public void addListener(Listener listener) {
this.listeners.add(listener);
}
public void removeListener(Listener listener) {
this.listeners.remove(listener);
}
}
在web应用中,web的基础view会自动获取任何时候页面因为用户的动作重新载入了,model中的改变,例如点击了提交按钮或一个链接。在我们的情况下,我们想要view在model改变的时候被更新,这样才能让用户总是看见最新的数据展现。这就是为什么我们引入listener接口并且保存一个listener列表用来更新任何时候的星星等级值改变。通过这样的方式,我们能够使view自身成为listener,使得view在任何model通知的的时候更新自己。最伟大的地方就是有了这样的架构,model就不需要知道任何关于view的事了,而且使的model在很多地方都可以被重用和减轻耦合。另一方面,view就不需要去想什么时候去重绘它自己,因为model会在view需要重绘的时候通知view。
C:controller
对,这和mvc原来的字母顺序不同,但是我们最好先开发controller。Controller的作用是接收用户的动作事件并且按照组件的描述来修改model。Android和在大多数的ui框架中一样,view接收未经处理的事件例如鼠标点击或者触摸,文本输入,屏幕方向改变等等。然而,我们想要减少view对这些事件的响应,而是把这些事件交给controller。通过这种方法我们能够让view部分只关注与显示数据而不是其他的。另外,应为controller是可以被更换的,我们可以以一种更灵活的方式去控制我们软件的行为。例如,我们可以改变controller使它不增加星星等级而是减少星星等级,或者让星星等级的增加依赖用户点击的区域。
在我们的软件中,controller的代码很简单:
public final class StarRatingController {
private StarRatingModel model;
public StarRatingController(StarRatingModel model) {
this.model = model;
}
public void handleTap(MotionEvent event) {
// the old trick with % to wrap around values
model.setStars((model.getStars() + 1) % (StarRatingModel.MAX_STARS + 1));
}
}
你可以看见,我们使controller负责将触摸事件转换为星星等级改变的业务逻辑操作
V:View
View一般是在mvc结构中最依赖平台的一部分。记住,我们想要展示这部分,或者展示model给用户。同时,view也捕获未经处理的事件并传递给controller以至于用户可以修改model。
我不在这里贴出完全的代码。源代码你能在文章末尾找到。
View类应该事件model的listener接口以至于view可以获取model的更新。View也尝试去注册和注销如果model改变了
public final class StarRatingView extends View implements StarRatingModel.Listener {
private StarRatingModel model;
private StarRatingController controller;
/* .... */
public void setModel(StarRatingModel model) {
if (model == null) {
throw new NullPointerException("model");
}
StarRatingModel oldModel = this.model;
if (oldModel != null) {
oldModel.removeListener(this);
}
this.model = model;
this.model.addListener(this);
this.controller = new StarRatingController(this.model);
if (oldModel != null) {
invalidate();
}
}
@Override
public void handleStarRatingChanged(StarRatingModel sender) {
invalidate();
}
/* .... */
}
你可以看见,任何时候model通知view更新,view就会强制地以invalidate()方法去重绘它自己。因为invalidate()或者其他的原因,Model的数据被用在view重绘的时候。
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < StarRatingModel.MAX_STARS; ++i) {
float starX = starTopLeft.x + i * starSize;
float starY = starTopLeft.y;
Paint paint = null;
if ((i + 1) <= model.getStars()) {
// draw a gold star
paint = goldPaint;
} else {
// draw a gray star
paint = grayPaint;
}
starRect.set(starX, starY, starX + starSize, starY + starSize);
canvas.drawBitmap(starBitmap, null, starRect, paint);
}
}
这就是关于view怎样和model交互的,当view获得未经处理的触摸事件的时候也和controller交互,如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
controller.handleTap(event);
return true;
} else {
return super.onTouchEvent(event);
}
}
当用户点击view时,view通知controller,controller修改model,model改变后,model会马上通知model所有的listener,这时,view将重绘
总结
我们能够从mvc架构中,甚至从这个简单的小东西得到的好处,有如下几点:
1. 我们有了改变view显示数据但是不改变model的业务逻辑,通过定义另外一个view的子类但是使用同样的model来绘制不同的数据(译者注:实现model的重用)
2. 我们可以开发另外一个cotroller来自定义对用户动作的响应
3. 我们可以在非ui的代码中重用model,序列化model或者将其存储在数据库中,而不影响ui
4. 最大的好处是,我们有一个好的架构,使得我们的功能可以被很好的提升和扩展
然后,关于mvc架构也有一下典型的问题,你应该记下以下几点:
1. Observer模式通常用来将view和model结合起来,当listener需要被注销而没有被注销时经常会成为model内存泄漏的源头
2. 当model和view在同一个线程或逻辑中,listener不被知道在运行前,这有可能阻塞线程
3. 当model和view在同一个线程中,你需要一个异步的方式去调用listener,同时确保通知以一个正确的顺序到达并且在这个时候还能有效的让listeners去处理
4. 必须有一个对于model,view,controller生命周期的清晰理解。当他们被创建时,谁创建了他们和什么应用被保存了
原文链接: http://www.wiseandroid.com/post/2010/07/19/Use-MVC-and-develop-a-simple-Star-Rating-widget-on-Android.aspx
第一次翻译,有诸多不到位的地方,欢迎各位批评指正