iOS开发基础126-深入探索设计模式

在iOS开发中,主要的设计模式包括MVC(Model-View-Controller)、MVVM(Model-View-ViewModel)和MVP(Model-View-Presenter)。这些模式旨在分离关注点,简化代码维护和提高代码的可测试性。实际项目中,选择模式取决于项目复杂度、团队习惯、以及具体需求。下面我们详细解析每种模式,并给出示例说明。

优缺点对比

  • MVC

    • 优点:简单、易于理解和实现。
    • 缺点:当应用变得复杂时,Controller 可能会变得臃肿,难以维护。
  • MVVM

    • 优点:解耦了 View 和 Model,ViewModel 更易于测试;数据绑定简化了 UI 更新工作。
    • 缺点:需要处理数据绑定相关的复杂性,尤其在 Objective-C 中实现数据绑定(相较于 Swift)会更繁琐。
  • MVP

    • 优点:Presenter 中心化业务逻辑,使得 View 更加简洁;易于测试 Presenter。
    • 缺点:View 和 Presenter 的关系更加紧密,可能会导致一些重复代码。

下面将更详细地探讨每种模式的深层次特点、优缺点和应用场景,并补充一些实际项目中的使用注意事项和技巧。

一、MVC(Model-View-Controller)

1. 深入理解

MVC 是一种将应用程序分为三部分的方法,以分离内部表示、用户交互和具体实现方式。以下是 MVC 的一些具体特点:

  • Model

    • 负责应用程序的数据逻辑。
    • 独立于用户界面。
    • 可以是简单的对象,包含属性和计算逻辑。
  • View

    • 负责显示 Model 的数据。
    • 独立于业务逻辑,不应包含任何数据处理逻辑。
    • 响应用户的输入,通常将这些输入转发给 Controller。
  • Controller

    • 负责接收用户的输入,并调用 Model 和 View 执行响应动作。
    • 在整个架构中发挥桥梁作用,协调 Model 和 View 的交互。

2. 示例

Model

@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end

@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}
@end

View

@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
- (void)updateWithUser:(User *)user;
@end

@implementation UserView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
        _ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
        [self addSubview:_nameLabel];
        [self addSubview:_ageLabel];
    }
    return self;
}

- (void)updateWithUser:(User *)user {
    self.nameLabel.text = user.name;
    self.ageLabel.text = [NSString stringWithFormat:@"%@", user.age];
}

@end

Controller

@interface UserController : UIViewController
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) User *user;
@end

@implementation UserController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.user = [[User alloc] initWithName:@"John Doe" age:30];
    
    self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.userView];
    
    [self.userView updateWithUser:self.user];
}

@end

3. 优缺点

  • 优点:直观,容易理解和实现;遵循单一责任原则(每个部分有明确职责)。
  • 缺点:Controller 容易变得臃肿,特别是在复杂的应用程序中。

4. 使用注意事项

  • 避免在 Controller 中添加过多逻辑,尽量将业务逻辑放到 Model 中处理。
  • View 应尽可能简单,避免包含业务逻辑。

二、MVVM(Model-View-ViewModel)

1. 深入理解

MVVM 通过引入 ViewModel 层,进一步解耦视图和业务逻辑。其核心思想是由 ViewModel 处理 View 的所有状态、数据和行为。

  • Model:与 MVC 中一样,负责数据和业务逻辑。
  • View:负责界面和 UI 逻辑。
  • ViewModel:是 Model 和 View 的中介,处理所有的界面逻辑,使得 View 更加简单。

2. 示例

Model

@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end

@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}
@end

View

@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end

@implementation UserView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
        _ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
        [self addSubview:_nameLabel];
        [self addSubview:_ageLabel];
    }
    return self;
}
@end

ViewModel

@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, strong, readonly) NSString *displayName;
@property (nonatomic, strong, readonly) NSString *displayAge;
- (instancetype)initWithUser:(User *)user;
- (void)updateWithUser:(User *)user;
@end

@implementation UserViewModel

- (instancetype)initWithUser:(User *)user {
    self = [super init];
    if (self) {
        [self updateWithUser:user];
    }
    return self;
}

