美文网首页
MVP_MVVM设计模式分享

MVP_MVVM设计模式分享

作者: 陈胜华 | 来源:发表于2017-02-17 16:14 被阅读74次
  • 面临开发的业务逻辑越来越错综复杂,MVC已经很大程度上不能完全满足我们的需求,于是就出现了MVVM、MVP等架构。其中,最广为人知的是MVVM,虽说上手没那么容易,但是出于它能为controller减压的优越性,被广泛使用。
  • 重量级视图控制器会让整个ViewController变得非常复杂且不可维护,让维护者接近崩溃却无从下手。(题外话:MVP实际用起来确实没有MVVM用起来效果明显。)

<a href="https://pan.baidu.com/s/1jIK9oCu">需要Demo工程的请点击获取</a>

MVP


MVP结构图.png

MVP模式是MVC的一个演化版本,MVP全称Model-View-Presenter

  • Model:主要提供数据的存储功能,一般都是用来封装网络获取的json数据的集合对象,Presenter通过调用Model进行对象交互。
  • View:View与MVC中的V又有一些小差别,MVP中的View可以是Viewcontroller、View等控件,Presenter通过向View传model数据进行交互。
  • Presenter:作为model和view的中间人,从model层获取数据之后传给view,使得View和model没有耦合,义务逻辑处理可以在此处处理

MVP模式目录结构

MVP模式目录结构.png

说明:对比MVC目录结构,多了Presenter的目录,数据交互处理都在Presenter中。就我理解,MVC中的Controller像一个大家族的大管家,MVP中的Presenter像这个家族中某个人的私人秘书


MVP模式代码及分析

Controller结构代码分析

@protocol HomeViewProtocol <NSObject>

///城市列表
- (void)onGetResponseCityListEntity:(NSArray<HomeResultEntity*>*)homeEntity fail:(NSInteger) errorCode des:(NSString *)des;

///行情列表
- (void)onGetResponseCondtionListEntity:(NSArray<NSDictionary*>*)condtionEntity fail:(NSInteger) errorCode des:(NSString *)des;

@end
@interface HomeViewController ()<HomeViewProtocol,UITableViewDelegate,UITableViewDataSource>

@property (nonatomic,strong) HomePresenter *homePresenter;

@property (nonatomic,strong) HomeCondtionPresenter *homeOtherPresenter;

@property (nonatomic,strong) NSMutableArray<NSMutableArray*> *listArray;

@property (nonatomic,strong) UITableView *tableView;

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _listArray = [[NSMutableArray alloc]initWithArray:@[@[],@[]]];
    
    _tableView = ({
        UITableView *tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-64) style:UITableViewStyleGrouped];
        [tableView registerNib:[UINib nibWithNibName:@"HomeViewCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([self class])];
        tableView.delegate = self;
        tableView.dataSource = self;
        [self.view addSubview:tableView];
        tableView;
    });
    
    //初始化HomePresenter,然后绑定一下自己的视图,
    _homePresenter = [[HomePresenter alloc]initWithView:self];
    [_homePresenter getCityResponseList];
    
    _homeOtherPresenter = [[HomeCondtionPresenter alloc]initWithView:self];
    [_homeOtherPresenter getCondtionResponseList];
}

///遵循HomeViewProtocol
- (void)onGetResponseCityListEntity:(NSArray<HomeResultEntity *> *)homeEntity fail:(NSInteger)errorCode des:(NSString *)des {
    if (homeEntity) {
        _listArray[0] = [NSMutableArray arrayWithArray:homeEntity];
        [_tableView reloadData];
    }
}

- (void)onGetResponseCondtionListEntity:(NSArray<NSDictionary *> *)condtionEntity fail:(NSInteger)errorCode des:(NSString *)des {
    if (condtionEntity.count > 0) {
        _listArray[1] = [NSMutableArray arrayWithArray:condtionEntity];
        [_tableView reloadData];        
    }
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return _listArray.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return _listArray[section].count;
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        HomeViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([self class])];
        [cell setModel:_listArray[0][indexPath.row]];
        return cell;
    } else {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
        if (!cell) {
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"UITableViewCell"];
        }
        NSDictionary *parm = _listArray[1][indexPath.row];
        cell.textLabel.text = parm[@"StoreName"]?:@"";
        cell.detailTextLabel.text = parm[@"StoreAddr"]?:@"";
        return cell;
    }
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        return 44;
    } else {
        return 66;
    }
}

