Android MVC设计模式详解

    MVC设计模式,该模式能够将系统分成三个层面,分别是数据访问层(M)、视图层(V)、业务逻辑层(C),可以降低代码的耦合度,提高代码的内聚度。MVC是Model View Controller的缩写,是Xerox PARC在八十年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已被广泛使用。最近几年被推荐为Sun公司J2EE平台的设计模式,并且受到越来越多的使用 ColdFusion 和 PHP 的开发者的欢迎。模型-视图-控制器模式是一个有用的工具箱,它有很多好处,但也有一些缺点。MVC用一种业务逻辑、数据、界面显示分离的方法组织代码,强制性的使应用程序的输入、处理和输出分开,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。


一、M-V-C介绍


Model(模型)

         模型表示应用的数据和业务规则。在MVC三者中,模型(M)是拥有最多的处理工作任务的,所有的业务逻辑都应该写在该层,即对数据库的操作、对网络等的操作、对业务计算等操作都应该在模型层(M)处理。一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。  


View(视图)

        视图是用户看到并与之交互的界面,视图层是应用程序中负责生成用户界面的部分,也是在整个MVC架构中用户唯一可以看到的一层。在视图中其实没有真正的处理发生,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。   


Controller(控制器)

       控制器接受用户的输入并调用模型和视图去完成用户的需求,它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。Android的控制层的重任通常落在了众多的Acitvity的肩上,这句话也就暗含了不要在Acitivity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。


二、M-V-C关系

  • 一个模型(M)   可以有多个视图(V);
  • 一个视图(V)    可以有多个控制器(C);
  • 一个控制器(C) 可以有多个模型(M) 。

Android MVC设计模式详解_第1张图片

  1. View 负责将用户的请求通知 Controller,并根据model更新界面;
  2. Controller 完成业务逻辑后,Activity或Fragment接收用户请求后改变 Model状态;
  3. Model 将新的数据发送到 View 显示,用户得到反馈;


三、代码案例

View层实现

public class TrackCtrlView implements View.OnClickListener{
    private ImageView btnStartTrack, btnStopTrack, btnPauseTrack;
    private TrackCtrlViewListener listener;
    private TrackRecordInfo trackRecordInfo;
    public TrackCtrlView(Activity activity, TrackCtrlViewListener listener){
        this.listener = listener;
        btnStartTrack = (ImageView) activity.findViewById(R.id.btnStartTrack);
        btnStopTrack = (ImageView) activity.findViewById(R.id.btnStopTrack);
        btnPauseTrack = (ImageView) activity.findViewById(R.id.btnPauseTrack);
        btnStartTrack.setOnClickListener(this);
        btnStopTrack.setOnClickListener(this);
        btnPauseTrack.setOnClickListener(this);
        btnPauseTrack.setOnClickListener(this);
    }
    /**
     * 将用户请求通知Controller
     */
    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.btnStartTrack:
                if(listener != null){
                    listener.trackStatusRequest(TrackRecordStatus.Recording);
                }
                break;

            case R.id.btnStopTrack:
                if(listener != null){
                    listener.trackStatusRequest(TrackRecordStatus.Stoped);
                }
                break;

            case R.id.btnPauseTrack:
                if(listener != null){
                    if(trackRecordInfo.status == TrackRecordStatus.Paused){
                        listener.trackStatusRequest(TrackRecordStatus.Recording);
                    }else{
                        listener.trackStatusRequest(TrackRecordStatus.Paused);
                    }
                }
                break;
            default:
               break;
        }
    }
    private void refreshView(){
        TrackRecordStatus trackStatus = trackRecordInfo == null ?
                TrackRecordStatus.Stoped : trackRecordInfo.status;
        if (trackStatus == TrackRecordStatus.Recording) {
            btnStartTrack.setVisibility(View.GONE);
            btnPauseTrack.setVisibility(View.VISIBLE);
            btnStopTrack.setVisibility(View.VISIBLE);
            btnPauseTrack.setImageResource(R.drawable.btn_track_ctrl_pause);

        } else if (trackStatus == TrackRecordStatus.Paused) {
            btnStartTrack.setVisibility(View.GONE);
            btnPauseTrack.setVisibility(View.VISIBLE);
            btnStopTrack.setVisibility(View.VISIBLE);
            btnPauseTrack.setImageResource(R.drawable.btn_track_ctrl_resume);

        } else {
            // TrackRecordStatus.Stoped
            btnStartTrack.setVisibility(View.VISIBLE);
            btnPauseTrack.setVisibility(View.GONE);
            btnStopTrack.setVisibility(View.GONE);
        }
    }
   public void setTrackRecordInfo(@Nullable TrackRecordInfo trackRecordInfo) {
        this.trackRecordInfo = trackRecordInfo;
        refreshView();
    }

    public interface TrackCtrlViewListener{
        /**
         * 用户点击按钮
         */
        public void trackStatusRequest(@Nullable TrackRecordStatus newStatus);
    }
}

Model层实现

