欢迎光临
我们一直在努力

MVP模式在携程酒店的应用和扩展

前言

酒店业务部门是携程旅行的几大业务之一,其业务逻辑复杂,业务需求变动快,经过多年的研发,已经是一个代码规模庞大的工程,如何规范代码,将代码按照其功能进行分类,将代码写到合适的地方对项目的迭代起着重要的作用。

MVP模式是目前客户端比较流行的框架模式,携程在很早之前就开始探索使用该模式进行相关的业务功能开发,以提升代码的规范性和可维护性,积累了一定的经验。本文将探讨一下该模式在实际工程中的优点和缺陷,并介绍携程面对这些问题时的思考,解决方案以及在实践经验基础上对该模式的扩展模式MVCPI。

一、从MVC说起

MVC已经是非常成熟的框架模式,甚至不少人认为它过时陈旧老气,在实践中,很多同事会抱怨,MVC会使得代码非常臃肿,尤其是Controller很容易变成大杂烩,预期的可维护性变得很脆弱,由此导致一方面希望有新框架模式可以解决现在的问题,但同时对框架模式又有些怀疑,新的框架模式是否能真正解决现在的问题?会不会重蹈覆辙?会不会过度设计?会不会掉进一个更深的坑?总之,这些类似“一朝被蛇咬,十年怕井绳”的担忧显得不无道理。但不管如何,我们需要仔细耐心的做工作。

1.1、被误解的MVC