@end
  • 因为MVP是协议式编程模式,所有需要遵守HomeViewProtocol协议。实现了该协议,当然需要实现对应的协议方法(onGetResponseCityListEntity: fail: des)。
  • 因为MVP是协议式编程模式,所有也需要绑定对应的View或者ViewController,所有[xxxPreenseter initWithView:self]方法。

Presenter结构代码及分析


@interface HomePresenter : HttpPresenter<id<HomeViewProtocol>>

///测试城市列表请求
- (void)getCityResponseList;

@end


@implementation HomePresenter

///测试城市列表请求
- (void)getCityResponseList {
    NSString *url = @"https://mobileapi.centanet.com/index/url";
    [self.httpClient get:url parameters:nil];
}

- (void)onSuccess:(id)responseObject fail:(id)clientInfo errCode:(NSInteger)errCode errInfo:(NSString *)errInfo {
    if ([_view respondsToSelector:@selector(onGetResponseCityListEntity:fail:des:)]) {
        if (responseObject) {
            HomeEntity * model = [HomeEntity yy_modelWithJSON:responseObject];
            [_view onGetResponseCityListEntity:model.Result fail:errCode des:errInfo];
        } else {
            [_view onGetResponseCityListEntity:@[] fail:errCode des:errInfo];
        }
    }
}

@end

代码说明

  • 因为HomePresenter是继承HttpPresenter的,所以当调用自身的get或者post请求的时候,需要实现(onSuccess: fail: errCode: errInfo)请求回调方法
  • “HttpPresenter<id<HomeViewProtocol>>”此泛型写法,将需要实现的协议方法转移到HomePresenter绑定的V中,本例子中就是通过这样的方法,将处理好的数据回调到Controller中去了

MVVM

MVVM结构模式.png

MVVM模式也是MVC的一个演化版本,全称Model-View-ViewModel

  • Model:数据模型,和MVP,MVC没区别
  • View:在和MVC 和 MVP 模式下,View就是用户界面
  • ViewModel:是一个公开公共属性和命令的抽象的view。取代了 MVC 模式的 controller,或 MVP 模式的任命者(presenter),MVVM 有一个驱动。在 viewmodel 中,这种驱动传达视图和数据绑定的通信。

MVVM模式目录结构

MVVM模式目录结构.png

MVVM模式代码及分析

Controller代码及分析

@interface TemplateViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (nonatomic,strong) UITableView *tableView;
@property (nonatomic,strong) TemplateViewModel *templateViewModel;

@end

@implementation TemplateViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _templateViewModel = [[TemplateViewModel alloc]init];
    
    _tableView = ({
        UITableView *tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-64) style:UITableViewStyleGrouped];
        [tableView registerNib:[UINib nibWithNibName:@"TemplateCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([TemplateCell class])];
        [tableView registerNib:[UINib nibWithNibName:@"StoreViewCell" bundle:nil] forCellReuseIdentifier:NSStringFromClass([StoreViewCell class])];
        tableView.delegate = self;
        tableView.dataSource = self;
        [self.view addSubview:tableView];
        tableView;
    });
    
    WS(weakSelf)
    [_templateViewModel setReturnBlock:^(NSURLSessionDataTask *task, id responseObject) {
        [weakSelf.tableView reloadData];
    } failureBlock:^(NSURLSessionDataTask *task, NSError *error) {
    }];
    
    [self addHttpOperation:[_templateViewModel getCityList]];
    [self addHttpOperation:[_templateViewModel getStoreList]];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [_templateViewModel tempNumberOfSection];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return [_templateViewModel tempNumberOfSection:section];
}

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        TemplateCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([TemplateCell class])];
        [cell setModel:[_templateViewModel tempIndexPathOfCity:indexPath.row]];
        return cell;
    } else {
        StoreViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([StoreViewCell class])];
        [cell setModel:[_templateViewModel tempIndexPathOfStore:indexPath.row]];
        return cell;
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        return 44;
    } else {
        return 66;
    }
}

@end

代码说明

  • _templateViewModel对象调用getCityList和getStoreList方法,获取服务器数据,并处理数据
  • _templateViewModel对象通过block回调,刷新View的数据
  • tableView刷新数据后,通过_templateViewModel获取对应的数据条数和每个cell显示的数据内容

ViewModel代码及分析(重点)

@interface TemplateViewModel : BaseViewModel

@property (nonatomic,strong) NSMutableArray<NSDictionary*> *resultStoreList;

//获取城市列表数据
- (NSURLSessionDataTask*)getCityList;

//获取门店列表数据
- (NSURLSessionDataTask*)getStoreList;

//tableView的组数(默认设置了2个section)
- (NSInteger)tempNumberOfSection;

