iOS 开发 – 均衡代码职责

编程入门 行业动态 更新时间:2024-10-25 00:24:59

iOS 开发 – 均衡代码<a href=https://www.elefans.com/category/jswz/34/1763209.html style=职责"/>

iOS 开发 – 均衡代码职责

前言

文章的标题有点绕口,不过想了半天,想不到更好的标题了。本文的诞生有一部分功劳要归于iOS应用现状分析,标题也是来源于原文中的“能把代码职责均衡的划分到不同的功能类里”。如果你看过我的文章,就会发现我是一个MVC主导开发的人。这是因为开发的项目总是算不上大项目,在合理的代码职责分工后项目能保持良好的状态,就没有使用到其他架构开发过项目(如果你的状态跟笔者差不多,就算不适用其他架构模式,你也应该自己学习)

OK,简短来说,在很早之前我就有写这么一篇文章的想法,大致是在当初面试很多iOS开发者的时候这样的对话萌生的念头,下面的对话是经过笔者总结的,切勿对号入座:

Q: 你在项目中使用了MVVM的架构结构,能说说为什么采用的是这种结构吗?

A: 这是因为我们的项目在开发中控制器的代码越来越多,超过了一千行,然后觉得这样控制器的职责太多,就采用一个个ViewModel把这些职责分离出来

Q: 能说说你们控制器的职责吗?或者有源码可以参考一下吗?

面试者拿出电脑展示源码

最后的结果就是,笔者不认为面试者需要使用到MVVM来改进他们的架构,这里当然是见仁见智了。由于对方代码职责的不合理分工导致了ViewModel层几乎没有业务逻辑,从而导致了控制器的失衡,变得笨重。在这种情况下即便他使用了ViewModel将控制器的代码分离了出来,充其量只是将垃圾挪到另一个地方罢了。我在MVC架构杂谈中提到过自身对MVC三个模块的职责认识,当你想将MVC改进成MVX的其他结构时,应当先思考自己的代码职责是不是已经均衡了。

码农小明的项目

在开始之前,还是强烈推荐推荐《重构-改善既有代码的设计》这本书,一本好书或者好文章应该让你每次观赏时都能产生不同的感觉。

正常来说,造成你代码笨重的最大凶手是重复的代码,例如曾经笔者看过这样一张界面图以及逻辑代码:

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @interface XXXViewController @property (weak, nonatomic) IBOutlet UIButton * rule1; @property (weak, nonatomic) IBOutlet UIButton * rule2; @property (weak, nonatomic) IBOutlet UIButton * rule3; @property (weak, nonatomic) IBOutlet UIButton * rule4; @end @implementation XXXViewController - (IBAction)actionToClickRule1: (id)sender {     [_rule1 setSelected: YES];     [_rule2 setSelected: NO];     [_rule3 setSelected: NO];     [_rule4 setSelected: NO]; } - (IBAction)actionToClickRule2: (id)sender {     [_rule1 setSelected: NO];     [_rule2 setSelected: YES];     [_rule3 setSelected: NO];     [_rule4 setSelected: NO]; } - (IBAction)actionToClickRule1: (id)sender {     [_rule1 setSelected: NO];     [_rule2 setSelected: NO];     [_rule3 setSelected: YES];     [_rule4 setSelected: NO]; } - (IBAction)actionToClickRule1: (id)sender {     [_rule1 setSelected: NO];     [_rule2 setSelected: NO];     [_rule3 setSelected: NO];     [_rule4 setSelected: YES]; } @end

别急着嘲笑这样的代码,曾经的我们也写过类似的代码。这就是最直接粗浅的重复代码,所有的重复代码都和上面存在一样的毛病:亢长、无意义、占用了大量的空间。实际上,这些重复的代码总是分散在多个类当中,积少成多让我们的代码变得笨重。因此,在讨论你的项目是否需要改进架构之前,先弄清楚你是否需要消除这些垃圾。

举个例子,小明开发的一款面向B端的应用中允许商户添加优惠活动,包括开始日期和结束日期:

1 2 3 4 5 6 7 8 9 @interface Promotion: NSObject + (instancetype)currentPromotion; @property (readonly, nonatomic) CGFloat discount; @property (readonly, nonatomic) NSDate * start; @property (readonly, nonatomic) NSDate * end; @end