- (void)updateWithUser:(User *)user {
    self.user = user;
}

- (NSString *)displayName {
    return self.user.name;
}

- (NSString *)displayAge {
    return [NSString stringWithFormat:@"%@", self.user.age];
}

@end

ViewController

@interface UserViewController : UIViewController
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserViewModel *viewModel;
@end

@implementation UserViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    User *user = [[User alloc] initWithName:@"John Doe" age:30];
    self.viewModel = [[UserViewModel alloc] initWithUser:user];
    
    self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.userView];
    
    [self updateView];
}

- (void)updateView {
    self.userView.nameLabel.text = self.viewModel.displayName;
    self.userView.ageLabel.text = self.viewModel.displayAge;
}

@end

3. 优缺点

  • 优点:通过数据绑定简化 View;解耦 View 和 Model,提高代码可测试性和复用性。
  • 缺点:实施数据绑定(特别是 Objective-C 中的 KVO)可能复杂且容易出错;对于简单项目,可能有过度设计的风险。

4. 使用注意事项

  • ViewModel 中不应包含 UIKit 相关的代码,应保持纯粹的数据处理和逻辑操作。
  • 数据绑定需要仔细设计,避免内存泄漏和循环引用。

三、结合 RAC 的 MVVM

ReactiveCocoa(简称 RAC) 是一个用于实现响应式编程的框架。RAC 可以用于简化事件处理、数据绑定,并更好地支持 MVVM 模式。在结合 RAC 的实践中,以下场景非常适用:

  • 数据绑定:将 View 和 ViewModel 绑定,使得 View 自动反应数据变化。
  • 流式处理:处理复杂的异步事件和操作。
  • 避免 callback:通过信号和观察者机制减少回调地狱。

通过结合 RAC 和 MVVM 模式,可以大幅度简化代码并提高响应能力。

还不知道怎么使用RAC的同学戳这iOS开发基础111-RAC

1. 实现步骤

  1. Model:定义 User 模型。
  2. View:定义展示用户信息的视图。
  3. ViewModel:处理业务逻辑及响应数据变化。
  4. 数据绑定:将 View 和 ViewModel 绑定。

2. 示例代码

Model

// User.h
@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end

// User.m
@implementation User

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}

@end

View

// UserView.h
@interface UserView : UIView
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end

// UserView.m
@implementation UserView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
        _ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
        [self addSubview:_nameLabel];
        [self addSubview:_ageLabel];
    }
    return self;
}

@end

ViewModel

// UserViewModel.h
#import <ReactiveObjC/ReactiveObjC.h>
#import "User.h"

@interface UserViewModel : NSObject
@property (nonatomic, strong, readonly) RACSignal *nameSignal;
@property (nonatomic, strong, readonly) RACSignal *ageSignal;

- (instancetype)initWithUser:(User *)user;
@end

// UserViewModel.m
#import "UserViewModel.h"

@interface UserViewModel ()
@property (nonatomic, strong) User *user;
@end

@implementation UserViewModel

- (instancetype)initWithUser:(User *)user {
    self = [super init];
    if (self) {
        _user = user;
        
        // 创建数据信号
        _nameSignal = RACObserve(self, user.name);
        _ageSignal = [RACObserve(self, user.age) map:^id _Nullable(id  _Nullable value) {
            return [NSString stringWithFormat:@"Age: %@", value];
        }];
    }
    return self;
}

@end

ViewController

// UserViewController.h
@interface UserViewController : UIViewController
@end

// UserViewController.m
#import "UserViewController.h"
#import "UserView.h"
#import "UserViewModel.h"
#import "User.h"

@interface UserViewController ()
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserViewModel *viewModel;
@end

@implementation UserViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建用户和 ViewModel
    User *user = [[User alloc] initWithName:@"John Doe" age:@30];
    self.viewModel = [[UserViewModel alloc] initWithUser:user];
    
    // 设置 UserView
    self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.userView];
    
    // 数据绑定
    [self bindViewModel];
}

- (void)bindViewModel {
    RAC(self.userView.nameLabel, text) = self.viewModel.nameSignal;
    RAC(self.userView.ageLabel, text) = self.viewModel.ageSignal;
}