//tableView每组显示的条数
- (NSInteger)tempNumberOfRowInSection:(NSInteger)section;

//第一个section的cell对应的model
- (TemplateResultEntity*)tempIndexPathOfCity:(NSInteger)row;

//第二个section的cell对应的model
- (NSDictionary*)tempIndexPathOfStore:(NSInteger)row;

@end

@implementation TemplateViewModel

- (instancetype)init {
    if (self = [super init]) {
        _resultStoreList = [[NSMutableArray alloc]init];
    }
    return self;
}

//获取城市列表数据
- (NSURLSessionDataTask *)getCityList{
    WS(weakSelf)
    return [ServiceClientRequest requestForCityListParm:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        TemplateEntity *model =[TemplateEntity yy_modelWithJSON:responseObject];
        [weakSelf.resultArray addObjectsFromArray:model.Result];
        if (weakSelf.successBlock) {
            weakSelf.successBlock(task,responseObject);
        }
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        if (weakSelf.failureBlock) {
            weakSelf.failureBlock(task,error);
        }
    }];
}

//获取门店列表数据
- (NSURLSessionDataTask *)getStoreList {
    WS(weakSelf)
    return [ServiceClientRequest requestForStoreParm:nil success:^(NSURLSessionDataTask *task, id responseObject) {
        NSArray *resultArray = responseObject[@"Result"];
        [weakSelf.resultStoreList addObjectsFromArray:resultArray];
        if (weakSelf.successBlock) {
            weakSelf.successBlock(task,responseObject);
        }
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        if (weakSelf.failureBlock) {
            weakSelf.failureBlock(task,error);
        }
    }];
}

- (NSInteger)tempNumberOfSection {
    return 2;
}

- (NSInteger)tempNumberOfRowInSection:(NSInteger)section{
    if (section == 0) {
        return self.resultArray.count;
    } else {
        return self.resultStoreList.count;
    }
}

- (TemplateResultEntity*)tempIndexPathOfCity:(NSInteger)row {
    return self.resultArray[row];
}

- (NSDictionary *)tempIndexPathOfStore:(NSInteger)row {
    return self.resultStoreList[row];
}

@end

代码说明

  • getCityList和getStoreList负责向远程服务器获取数据和映射成对应的数据结构模型
  • tempNumberOfSection返回对应tableView的组数
  • tempNumberOfRowInSection返回对应的tableView某个indexSection的row数组个数

当tableView触发刷新数据的时候,就会从这些方法中获取需要的数据,从而将数据处理和逻辑处理给转移到ViewModel上了

相关文章

  • MVP_MVVM设计模式分享

    面临开发的业务逻辑越来越错综复杂,MVC已经很大程度上不能完全满足我们的需求,于是就出现了MVVM、MVP等架构。...

  • 设计模式分享系列

    分享内容提纲 设计模式分享之Concept 设计模式》概念(起源)》分类》 分类》23种设计模式》举例 设计模式分...

  • Java设计模式——命令模式

    Java设计模式之命令模式 这期要分享的模式是命令模式,也是行为模式的一种。分享了这么多期的设计模式方面的内容,可...

  • 设计模式之“委派模式”?!

    今天想要分享的是设计模式中的委派模式,说他是设计模式,但并不是23种设计模式之中的。 委派模式(Delegate)...

  • 十道前端面试题第【05】篇

    摘要:本篇是设计模式专题,分享了10个设计模式的JS示例代码——工厂模式、单例模式、原型模式、建造者模式、外观模式...

  • Java 设计模式(状态模式)

    之前分享了两个 Java 设计模式,今天给大家带来的系列完整的 java 设计模式。为了帮助大家更好理解设计模式,...

  • Retrofit源码设计模式解析(下)

    本文将接着《Retrofit源码设计模式解析(上)》,继续分享以下设计模式在Retrofit中的应用: 适配器模式...

  • Java设计模式——解释器模式

    Java设计模式之解释器模式 这期开始跟大家分享行为模式,也是最后一类设计模式模式。 简介 解释器模式是定义一套规...

  • 好程序员Java培训​分享java设计模式之享元模式

    好程序员Java培训​分享java设计模式之享元模式,Java设计模式中的享元模式。享元模式有点类似于单例...

  • 设计模式(二)观察者模式

    1.前言 本期分享设计模式主题:观察者设计模式。 这是一个非常简单易学的设计模式,读完本文,你能知道观察者模式的设...

网友评论

      本文标题:MVP_MVVM设计模式分享

      本文链接:https://www.haomeiwen.com/subject/atzkwttx.html