由于商户同一时间只会存在一个优惠活动,小明把活动写成了单例,然后其他模块通过获取活动单例来计算折后价格:

1 2 3 4 5 //  module A Promotion * promotion = [Promotion currentPromotion]; NSDate * now = [NSDate date]; CGFloat discountAmount = _order.amount; if ([now timeIntervalSinceDate: promotion.start] > 0 && [now timeIntervalSinceDate: promotion.end]  0 && [now timeIntervalSinceDate: promotion.end]

小明在开发完成后优化代码时发现了多个模块存在这样的重复代码,于是他写了一个NSDate的扩展来简化了这段代码,顺便还添加了一个安全监测:

Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @implementation NSDate (convenience) - (BOOL)betweenFront: (NSDate *)front andBehind: (NSDate *)behind {     if (!front || !behind) { return NO; }     return ([self timeIntervalSinceDate: front] > 0 && [self timeIntervalSinceDate: behind] < 0); } @end //  module A Promotion * promotion = [Promotion currentPromotion]; NSDate * now = [NSDate date]; CGFloat discountAmount = _order.amount; if ([now betweenFront: promotion.start andBehind: promotion.end]) {     discountAmount *= promotion.discount; } //  module B Promotion * promotion = [Promotion currentPromotion]; NSDate * now = [NSDate date]; if ([now betweenFront: promotion.start andBehind: promotion.end]) {     [_cycleDisplayView display: @"全场限时%g折", promotion.discount*10]; }

过了一段时间,产品找到小明说:小明啊,商户反映说只有一个优惠活动是不够的,他们需要存在多个不同的活动。小明一想,那么就取消Promotion的单例属性,增加一个管理单例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @interface PromotionManager: NSObject @property (readonly, nonatomic) NSArray * promotions + (instancetype)sharedManager; - (void)requestPromotionsWithComplete: (void(^)(PromotionManager * manager))complete; @end //  module A - (void)viewDidLoad {     PromotionManager * manager = [PromotionManager sharedManager];     if (manager.promotions) {         [manager requestPromotionsWithComplete: ^(PromotionManager * manager) {             _promotions = manager.promotions;             [self calculateOrder];         }     } else {         _promotions = manager.promotions;         [self calculateOrder];     } } - (void)calculateOrder {     CGFloat orderAmount = _order.amount;     for (Promotion * promotion in _promotions) {         if ([[NSDate date] betweenFront: promotion.start andBehind: promotion.end]) {             orderAmount *= promotion.discount;         }     } }

随着日子一天天过去,产品提出的需求也越来越多。有一天,产品说应该让商户可以自由开关优惠活动,于是Promotion多了一个isActived是否激活的属性。其他模块的判断除了判断时间还多了判断是否启动了活动。再后来,还添加了一个synchronize属性判断是否可以与其他活动同时计算判断。最近产品告诉小明活动现在不仅局限于折扣,还新增了固定优惠,以及满额优惠,于是代码变成了下面这样:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @interface Promotion: NSObject @property (assign, nonatomic) BOOL isActived; @property (assign, nonatomic) BOOL synchronize; @property (assign, nonatomic) CGFloat discount; @property (assign, nonatomic) CGFloat discountCondition; @property (assign, nonatomic) DiscountType discountType; @property (assign, nonatomic) PromotionType promotionType; @property (readonly, nonatomic) NSDate * start; @property (readonly, nonatomic) NSDate * end; @end //  module A - (void)viewDidLoad {     PromotionManager * manager = [PromotionManager sharedManager];     if (manager.promotions) {         [manager requestPromotionsWithComplete: ^(PromotionManager * manager) {             _promotions = manager.promotions;             [self calculateOrder];         }     } else {         _promotions = manager.promotions;         [self calculateOrder];     } } - (void)calculateOrder {     CGFloat orderAmount = _order.amount;     NSMutableArray * fullPromotions = @[].mutableCopy;     NSMutableArray * discountPromotions = @[].mutableCopy;     for (Promotion p in _promotions) {         if (p.isActived && [[NSDate date] betweenFront: p.start andBehind: p.end]) {             if (p.promotionType == PromotionTypeFullPromotion) {                 [fullPromotions addObject: p];             } else if (p.promotionType == PromotionTypeDiscount) {                 [discountPromotions addObject: p];             }         }     }     Promotion * syncPromotion = nil;     Promotion * singlePromotion = nil;     for (Promotion * p in fullPromotions) {         if (p.synchronize) {             if (p.discountCondition != 0) {                 if (p.discountCondition > syncPromotion.discountCondition) {                     syncPromotion = p;                 }             } else {                 if (p.discount > syncPromotion.discount) {                     syncPromotion = p;                 }             }         } else {             if (p.discountCondition != 0) {                 if (p.discountCondition > singlePromotion.discountCondition) {                     singlePromotion = p;                 }             } else {                 if (p.discount > singlePromotion.discount) {                     singlePromotion = p;                 }             }         }     }     //  find discount promotions     ...... }