@end

3、优缺点

优点:
  1. 代码更简洁:通过信号和数据绑定,减少繁琐的代码。
  2. 提升响应能力:View 自动响应 ViewModel 的变化。
  3. 提高可测试性:通过独立的 ViewModel 层,业务逻辑可以相对独立地进行测试。
缺点:
  1. 学习曲线:RAC 和响应式编程的概念较为复杂,需要开发者理解信号、观察者等基础知识。
  2. 调试复杂:因为涉及到信号和订阅,调试过程可能比传统代码复杂。

4、总结

结合了 ReactiveCocoa 的 MVVM 使得数据绑定和事件处理变得简单和优雅,通过信号和响应式编程提升了代码的可读性和可维护性。

四、MVP(Model-View-Presenter)

1. 深入理解

MVP 模式通过引入 Presenter,完全分离了视图和业务逻辑。Presenter 负责处理所有从 View 接收的用户交互,并协调 Model 和 View 之间的通信。

  • Model:负责数据和业务逻辑。
  • View:通常是协议,定义更新 UI 的方法。
  • Presenter:负责处理来自 View 的用户输入,并更新 View。

2. 示例

Model

@interface User : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end

@implementation User
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}
@end

View

@protocol UserViewProtocol <NSObject>
- (void)updateName:(NSString *)name;
- (void)updateAge:(NSString *)age;
@end

@interface UserView : UIView <UserViewProtocol>
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@end

@implementation UserView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 200, 50)];
        _ageLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 120, 200, 50)];
        [self addSubview:_nameLabel];
        [self addSubview:_ageLabel];
    }
    return self;
}

- (void)updateName:(NSString *)name {
    self.nameLabel.text = name;
}

- (void)updateAge:(NSString *)age {
    self.ageLabel.text = age;
}

@end

Presenter

@interface UserPresenter : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, weak) id<UserViewProtocol> view;
- (instancetype)initWithUser:(User *)user view:(id<UserViewProtocol>)view;
- (void)updateUser;
@end

@implementation UserPresenter

- (instancetype)initWithUser:(User *)user view:(id<UserViewProtocol>)view {
    self = [super init];
    if (self) {
        _user = user;
        _view = view;
    }
    return self;
}

- (void)updateUser {
    [self.view updateName:self.user.name];
    [self.view updateAge:[NSString stringWithFormat:@"%@", self.user.age]];
}

@end

ViewController

@interface UserViewController : UIViewController <UserViewProtocol>
@property (nonatomic, strong) UserView *userView;
@property (nonatomic, strong) UserPresenter *presenter;
@end

@implementation UserViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    User *user = [[User alloc] initWithName:@"John Doe" age:30];
    self.presenter = [[UserPresenter alloc] initWithUser:user view:self];
    
    self.userView = [[UserView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.userView];
    
    [self.presenter updateUser];
}

// UserViewProtocol methods
- (void)updateName:(NSString *)name {
    [self.userView updateName:name];
}

- (void)updateAge:(NSString *)age {
    [self.userView updateAge:age];
}

@end

3. 优缺点

  • 优点:完全分离视图和业务逻辑,使 View 非常轻量;提高代码的可测试性。
  • 缺点:需要定义大量协议和接口,增加了代码的复杂性。

4. 使用注意事项

  • View 协议应尽量简单,只包含必要的 UI 更新方法。
  • Presenter 中不应直接引用 UIKit 相关代码,保持其纯粹的逻辑角色。

总结

深入理解 MVC、MVVM 和 MVP,可以帮助开发者选择最适合的架构模式进行开发。不同模式适用于不同的复杂度和需求的项目,理解每种模式的优缺点和适用场景是关键。无论选择哪种模式,都应遵循单一责任原则,保持代码的可维护性和可测试性。

posted @   Mr.陳  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
历史上的今天:
2015-07-17 iOS开发基础10-UIButton内边距和图片拉伸模式
2015-07-17 iOS开发基础9-提示框(UIAlertController)
点击右上角即可分享
微信分享提示