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.setOnClickListener(new View.OnClickListener() {                  
  14.                       @Override    
  15.                       public void onClick(View v) {  
  16.                              sendRequest(); 
  17.                     } 
  18.             }); 
  19.          } 
  20.      } 
  21.  
  22.      private void sendRequest() { 
  23.         new Thread() { 
  24.             public void run() { 
  25.                 Thread.sleep(15*1000); 
  26.                 handler.post(new Runnable() { 
  27.                     public void run() { 
  28.                          Context context = mFragment.getActivity(); 
  29.                          int duration = LENGTH_SHORT; 
  30.                          //NullPointerException will occur 
  31.                          Toast.makeText(context,"成功”,duration).show();                    
  32.                     }  
  33.                }); 
  34.             }  
  35.        }.start(); 
  36.     } 
  37. }  

改进的方案中,考虑到需要使用上下文,因此新增了接口传入Fragment作为上下文,在Presenter需要时可以使用,但是,由于Fragment生命周期会了变化,可能会导致空指针问题。

于是新的问题又需要解决。主要是两个思路,一个是为Presenter增加生命周期方法,在Fragment的生命周期方法里调用Presenter对应的生命周期函数,但这就让Presenter看起来像Fragment的孙子;另外一个就是承认Presenter其实不太合适承担Controller的职责,从而提供接口给外部处理;如下:


  1. public class HotelPresenter { 
  2.        private IHotelView mHotelView; 
  3.        private Handler handler = new Handler(Looper.getMainLooper()); 
  4.        public void setData(HotelModel hotelModel) { 
  5.            View button = mHotelView.getButtonView();        
  6.            button.setVisibility(hotelModel.showButton ? VISIBLE :GONE); 
  7.            if (hotelModel.showButton) {            
  8.                 button.setOnClickListener(new View.OnClickListener() {                
  9.                 @Override  
  10.                 public void onClick(View v) { 
  11.                      if (mCallback != null) { 
  12.                           mCallback.onSendButtonClicked(); 
  13.                     } 
  14.                 }); 
  15.            } 
  16.        } 
  17.  
  18.        public interface Callback { 
  19.           public void onSendButtonClicked(); 
  20.        } 
  21.  
  22.        private Callback mCallback; 
  23.        public  void setCallback(Callback  callback) { 
  24.             mCallack = callback; 
  25.        } 

这个方案很稳定,似乎成为了最佳的选择。但是自定接口和回调始终有那么一点痛。

三、MVP的扩展模式MVCPI

由于前面的分析,MVP参考实现并不是万能的,携程酒店并没有完全采用参考实现方案,而是结合自身的实践经验思考之后设计出来的扩展方案。我们主要考虑了一下的几个问题:

  • 如何定义View接口?
  • 如何定位Presenter ?
  • 如何对待Controller?
  • 如何解决长长的回调链?

通过对上述问题的思考,提出对应的解决方法,规避前面论述的各种问题,形成了携程酒店的MVCPI框架模式,并在多个业务场景运行,取得了较为满意的效果。下面,详细介绍MVCPI模式。

3.1、 IView

和Android 参考实现不一样的是,我们并没有采用强类型的接口作为表达View的方式,而是采用弱类型的接口来定义View。具体定义方式如下:


  1. public interface IView { 
  2.     //用于展示酒店名称的控件  
  3.     int NAME_VIEW = R.id.name_view;  
  4.     //用于展示酒店地址的控件 
  5.     int ADDRESS_VIEW = R.id.address_view;   
  6.     //用于展示酒店星级的控件  
  7.     int STAR_VIEW = R.id.star_view; 
  8.     //用于展示酒店详情入口的的控件 
  9.     int DETAIL_BUTTON = R.id.detail_button; 
  10. }  

上面的接口简洁的描述了作为业务控件的View需要具备的子控间ID,并不需要具体的实现类。因此也不需要Activity去实现这个接口,只需要在layout中申明这几个ID的即可,极大的简化了代码。

3.2、 Presenter

与参考实现的定位不一样,我们认为由Presenter取代Controller并不是一个好的做法,Presenter应是Controller的补充,主要起到View和Model解耦和数据绑定的作用,所负责的控件的上的业务还是有Controller决定如何去处理。另外setView接受的参数是一般的View,而非一个接口类型,内部根据IView定义的ID去查找子控件。如下:


  1. public class CtripHotelPresenter { 
  2.     TextView mNameView;  
  3.     TextView mAddressView; 
  4.     TextView mStarView;  
  5.     Button mDetailButton;  
  6.     public void setView(View view) {      
  7.         mNameView = (TextView)mView.findViewById(IView.NAME_VIEW);         
  8.         mAddressView = (TextView)mView.findViewById(IView.ADDRESS_VIEW);   
  9.         mStarView = (TextView) mView.findViewById(IView.STAR_VIEW);           
  10.         mDetailButton = (Button) mView.findViewById(IView.DETAIL_BUTTON);    
  11.    }  
  12.    public void setData(HotelModel hotel) {        
  13.         mNameView.setText(hotel.hotelName);         
  14.         mAddressView.setText(hotel.hotelAdress);        
  15.         mStarView.setText(hotel.hotelStar);  
  16.         int v = hotel.showButton ? View.VISIBLE : View.GONE;        
  17.         mDetailButton.setVisibility(v); 
  18.     } 
  19. }  

3.3、 Interactor

Interactor是我们定义出来的扩展元素,在MVP和MVC中都没有对应的角色。为了阐述它的含义,我们先来看看两个非常常见的场景。

回调链过长

在前面介绍过,Presenter自定义接口是很多候选方案中较为合理的选择,但相比MVC而言,MVP更容易出现如上图的一种调用和回调关系(甚至更长)。维护这种回调链通常来说是一件非常头痛的事情,从View的角度来看,很难知道某个事件到最后究竟完成了什么业务,Acitivity也不知道到要装配哪些回调。某个未知的新需求可能需要将该链条上的每个环节都增加回调。

下面来是另外一种场景,大家可以脑补一下采用上面的回调方案,回调链会是什么情况。

交互集中型界面

在该界面有几个特点:

  • 几十种动态交互需求,
  • 分布于不同的模块
  • 分布于不同深度的嵌套层次中

经过大量版本迭代后,无论产品经理,研发或者测试,都不清楚到底有哪些需求,业务逻辑是什么,写在什么地方等等……

上述两个场景可以得出两个结论:

  • 排查问题非常耗时
  • 增加功能成本高,容易引致其他问题

为了解决上述两个比较棘手的问题,我们引入了Interactor,用于描述整个界面的交互,一举解决上述两个问题。我们认为交互模型是一个功能模块的重要逻辑单元,相对于实体模型来说,交互模型更加抽象,在大多数的情况,并不能引起大家的注意,但它确实是如实体一样的存在,正是因为没有对交互进行系统的描述,才导致上面两种突出的问题。尽管抽象,但是交互模型本质非常简单,它有着和实体模型有相似的结构,示例如下:


  1. public class HotelOrderDetailListeners { 
  2.     public View.OnClickListener mBackListener; // 返回按钮点击事件监听者 
  3.     public View.OnClickListener mShareClickListener;//分享按钮事件监听者 
  4.     public View.OnClickListener mConsultClickListener;//咨询按钮事件监听者 
  5.     …… 
  6. }  

通过对界面整体分析后,我们建立如上的交互模型,所有的交互都在交互模型进行注册,由交互模型统一管理,进而可以对整个界面的交互进行宏观把控;然后在页面的所有元素中共享同一个交互模型,进而各个元素不再需要自定义接口和避免建立回调链。最后由Controller负责组装,进一步加强Controller的控制能力。

3.4、 MVCPI全貌

最后,整体介绍一下MVCPI的代码结构

1、首先定义整个界面中有哪些用户交互,本例中就一个详情按钮交互


  1. public class HotelInteractor { 
  2.      //点击详情的事件处理器 
  3.      public View.OnClickListener mDetail; 
  4. }  

2、Presenter构造时需要传入交互模型,内部定义了IView接口,传入的View中需要包含它定义的ID的控件,在bindData时,详情按钮的点击不是通过匿名内部类去处理,而是直接引用交互模型中定义的mDetail


  1. public class HotelPresenter { 
  2.    private View hotelView; 
  3.    private HotelInteractor mInteractor; 
  4.    private Button mDetailButton; 
  5.  
  6.    public HotelPresenter(HotelInteractor interactor) { 
  7.          this.mInteractor = interactor; 
  8.    } 
  9.  
  10.    private interface IView { 
  11.          int DETAIL= R.id.detail_button; 
  12.           …… 
  13.    } 
  14.    public void setView(View hotelView) { 
  15.          this.hotelView  = hotelView;  
  16.          mDetailButton= (Button)findViewById(IView. DETAIL ); 
  17.    } 
  18.    public void setData(HotelModel hotel) { 
  19.         if (hotel.showButton) { 
  20.              mDetailButton.setVisibility(View.Visibile); 
  21.              mDetailButton.setOnClickListener(mInteractor.mDetail); 
  22.         } 
  23.    } 
  24. }  

3、Controller负责界面各个元素(包括交互模型)的初始化和装配


  1. public class HotelActivity extends Activity { 
  2.     @Override 
  3.     protected void onCreate(Bundle savedInstanceState) { 
  4.         super.onCreate(savedInstanceState); 
  5.         setContentView(R.layout.activity_main2); 
  6.         HotelInteractor interactor = new HotelInteractor(); 
  7.         interactor.mDetail = new View.OnClickListener() { 
  8.              public void onClick(View view) { 
  9.                  viewHotelDetail();//处理详情业务;  
  10.             }  
  11.        }; 
  12.         HotelModel model= HotelLoader.loadHotelById(1000);        
  13.         HotelPresenter presenter = new HotelPresenter (interactor);        
  14.         View view= findViewById(R.id.hotel_view);        
  15.         presenter.setView(view); 
  16.         presenter.setData(hotel); 
  17.       } 
  18. }  

四、结论

通过对MVC、MVP的介绍和研究,我们发现二者的关系并不是相互取代的关系,而是一种演化和改进的关系。经实践证明,MVC仍然具有强大的生命力,试图用MVP取代MVC几乎都会失败。携程在MVC模式基础上,结合MVP思想,加入Interactor元素搭建的MVCPI框架模式,一方面将数据绑定逻辑从Controller(或者View)中分离出去,另一方面将交互模型的控制纳入进来,进一步加强了Controller的控制能力。无论从代码的简洁性,维护性,扩展性来看,都具有较大优势,具有一定的实践推广价值。

当然,任何框架模式都不是全能的,MVCPI也存在它不足,如果有好的意见和建议,欢迎加入,一起讨论推进框架模式的发展。

本文作者:佚名

来源:51CTO

时间: 2024-10-31 13:15:20

MVP模式在携程酒店的应用和扩展的相关文章

携程酒店预订业务增速放缓正面临严峻挑战

携程作为中国在线旅游OTA的龙头老大,凭借其强大的电话 客服中心和网络销售模式,很长一段时间在酒店和机票的在线预订市场中遥遥领先于同行其他厂商.近几年,受到航空公司机票直销的影响,导致佣金比例下降,机票业务为携程的收入贡献正在逐年减弱.而利润率最为丰厚的酒店预订业务,同样正在面临严峻的挑战,受到其他OTA厂商和去哪儿.淘宝等平台的挤压,携程酒店预订业务增速放缓,酒店预订市场格局正在悄然发生变化.2008-2012年携程Q1季度酒店预订业务营收情况EnfoDesk易观智库分析认为酒店预订市场竞争日

携程酒店团购过有效期可全额退款

国内最大的酒店预订网站-- 携程旅行网在4月底推出酒店团购"百团大战"后,迅速成为酒店团购市场老大. 昨日,携程酒店新业务负责人施聿耑在接受访问时透露,目前携程已获得酒店团购市场近30%的份额.另外,携程还宣布在酒店团购领域第一家承诺,过有效期可全额退款. 施聿耑介绍,即日起,客人参与携程的普通酒店团购--"马上团"团购酒店后,因为种种原因没有在有效期内入住,携程给予全额退款并将款项退至客人账户.施聿耑表示,此举也是为充分保障客人的利益.客人在团购成功后,常常会因为

8823模式pk携程模式

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 网络和生活的紧密联系造就了携程网的成功 随着生活水平的提高和网络的普及,类似于携程网的酒店预订网络企业蓬勃发展,携程网凭借其独到的营销理念和强劲的推广手段,谱写了互联网上的新篇章,携程以提供集酒店预订.机票预订.度假预订.商旅管理.特约商户及旅游资讯在内的全方位旅行服务,被誉为互联网和传统旅游无缝结合的典范.类似于携程网之类的网站确实给一些高

携程酒店团购摆乌龙

本报讯(记者任芬)6月9日,携程在其官网上推出一款0元团购酒店产品,吸引来近五百名 网友后,又瞬间变脸"0元团购抽奖".不少网友质疑携程做法不诚信,而携程昨天则回应称是标题拟的有歧义,在发现问题后立即进行修改. >>事件0元团购变身抽奖 6月9日,携程推出系列0元活动,正当在官方微博上大肆推广时,来自海南蓝蝴蝶(网名)的微博引起网友关注.海南蓝蝴蝶(网名)在微博上写道:"0元团购希尔顿的豪海房493人团购成功了,然后立马下线改成了0元团购希尔顿的免房1次机会.算下

携程酒店折扣以挂牌价为基准涉嫌团购陷阱

每经记者 李卓 发自北京 <每日经济新闻>记者昨日(5月5日)发现,携程折扣均按酒店"标牌价"为基准计算,而实际上的"网络现付价"以及"前台现付价"都比标牌价要低,由此折算出的折扣价和实际价格相比,相差颇大. 携程网昨日一共推出了107家酒店团购,包括28家"马上订"(团购下单时直接确定入住日期,抢订成功保证有房)和79家"马上团"(团购成功后凭团购券号密码自行联系酒店预订入住). 记者发现,&

携程酒店地图:辐射5公里 涵盖6地标

出差在外的商旅人士或出外旅游的游客,往往会遇到这种情况:在网上查询预订好的酒店,实际前往时要花费预料之外的不少时间. 日前,携程网相关负责人告诉记者,携程已经率先发布了酒店地图速查业务.该业务以"地标"为中心,并在此基础上辐射周边5公里以内的酒店信息,包括各类酒店的价格.评分及http://www.aliyun.com/zixun/aggregation/18677.html">用户评价信息等,供消费者参考入住. 目前,该网站的酒店地图涵盖6大地标主题,分别是高尔夫球场

在线旅游预订商携程(CTRP.NASDAQ)正在四面受敌,佣金模式再次面临巨大挑战

此前,受淘宝网切入低价机票领域冲击,携程已经被迫低价"迎战",而今,正在迅猛上升的团购模式再次挑战携程的佣金模式.一间原价168元一夜的客房在团购网站最低只要10元即可. 目前,不少酒店都开辟了团购分销渠道,这种模式下的酒店价格可比正常价低30%~80%不等,有时甚至低于1折.由于团购可减少酒店客房空置率.平衡收益管理.减少营销费用,甚至抓住未来客户,大量酒店开始热衷于此方式销售,受此影响,这些酒店在携程的销售量近期已出现10%~30%不等的下滑. 团购冲击携程高佣金模式 团购的操作模

携程质疑去哪儿搜索不公:酒店直销对战升级

熊晓辉 携程和去哪儿,这一对在线旅游的"老冤家"之间的恩怨再一次升级. 近日,携程主动将23451.html">酒店产品从去哪儿网下线,并指责"去哪儿通过大量'马甲'代理商优先排序,破坏搜索比价平台的公正性". 去哪儿对此的回应则显得"无所谓",去哪儿相关人士告诉<中国经营报>记者,"我们本身直销酒店的量就非常大,携程的来去对我们没有什么影响." "星级酒店市场我们是要正面开火的.携程最高

携程换帅反思鼠标+水泥模式:梁建章被称更懂IT

中介交易 SEO诊断 淘宝客 云主机 技术大厅 本报记者 侯继勇 北京报道 梁建章复出,携程这艘在线旅游行业的巨舰将驶往何方? 2月21日,携程任命公司董事长梁建章兼任CEO,任命原CEO范敏为董事会副主席兼总裁.范敏还将兼任携程旅游控股有限公司董事长. 携程旅游控股有限公司为携程旗下负责旅游相关业务的控股与投资公司.天灏资本首席分析师侯晓天认为,调整后范敏所负责业务是携程的非主流业务,范敏的作用比以前更少了. 而携程方面则表示"相信范敏会在新的职位上继续创造优异的业绩,帮助携程抓住新的机会.&