public class TrackRecordInfo {
    private static final Gson gson = new Gson();
    /**
     * 应该是保存轨迹数据库id,此demo中数据库操作不实现,暂时trackId一直为0
     */
    public int trackId;
    public TrackRecordStatus status;
    public TrackRecordInfo(int trackId, TrackRecordStatus status) {
        this.trackId = trackId;
        this.status = status;
    }
    @NonNull
    public static TrackRecordInfo loadTrackRecordInfo(@NonNull Context context){
        String pref = SpUtil.getString(context, SpUtil.KEY_TRACK_RECORD_INFO, "");
        if(!TextUtils.isEmpty(pref)){
            return gson.fromJson(pref, TrackRecordInfo.class);
        }
        return null;
    }
    public static void changeTrackRecordInfo(@NonNull Context context, @Nullable TrackRecordInfo info){
        SpUtil.saveString(context,
                SpUtil.KEY_TRACK_RECORD_INFO,
                info == null ? "" : gson.toJson(info));

        //model通过消息总线,通知View刷新
        EventBus.getDefault().post(new EventTrackRecordInfoChanged(info));
    }
}

Controller层实现

public class MainActivity extends ActionBarActivity implements TrackCtrlView.TrackCtrlViewListener{
    private TrackCtrlView trackCtrlView;
    private TrackRecordInfo trackRecordInfo;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        trackCtrlView = new TrackCtrlView(this, this);
        EventBus.getDefault().register(this);
        trackRecordInfo = TrackRecordInfo.loadTrackRecordInfo(this);
        trackCtrlView.setTrackRecordInfo(trackRecordInfo);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
    @Override
    public void trackStatusRequest(@Nullable TrackRecordStatus newStatus) {
        if(newStatus == TrackRecordStatus.Recording){
            int trackId = 0;  //在数据库创建一条轨迹,并获取到数据库id
            trackRecordInfo = new TrackRecordInfo(trackId, TrackRecordStatus.Recording);
        }else if (newStatus == TrackRecordStatus.Paused) {
            if(trackRecordInfo != null){
                trackRecordInfo.status = newStatus;
            }
        } else {
            trackRecordInfo = null;
        }
        TrackRecordInfo.changeTrackRecordInfo(this, trackRecordInfo);
    }
    public void onEventMainThread(EventTrackRecordInfoChanged event){
        trackRecordInfo = event.info;
        trackCtrlView.setTrackRecordInfo(trackRecordInfo);
    }
}

四、总结


4.1 优势


(1)耦合性低、扩展性高

    相对于把代码混在一起写来说,使用MVC可以将模型层、视图层、控制器的代码结构分离,可以很大程度上降低了代码模块之间的耦合性,提高其独立性,方便应用程序的扩展和维护。

————————————————————————————————————

模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。

————————————————————————————————————

例如,模型层用到的网络请求方式是Volley框架,现在要改成Retrofit框架。如果是“硬编程”估计要吃点苦头了,但是如果使用MVC设计模式,那么模型层会独立出来。可以把网络框架在父级中进行封装,然后在子类中具体实现和向外暴露的调用方法。或者写一个模型层的接口向外暴露调用方法,具体实现内容在模型层内部。


(2)重用性高

   一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。

例如,去食堂打饭用一个大碗和一个小碗,然后用一个勺子(Model)往这两个碗(View)里盛饭。道理和上面是一样的,总不至于大碗还得用大勺子、小碗还得用小勺子吧?当然可以这样,别扯皮,但是这里不就是在扯MVC的优点嘛!

(3)团队开发快

    使用MVC模式职责划分更明确,利于代码的开发维护。可以帮助开发团队各自分工集中处理自己的工作,可以提升开发效率。 例如,搭建一个网站,Java开发人员集中精力于业务逻辑,HTML和JSP开发人员集中精力于表现形式上。

4.2 缺点


(1)首先,开发成本高

    由于使用MVC的项目需要付出成本去理解和设计,花费大量时间和精力将MVC应用到规模并不是很大的应用程序上,可能会得不偿失。特别是,对于一些简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性。所以完全按照MVC的设计模式去开发项目并不是轻而易举的事情,希望开发者们三思而行。


(2)其次,调试困难

    由于一个模型为多个视图提供数据,且模型层与视图层的严格分离,这样都给调试应用程序带来了一定的困难。需要每个模型在使用之前都需要经过彻底的测试。


(4)最后,MVC自身设计的先天缺陷

   在MVC模型里,更关注模型层(Model)的不变,为了让多个视图层(View)的不同显示。所以,在MVC模型里,Model不依赖于View,但是View是依赖于Model的。由于视图层需要持有控制层的Activity引用才能进行UI的引用和交互处理,妨碍了视图的独立重用。不仅如此,视图层(View)是可以直接访问模型层(Model)的,从而视图层不可避免会涉及一些业务逻辑。因为在View里实现的业务逻辑是无法被重用的,导致要更改和重用View变得比较困难。由于模型操作接口的不同,视图可能需要多次调用模型才能获得足够的显示数据,不必要的频繁访问未变化数据的,也损害一定性能。




至此,全部内容以及讲解完了,如有不足之处请留言提醒,或加入技术讨论群。

Android MVC设计模式详解_第2张图片

通往Android的阶梯:569614530


你可能感兴趣的:(Android,UI\HTTP框架)