这时候模块获取优惠活动信息的代价已经变得十分的昂贵,一堆亢长的代码,重复度高。这时候小明的同事对他说,我们改进一下架构吧,通过ViewModel把这部分的代码从控制器分离出去。其实这时候ViewModel的做法跟上面小明直接扩展NSDate的目的是一样的,在这个时候ViewModel几乎无作为,基本所有逻辑都在控制器中不断地撑胖它。小明认真思考,完完全全将代码阅览后,告诉同事现在最大的原因在于代码职责混乱,并不能很好的分离到VC的模块中,解决的方式应该是从逻辑分工下手。

首先,小明发现Promotion本身除了存储活动信息,没有进行任何的逻辑操作。而控制器中判断活动是否有效以及折扣金额计算的业务理可以由Promotion来完成:

Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @interface Promotion: NSObject - (BOOL)isEffective; - (BOOL)isWorking; - (CGFloat)discountAmount: (CGFloat)amount; @end @implementation Promotion - (BOOL)isEffective {     return [[NSDate date] betweenFront: _start andBehind: _end]; } - (BOOL)isWorking {     return ( [self isEffective] && _isActived ); } - (CGFloat)discountAmount: (CGFloat)amount {     if ([self isWorking]) {         if (_promotionType == PromotionTypeDiscount) {             return [self calculateDiscount: amount];         } else {             if (amount < _discountCondition) { return amount; }             return [self calculateDiscount: amount];         }     }     return amount; } #pragma mark - Private - (CGFloat)calculateDiscount: (CGFloat)amount {     if (_discountType == DiscountTypeCoupon) {         return amount - _discount;     } else {         return amount * _discount;     } } @end

除此之外,小明发现先前封装的活动管理类PromotionManager本身涉及了网络请求和数据管理两个业务,因此需要将其中一个业务分离出来。于是网络请求封装成PromotionRequest,另一方面原有的数据管理只有获取数据的功能,因此增加增删改以及对活动进行初步筛选的功能:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #pragma mark -  PromotionManager.h @class PromotionManager; typeof void(^PromotionRequestComplete)(PromotionManager * manager); @interface PromotionRequest: NSObject + (void)requestPromotionsWithComplete: (PromotionRequestComplete)complete; + (void)insertPromotion: (Promotion *)promotion withComplete: (PromotionRequestComplete)complete; + (void)updatePromotion: (Promotion *)promotion withComplete: (PromotionRequestComplete)complete; + (void)deletePromotion: (Promotion *)promotion withComplete: (PromotionRequestComplete)complete; @end @interface PromotionManager: NSObject + (instancetype)sharedManager; - (NSArray *)workingPromotions; - (NSArray *)effectivePromotions; - (NSArray *)fullPromotions; - (NSArray *)discountPromotions; - (void)insertPromotion: (Promotion *)promotion; - (void)updatePromotion: (Promotion *)promotion; - (void)deletePromotion: (Promotion *)promotion; @end #pragma mark -  PromotionManager.m @interface PromotionManager () @property (nonatomic, strong) NSArray * promotions; @end @implementation PromotionManager + (instancetype)sharedManager { ... } - (NSArray *)fullPromotions {     return [self filterPromotionsWithType: PromotionTypeFullPromote]; } - (NSArray *)discountPromotions {     return [self filterPromotionsWithType: PromotionDiscountPromote]; } - (NSArray *)workingPromotions {     return _promotions.filter(^BOOL(Promotion * p) {         return (p.isWorking);     }); } - (NSArray *)effectivePromotions {     return _promotions.filter(^BOOL(Promotion * p) {         return (p.isEffective);     }); } - (NSArray *)filterPromotionsWithType: (PromotionType)type {     return [self workingPromotions].filter(^BOOL(Promotion * p) {         return (p.promotionType == type);     }); } - (void)insertPromotion: (Promotion *)promotion {     if ([_promotions containsObject: promotion]) {         [PromotionRequest updatePromotion: promotion withComplete: nil];     } else {         [PromotionRequest insertPromotion: promotion withComplete: nil];     } } - (void)updatePromotion: (Promotion *)promotion {     if ([_promotions containsObject: promotion]) {         [PromotionRequest updatePromotion: promotion withComplete: nil];     } } - (void)deletePromotion: (Promotion *)promotion {     if ([_promotions containsObject: promotion]) {         [PromotionRequest deletePromotion: promotion withComplete: nil];     } } - (void)obtainPromotionsFromJSON: (id)JSON { ... } @end

