在日常开发中,经常讨论什么样的框架能"高内聚低耦合"
。感觉什么都知道,但做起来又好像什么都不知道。各种大佬又有各种的流派,框架这种东西还是得自己理解并且贯通。就如同武功秘籍,必须打通"任通二脉"才能突破到更高一层一样。各位程序猿,也是以这个目标进行自我修行提高。(PS:本文只针对自己的理解加以记录)
一. MVC解耦合
顾名思义,MVC包括三个模块:Model
(模型层)、View
(视图层)、Controller
(控制层)。与用户的交互顺序是:用户通过视图层发送请求(例如查询账户信息request),视图层的请求发送到控制层,控制层进行中转后,通过模型层与数据库交互查询。模型层的查询结果又经由控制层形成响应结果(response),在视图层展示。
这就是MVC模式的基本结构图,Controller 承担了 model 数据更新展示到 view上(随着项目的迭代,功能的累加)Controller会累加很多的代码业务。造成后期controller代码逻辑混杂难以维护
。
下面通过一个MVC小例子来研究分析:
@interface MVCViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *dataArray;
@end
@implementation MVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// UI -> 布局代码
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
self.tableView.dataSource = self;
NSArray *temArray =
@[
@{@"name":@"YYYYY",@"imageUrl":@"http://111",@"num":@"99"},
@{@"name":@"NNNNN",@"imageUrl":@"http://222",@"num":@"99"},
@{@"name":@"CCCCCCC",@"imageUrl":@"http://333",@"num":@"99"},
@{@"name":@"CgGGGG",@"imageUrl":@"http://4444",@"num":@"59"},
for (int i = 0; i<temArray.count; i++) {
Model *m = [Model modelWithDictionary:temArray[i]];
[self.dataArray addObject:m];
}
[self.tableView reloadData];
}
#pragma mark **- lazy**
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.tableFooterView = [UIView new];
_tableView.backgroundColor = [UIColor whiteColor];
[_tableView registerClass:[MVCTableViewCell class] forCellReuseIdentifier:reuserId];
}
return _tableView;
}
#pragma mark **UITableViewDataSource**
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return !self.dataArray ? 0: self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserId forIndexPath:indexPath];
((MVCTableViewCell *)cell).model = self.dataArray[indexPath.row];
return cell;
}
@end
//cell
@interface MVCTableViewCell : UITableViewCell
@property (nonatomic, strong) UIButton *subBtn;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *numLabel;
@property (nonatomic, strong) UIButton *addBtn;
@property (nonatomic, assign) int num;
@property (nonatomic,strong) Model *model;
@end
@implementation MVCTableViewCell
//*********核心代码**********//
- (void)setNum:(int)num{
_num = num;
self.numLabel.text = [NSString stringWithFormat:@"%d",self.num];
self.model.num = self.numLabel.text; // 解决上下拉 cell复用改变UI的值
}
// APP : MODEL -> UI
- (void)setModel:(Model *)model{
_model = model;
self.numLabel.text = model.num;
self.nameLabel.text = model.name;
}
@end
//model
@interface Model : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *imageUrl;
@property (nonatomic, copy) NSString *num;
@end
复制代码
通过上面核心代码解决了,上下拉cell复用时model的值没有改变,cell显示回99的问题
。
可是这样改代码,直接在View层改变model的值合适吗。这样处理违背了MVC的设计理念(单一原则)
,view只做UI展示不管model的值变化逻辑,如果后期这个加减的逻辑需要在别的地方用难道要重新在写一份同样的逻辑,这也就产生了代码冗余
。 有什么更好的办法能避免这样直接更改model呢?那么VC的意义是什么?
我们只希望VC建立依赖关系,不做别的过多任务。
核心问题:
- VC任务过重!
- 承担了哪些多余任务?
- 获取提供数据
- 代理任务业务处理
先把数据抽取出来,创建一个Present类来提供数据(数据提供层
),更改num业务代码(抽取业务代理
)。 上代码:
@protocol PresentDelegate <NSObject>
// 需求: UI num -> model
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath;
- (void)reloadUI;
@end
@interface Present : NSObject<PresentDelegate> //自己实现自己的代理
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic,weak) id<PresentDelegate> delegate;
@end
//Present.m
@implementation Present
//***************************核心代码**************************//
#pragma mark **-PresentDelegate**
- (void)didClickNum:(NSString *)num indexpath:(NSIndexPath *)indexpath{
if (indexpath.row < self.dataArray.count) {
Model *model = self.dataArray[indexpath.row];
model.num = num;
// model - delegate -> UI
if (self.delegate && [self.delegate respondsToSelector:@selector(reloadUI)]) {
[self.delegate reloadUI];
}
}
}
@end
复制代码
还可以把tableview->dataSource 抽取出来,UI-dataSource代理提供层(NYDataSource
)
@implementation NYDataSource
- (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before {
if(self = [super init]) {
_cellIdentifier = identifier;
_cellConfigureBefore = [before copy];
}
return self;
}
- (void)addDataArray:(NSArray *)datas{
if(!datas) return;
if (self.dataArray.count>0) {
[self.dataArray removeAllObjects];
}
[self.dataArray addObjectsFromArray:datas];
}
- (id)modelsAtIndexPath:(NSIndexPath *)indexPath {
return self.dataArray.count > indexPath.row ? self.dataArray[indexPath.row] : nil;
}
#pragma mark **UITableViewDataSource**
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return !self.dataArray ? 0: self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
id model = [self modelsAtIndexPath:indexPath];
if(self.cellConfigureBefore) {
self.cellConfigureBefore(cell, model,indexPath);
}
return cell;
}
- (NSMutableArray *)dataArray
{
if (!_dataArray)
{
_dataArray = [NSMutableArray arrayWithCapacity:5];
}
return _dataArray;
}
@end
复制代码
我感觉框架主要还是思想提升,方法的提高,写一遍代码只是帮助理解,并不是最重要的。
@implementation MVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//数据提供层
self.pt = [[Present alloc] init];
//UI处理层
self.dataSource = [[NYDataSource alloc] initWithIdentifier:reuserId configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
cell.model = model;
}];
// UI -> 布局代码
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
self.tableView.dataSource = self.dataSource;
//建立关系
[self.dataSource addDataArray:self.pt.dataArray];
[self.tableView reloadData];
}
#pragma mark **- lazy**
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.tableFooterView = [UIView new];
_tableView.backgroundColor = [UIColor whiteColor];
[_tableView registerClass:[MVCTableViewCell class] forCellReuseIdentifier:reuserId];
}
return _tableView;
}
@end
复制代码
Controller只做业务关联
,把具体任务安排给对应的负责层(如Present,NYDataSource 等)。 可以创建一个自定义view来接管self.view。所有的UI布局由自定义的view来处理(这里不体现具体代码)。
我们这边解藕还差一步(优化MVCTableViewCell):
MVCTableViewCell
#pragma mark **- setter**
// UI 响应 -> model
// 架构本质: 高内聚 低耦合 -> 维护
// 谁的事情谁做
// view 视图的展示
// model 数据
- (void)setNum:(int)num{
_num = num;
self.numLabel.text = [NSString stringWithFormat:@"%d",self.num];
// 发出响应 model delegate UI
if (self.delegate && [self.delegate respondsToSelector:@selector(didClickNum:indexpath:)]) {
[self.delegate didClickNum:self.numLabel.text indexpath:self.indexPath];
}
}
//VC 在修改部分
self.dataSource = [[NYDataSource alloc] initWithIdentifier:reuserId configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
//cell.delegate = _swpt;
__strong __typeof(weakSelf)strongSelf = weakSelf;
cell.delegate = strongSelf.pt;
//赋值
cell.indexPath = indexPath;
cell.numLabel.text = model.num;
cell.nameLabel.text = model.name;
}];
复制代码
这样就解决了model和view相互耦合的目的,也就是面向协议编程(MVP)。
1.MVVM
什么是MVVM结构呢?是Model-View-ViewModel的简写
,是M-V-VM三部分组成。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,其中ViewModel将视图 UI 和业务逻辑分开,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。
核心思路创建V和Model的双向绑定
,UI可以通知Model改变,Model也可以通知UI改变。 我们通过一个小例子来了解一下(这里用了RAC注册监听):
//MVVMViewModel核心代码
@implementation MVVMViewModel
- (instancetype)init
{
if (self==[super init]) {
[RACObserve(self, contentKey) subscribeNext:^(id _Nullable x) {
@synchronized (self) {
Model *rm = nil;
for (Model *item in self.list) {
if ([item.name isEqualToString:x]) {
rm = item;
continue;
}
}
if(rm)
{
[self.list removeObject:rm];//移除
}
}
if (self.successBlock) {
self.successBlock(self.list);
}
}];
}
return self;
}
- (void)loadData
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
self.list = [NSMutableArray array];
for (NSString *name in array) {
Model *m = [[Model alloc]init];
m.name = name;
[self.list addObject:m];
}
dispatch_async(dispatch_get_main_queue(), ^{
//外面调用 封装的代码块 能够获取到我们给的数据(及时性)
self.successBlock(self.list);
});
});
}
@end
//ViewController核心代码
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.cellIdentifier = @"cellid";
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.tableView];
//绑定 MVVM
__weak typeof(self) weakSelf = self;
self.vvmodel.successBlock = ^(NSMutableArray *list) {
__strong typeof(self) strongSelf = weakSelf;
[strongSelf.dataArray removeAllObjects];
[strongSelf.dataArray addObjectsFromArray:list];
MVVMView *headView = [[MVVMView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, (strongSelf.dataArray.count+1)/4*50)];
headView.dataList = strongSelf.dataArray;
strongSelf.tableView.tableHeaderView = headView;
[headView loadView];
[strongSelf.tableView reloadData];
};
[self.vvmodel loadData];//加载数据-执行block刷新
}
#pragma mark **- lazy**
- (NSMutableArray *)dataArray
{
if(!_dataArray){
_dataArray = [NSMutableArray array];
}
return _dataArray;
}
- (MVVMViewModel *)vvmodel
{
if(!_vvmodel){
_vvmodel = [[MVVMViewModel alloc] init];
}
return _vvmodel;
}
- (UITableView *)tableView{
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.tableFooterView = [UIView new];
_tableView.backgroundColor = [UIColor whiteColor];
_tableView.rowHeight = 40;
_tableView.dataSource = self;
_tableView.delegate = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:self.cellIdentifier];
}
return _tableView;
}
复制代码
通过自己的实践得到了这粗浅的认识,虽然粗浅但是确实是自己理解了的。
2.适配器
适配器模式(Adapter Pattern) 定义 (引用了《适配器模式》) 加入了自己的一点理解
Convert the interface of a class into another interface clients expect.Adapter lets classeswork together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)
适配器设计模型主要由三个角色组成,分别是:
- 适配器角色(
adapter
)-- 将已有接口转换成目标接口 - 目标角色(
target
) -- 目标可以是具体的或抽象的类,也可以是接口 - 源角色或被适配者(
adaptee
) -- 已有接口
接下来我们以手机充电适配器为例子,手机充电需要的电压一般为5V。国内家用供电为220V,而日本则是110V,针对不同地区的电压,我们需要一个适配器,这样才能对手机进行充电。
// Power.h
#import <Foundation/Foundation.h>
@interface Power : NSObject
//输出电压
- (NSInteger)outputValue;
@end
// Power.m
#import "Power.h"
@implementation Power
- (NSInteger)outputValue {
return 0;
}
@end
//手机5V电压的协议
// PowerPhoneNeedInterface.h
#import <Foundation/Foundation.h>
@protocol PowerPhoneNeedInterface <NSObject>
@required
- (NSInteger)outputPowerPhone;
@end
// PowerACChina.h 中国电压
#import "Power.h"
@interface PowerACChina : Power
@end
// PowerACChina.m
#import "PowerACChina.h"
@implementation PowerACChina
- (NSInteger)outputValue {
return 220;
}
@end
//PowerAdapterChina.h 中国适配器/ 适配者角色
#import "PowerACChina.h"
#import "PowerPhoneNeedInterface.h"
@interface PowerAdapterChina : PowerACChina<PowerPhoneNeedInterface>
@end
//PowerAdapterChina.m
#import "PowerAdapterChina.h"
@implementation PowerAdapterChina
- (NSInteger)outputPowerPhone {
return [self outputValue] / 44;
}
+ (void)test1 {
PowerAdapterChina *adapter = [[PowerAdapterChina alloc] init];
NSInteger inputPower = [adapter outputPowerPhone];
NSLog(@"source output power = %ld\n adpater output power = %ld", [adapter outputValue], inputPower);
}
@end
复制代码
运行结果:
source output power = 220
adpater output power = 5
复制代码
x日本适配器代码:
// PowerACJapan.h
#import "Power.h"
@interface PowerACJapan : Power
@end
//PowerACJapan.h
#import "PowerACJapan.h"
@implementation PowerACJapan
- (NSInteger)outputValue {
return 110;
}
@end
// PowerAdapterAll.h 适配器/适配角色
#import <Foundation/Foundation.h>
#import "Power.h"
#import "PowerPhoneNeedInterface.h"
@interface PowerAdapterAll : NSObject<PowerPhoneNeedInterface>
@property (nonatomic, strong) Power *power;
- (instancetype)initWithPower:(Power *)power;
@end
// PowerAdapterAll.m
#import "PowerAdapterAll.h"
#import "Power.h"
@implementation PowerAdapterAll
- (instancetype)initWithPower:(Power *)power {
self = [super init];
if (self) {
_power = power;
}
return self;
}
- (NSInteger)outputPowerPhone {
NSInteger outputPower = [self.power outputValue];
NSInteger sta = outputPower / 5;
if ([self.power isKindOfClass:PowerACJapan.class]) {
sta = 22;
} else if ([self.power isKindOfClass:PowerACChina.class]) {
sta = 44;
}
return outputPower / sta;
}
@end
PowerACJapan *power2 = [[PowerACJapan alloc] init];
PowerAdapterAll *adapter2 = [[PowerAdapterAll alloc] initWithPower:power2];
NSInteger inputPower2 = [adapter2 outputPowerPhone];
NSLog(@"source output power = %ld\n adpater output power = %ld", [adapter2.power outputValue], inputPower2);
复制代码
运行结果:
source output power = 110
adpater output power = 5
复制代码
设计图:
小结:
-
复用性:系统需要使用已经存在的类,功能符合系统要求,但这个类的接口不符合系统的需求,通过适配器模式解决不兼容的问题,使这些功能类得到复用。
-
耦合性:一定程度上的解耦
-
根据实际项目情况,使用设计模式,让项目更优化,易维护。
作者:可乐泡枸杞
链接:https://juejin.cn/post/7125346843076067359
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。