在MVP模式逐渐流行之前,不管我们有意识或无意识地,我们使用的就是MVC模式。以Android为例,我们来看看MVC是什么样子。


 
  1. public class HotelActivity extends Activity {     
  2.       private TextView mNameView;  
  3.       private TextView mAddressView;   
  4.       private TextView mStarView;  
  5.       @Override     
  6.       protected void onCreate(Bundle savedInstanceState) {              
  7.           super.onCreate(savedInstanceState);             
  8.           setContentView(R.layout.activity_main2);     
  9.  
  10.           mNameView = (TextView) findViewById(R.id.hotel_view);               
  11.           mAddressView = (TextView) findViewById(R.id.address_view);           
  12.           mStarView = (TextView) findViewById(R.id.star_view); 
  13.  
  14.           HotelModel hotel = HotelLoader.loadHotelById(1000); 
  15.  
  16.           mHotelNameView.setText(hotel.hotelName);        
  17.           mHotelAddressView.setText(hotel.hotelAdress);         
  18.           mHotelStarView.setText(hotel.hotelStar); 
  19.       } 
  20.  

上面的代码,概括了Android MVC的基本结构,从笔者的经验来看,很多应用都存在这样的代码风格,也就是大部分人认为的MVC:

  • Model:

 
  1. Hotel,HotelLoader 
  • Controller:

 
  1. HotelActivity 
  • View:

 
  1. mHotelNameView 
  2.  
  3. mHotelAddressViewmHotelStarView  

可以试想一下如果这个界面展示的数据非常的多话,MainActivity必然会变得非常庞大,就像大部分人所抱怨的那样。诚然,上面的demo是MVC模式,但是,它仅是从系统框架的角度来看,如果从应用框架来看,它不是。下面来看一下,从应用框架来看一下MVC正确的结构:

1.2、MVC的正确姿势

应用中的MVC应该在系统的MVC框架上根据业务的自身的需要进行进一步封装,也就是说,如果在我们宣称我们是使用MVC框架模式的时候,代表我们的主要工作是封装自己的MVC组件。它看起来应该是像下面的风格:


 
  1. public class HotelActivity extends Activity { 
  2.  
  3.     private HotelView mHotelView; 
  4.  
  5.     @Override    
  6.     protected void onCreate(Bundle savedInstanceState) {         
  7.         super.onCreate(savedInstanceState);          
  8.         setContentView(R.layout.activity_main2); 
  9.         mHotelView = (HotelView) findViewById(R.id.hotel_view); 
  10.         HotelModel hotel = HotelLoader.loadHotelById(1000); 
  11.         mHotelView.setHotel(hotel);    
  12.      } 
  13.  

跟之前的代码相比,基本结构是相似的,如下:

  • Model:

 
  1. Hotel,HotelLoader 
  • Controller:

 
  1. HotelActivity 
  • View:

 
  1. mHotelView 

仅仅View层发生了变化,这是因为,Model和Controller相对是大家容易理解的概念,在面临任何一个业务需求的时候,自然就能产生的近乎本能的封装(尽管Model的基本封装大部分工程师都可完成,但不可否认Model的设计是至关重要而有难度的);而对View的看法,可能就是“能正确布局和展示就行”。但这正是关键所在:我们需要对界面进行全方位的封装,包括View。具体来说,一个真正的MVC框架应该具备下面的特点:

  • 数据都由Model进行封装
  • View绑定业务实体,view.setXXX
  • Controller不管理与业务无关的View

1.3 MVC模式的问题所在

前面说到,很多人抱怨采用MVC模式使得Controller变得很臃肿,我相信,Controller变得臃肿是事实,但其归结于采用MVC模式是不正确的,这个锅不应该由MVC来背,因为,这个论点会导致我们走向错误的方向从而无法发现MVC真正的问题所在。为什么这么说呢,那是因为在本人了解到的很多情况下,大家并没有正确理解MVC框架模式,如采用前文中第一种模式,自然会使得Controller臃肿,但是如果采用第二种模式,Controller的代码和逻辑也会非常清晰,至少不至于如此多的抱怨。因此如果只是想解决Controller臃肿的话,MVC就够了,毋庸质疑。那MVC的问题是什么呢?我想只有深刻的理解了这个问题,我们才有必要考虑是否需要引入新的框架模式,以及避免新的模式中可能出现的问题。

View强依赖于Model是MVC的主要问题。由此导致很多控件都是根据业务定制,从Android的角度来看,原本可以由一个通用的layout就能实现的控件,由于要绑定实体模型,现在必须要自定义控件,这导致出现大量不必要的重复代码。因此有必要将View和Model进行解耦,而MVP的主要思想就是解耦View和Model。由此引入MVP就显得很自然。

二、 Android MVP

2.1、参考实现

Android 官方提供的MVP参考实现,大致思想如下:

1、抽象出IView接口,规范控件访问方法,而不限View具体来源


 
  1. public interface IHotelView { 
  2.  
  3.   public TextView getNameView(); 
  4.  
  5.   public TextView getAddressView(); 
  6.  
  7.   public TextView getStarView(); 
  8.    
  9.  

2、抽象出IPresenter接口,定义IView 和 Model的绑定接口


 
  1. public interface IHotelPresenter { 
  2.  
  3.   public void setView(IHotelView hotelView); 
  4.   
  5.   public void setData(HotelMotel hotel); 
  6.  
  7.  

3、IPresenter的实现类,实施数据和IView的绑定,并负责相关的业务处理 


 
  1. public class HotelPresenter implements IHotelPresenter { 
  2.  
  3.   private IHotelView hotelView; 
  4.  
  5.   public void setView(IHotelView hotelView) { 
  6.  
  7.     this.hotelView = hotelView; 
  8.  
  9.   } 
  10.  
  11.   public void setData(HotelModel hotel) { 
  12.  
  13.     hotelView.getNameView().setText(hotel.hotelName); 
  14.  
  15.     hotelView.getAddressView().setText(hotel.hotelAddress); 
  16.  
  17.     hotelView.getStarView().setText(hotel.hotelStart); 
  18.  
  19.   } 
  20.  
  21.  

4、Activity实现IView,角色转变为View,弱化Controller的功能


 
  1. public class HotelActivity extends Activity  implements IHotelView { 
  2.     @Override    
  3.     protected void onCreate(Bundle savedInstanceState) {        
  4.         super.onCreate(savedInstanceState);        
  5.         setContentView(R.layout.activity_main2);       
  6.  
  7.         HotelModel hotel = HotelLoader.loadHotelById(1000);        
  8.         IPresenter presenter = new Presenter();           
  9.         presenter.setView(this);  
  10.         presenter.setData(hotel); 
  11.  
  12.     }  
  13.     @Override      
  14.        public TextView getNameView() { 
  15.             return (TextView)findViewById(R.id.hotel_name_view);  
  16.     } 
  17.     @Override    
  18.     public TextView getAddressView() { 
  19.         return (TextView)findViewById(R.id.hotel_address_view);   
  20.     } 
  21.     @Override     
  22.     public TextView getStarView() { 
  23.         return (TextView)findViewById(R.id.hotel_address_view);  
  24.     } 
  25.  

上述代码,主要的特点可以概括为:

  • 面向接口
  • View – Model 解耦
  • Activity角色转换

就目前了解到的情况来看,很多采用MVP模式的应用基本上和android参考实现方案差别不大,说明该模式的应用场景也是很广泛的。

2.2 Android MVP存在的问题

尽管已经有了大量的应用,但不可否认该模式的还是存在一些问题,这些问题在携程的使用过程中也得到了体现。比如,上下文丢失问题,生命周期问题,内存泄露问题以及大量的自定义接口,回调链变长等问题。可以归纳为:

  • 业务复杂时,可能使得Activity变成更加复杂,比如要实现N个IView,然后写更多个模版方法。
  • 业务复杂时,各个角色之间通信会变得很冗长和复杂,回调链过长。
  • Presenter处理业务,让业务变得很分散,不能全局掌握业务,很难去回答某个业务究竟是在哪里处理的。
  • 用Presenter替代Controller是一个危险的做法,可能出现内存泄漏,生命周期不同步,上下文丢失等问题。

以下面的这个需求来看几个具体的示例:

详情按钮的展示需要服务端下发标记位控制,展示时点击需要请求一个服务,服务返回时toast提示用户


 
  1. public class HotelPresenter {  
  2.     private IHotelView mHotelView; 
  3.     private Handler handler = new  Handler(getMainLooper());   
  4.     public void setData(HotelModel hotelModel) {    
  5.        View button = mHotelView.getButtonView();    
  6.        int visibility = hotelModel.showButton ? .VISIBLE :GONE;                 
  7.        button.setVisibility(visibility);   
  8.        if (hotelModel.showButton) {             
  9.            button.setOnClickListener(new View.OnClickListener() {                 
  10.            @Override                 
  11.            public void onClick(View v) {                
  12.                sendRequest();              
  13.            }        
  14.         });     
  15.      }  
  16.  
  17.      private void sendRequest() {        
  18.         new Thread() {        
  19.             public void run() { 
  20.                 Thread.sleep(15*1000); 
  21.                 handler.post(new Runnable() {          
  22.                         public void run() {                
  23.                               Toast.makeText(???) //Where is Context? 
  24.                         }  
  25.                 });    
  26.          }  
  27.        }.start();   
  28.      } 
  29.  

上述代码表明,HotelPresenter可以处理大部分的业务,但是在最后需要使用上下文的时候,出现了困难,因为脱离了上下文,展示一个Toast都不能实现

为了避免这样的尴尬,因此改进方案如下:

  1. public class HotelPresenter {   
  2.      private IHotelView mHotelView; 
  3.      private Fragment mFragment; 
  4.      private HotelPresenter(Fragment fragment) {  
  5.        this.mFragment = fragment;  
  6.      }   
  7.      private Handler handler = new Handler(Looper.getMainLooper());    
  8.  
  9.      public void setData(HotelModel hotelModel) {   
  10.          View button = mHotelView.getButtonView();        
  11.          button.setVisibility(hotelModel.showButton ? VISIBLE :GONE);  
  12.          if (hotelModel.showButton) {             
  13.                button

赞(0) 打赏
未经允许不得转载:九八云安全 » MVP模式在携程酒店的应用和扩展

评论 抢沙发