最后,小明发现其他模块在寻找最优惠活动的逻辑代码非常的多,另外由于存在满额优惠和普通优惠两种活动,进一步加大了代码量。因此小明新建了一个计算类PromotionCalculator用来完成查找最优活动和计算最优价格的接口:

Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @interface PromotionCalculator: NSObject + (CGFloat)calculateAmount: (CGFloat)amount; + (Promotion *)bestFullPromotion: (CGFloat)amount; + (Promotion *)bestDiscountPromotion: (CGFloat)amount; @end @implementation PromotionCalculator + (CGFloat)calculateAmount: (CGFloat)amount {     Promotion * bestFullPromotion = [self bestFullPromotion: amount];     Promotion * bestDiscountPromotion = [self bestDiscountPromotion: amount];     if (bestFullPromotion.synchronize && bestDiscountPromotion.synchronize) {         return [bestFullPromotion discountAmount: [bestDiscountPromotion discountAmount: amount]];     } else {         return MAX([bestDiscountPromotion discountAmount: amount], [bestFullPromotion discountAmount: amount]);     } } + (Promotion *)bestFullPromotion: (CGFloat)amount {     PromotionManager * manager = [PromotionManager sharedManager];     return [self bestPromotionInPromotions: [manager fullPromotions] amount: amount]; } + (Promotion *)bestDiscountPromotion: (CGFloat)amount {     PromotionManager * manager = [PromotionManager sharedManager];     return [self bestPromotionInPromotions: [manager discountPromotions] amount: amount]; }   + (Promotion *)bestPromotionInPromotions: (NSArray *)promotions amount: (CGFloat)amount {     CGFloat discount = amount;     Promotion * best = nil;     for (Promotion * promotion in promotions) {         CGFloat tmp = [promotion discountAmount: amount];         if (tmp < discount) {             discount = tmp;             best = promotion;         }     }     return best; } @end

当这些代码逻辑被小明分散到各处之后,小明惊讶的发现其他模块在进行计算时剩下几行代码而已:

1 2 3 4 5 - (void)viewDidLoad {     [PromotionRequest requestPromotionsWithComplete: ^(PromotionManager * manager) {         _discountAmount = [PromotionCalculator calculateAmount: _order.amount];     }]; }

这时候代码职责的结构图,小明成功的均衡了不同组件之间的代码职责,避免了改变项目原架构带来的风险以及不必要的工作:

尾语

这是第二篇讲MVC的文章,仍然要告诉大家的是MVC确确实实存在着缺陷,这个缺陷会在项目变得很大的时候暴露出来(笔者没有开发过大型项目的弱鸡),如果你的项目结构分层做的足够完善的话,那么该改进更换架构的时候就不要犹豫。但千万要记住,如果仅仅是因为重复了太多的无用代码,又或者是逻辑全部塞到控制器中,那么更换架构无非是将垃圾再次分散罢了。

关注iOS开发获得笔者更新动态
转载请注明地址以及作者

 

转载:/

 

更多推荐

iOS 开发 – 均衡代码职责

本文发布于:2024-02-26 23:04:53,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1704303.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:职责   代码   iOS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!