设计模式与代码重构——iOS篇

本文探讨了iOS开发中的设计模式,包括编程设计模式、苹果App开发常用设计模式,以及如何进行代码重构。文章强调了设计模式在提高编程效率和运行效率中的重要性,并通过实例解释了如何在实践中运用和优化代码。同时,文中提到了一些常见的设计模式,如观察者模式、装饰者模式、单例模式等,并讨论了如何在iOS项目中实施这些模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        有一阵子没写技术分享文了,最近每个月写一篇个人空间日记。主要是觉得自己技术比较一般写不出有质量的东西,误人子弟。互联网信息膨胀,让我们获取信息更加便捷,然而获取个人所需的正确信息,却需要每个人具备更强的搜索能力。搜索能力作为代码,就需要更优的算法。算法就像是程序的CPU,决定着程序的运行效率。
        与其说电脑改变了世界,不如说是电脑改变了人类改变世界的效率。电脑其实是根据人脑设计的,而程序思想和人的思想相通,所以一个程序员在学会一门语言后,学习第二门语言会来的容易很多,因为编程思想是相通的。我认为,这和学外语有相通之处。
        和一个爱武侠的朋友谈论程序,觉得写程序也像是练武,代码编辑器是武器,编程语言是武功秘籍,算法是功力,设计模式是招式。
        做iOS开发,无非是XCode一种代码编辑器,由于apple公司的封闭,XCode目前除了Mac系统下没有其他系统下的版本。XCode也随着iOS系统升级不断改良,GDI(Graphics Device Interface)不断完善,由起初的一个页面对应一个控制器的XIB编辑器,演变为模块化试图编辑的storyBoard,加之设备屏幕的丰富性,增加了图形界面可视化约束,以及数据模型视图CoreData的可视化编辑。功能的强大需要程序员更加深入的学习及掌握。然而人性的本能惰性使得很多老程序员并不喜欢使用新兴的工具,而选择纯代码实现,所以也得不到GDI所提供的视图编辑便利。是否使用GDI不关系一个程序员的编程思想,GDI在多数是客户端视图编码用到,它本身是由代码实现的,做项目可以没有GDI,但离不开编码,在多人协同开发的情况下越来越多的出现了多样化的混编。而CoreData的可视化数据模型也存在缺陷,其定义的模型属性,头字母必须是小写的。
        程序员何其多,做算法的没有几个,多数程序员是做界面的,做业务的。业务的复杂性,或设备硬件的限制才需要由算法提高运行效率。算法意味着接触更底层的东西,指针、位、汇编、机器语言。至于黑客种种,是一群对网络安全极其了解且对算法了如指掌的人把算法运用于攻击网络安全防线的神,他们不是人,我也见不到他们。对于目前的我,还是扎扎实实学精设计模式,运用于编程,让编程效率更高,运行效率更优。而我始终相信:会设计得码农,是工程师,不会设计得工程师是码农。精通算法的工程师,是架构师,不懂算法的工程师,只是代码的搬运工。

下面我针对编程设计模式和苹果App开发常用设计模式对设计模式进行简单介绍,也算是自己设计模式的读书笔记。

一、编程设计模式

        第一次我看《Head First,Design patterns》这本书的时候并不知道Head Frist的意思,因为知道自己看的是设计模式的书,联想到面向对象编程,以为它就是“头文件先写”的意思,后来才知道Head First是一个系列的丛书,所以应该与设计模式没有关系。于是百度一下,原来翻译为“深入浅出”。然而,“头文件先写”不但让我认识到自己需要加强英语学习,而且成为我编写代码的第一要务,于是后来慢慢有了一点面向对象的认识。

代码设计遵循以下原则:

  1.  找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2.   针对接口编程,而不是针对实现编程。
  3.  多用组合,少用继承。
  4.  别调用我们,我们会调用你。
  5.  一个类应该只有一个引起变化的原因。

下面就介绍一下常用设计模式和其对应的生活中的模式应用:

  1. 观察者模式(Subject、Observer):气象台、报纸杂志( 天气=>各种布告板;报纸杂志=>订阅人、读者)
  2.  装饰者模式:星巴克咖啡
  3.  工厂模式:披萨工厂、红团印、盖章
  4.  抽象工厂模式:披萨店
  5.  单例模式:巧克力工厂锅炉
  6.  命令模式:遥控器开关、订单
  7.  适配器模式:插座转接头
  8.  模板方法模式:泡茶、咖啡
  9.  迭代器和组合模式:餐厅并购煎饼屋,菜单的遍历
  10.  状态模式:糖果机、饮料机
  11.  代理模式:代表辅助完成某功能
  12.  复合模式:结合两个或多个模式,解决一般或重复发生的问题。

            由于本篇主要讲述iOS设计模式,这里不对编程模式展开叙述,后期会对编程模式写一篇学习笔记。这里就简要介绍一下。代码设计软件StarUML,是Mac下一款比较常用的设计软件。

二、苹果App开发常用设计模式及架构探索

  1. 设计模式的起源:MVC设计模式

1.1在模型对象中封装数据和基本行为
1.2使用视图对象向用户展示信息
1.3用控制器对象联系其模型和视图

  1. 针对接口编程,而不是针对实现编程


2.1.类和类型的差别:一个对象可以具有多个类型,而不同的类的对象可以有相同的类型
2.2.类型的所有对象,包括其子类的对象,都可以针对接口(协议、抽象类的虚方法)请求作出应答。
2.3.协议@protocol与抽象基类

            2.3.1协议并不定义任何实现,而只声明方法,以确定符合协议的类的行为。

      如我们最常用的UITableViewDataSource && UITableViewDelegate。             


@protocol UITableViewDataSource<NSObject>
@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

 @optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;             
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 

……
@end


@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>//协议本身可以遵守其他协议
 @optional
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
……
@end

​

 

      3, 对象组合与类继承


3.1类继承是白箱复用,对象组合是黑箱复用
3.2 优先使用组合而不是类继承,这里举一个项目里“发言”用的组合例子:

有多种发言,1、文字2、图文+文字3、音频(视频)4、带“@”的发言 5、带地址的发言 6、带发送范围的发言等,这个时候我们很难抽象出一个CELL基类满足各种“发言”的显示需求,即便可以,每种发言有自己相应的操作,这样就会导致耦合度也非常高,这个时候我们就需要用到组合,每个CELL实现自己的代理方法实现对应的操作。如下:

//

//  YYHomeRecorderTableViewCell.h

//  SuperCrm

//

//  Created by bill on 15/7/30.

//  Copyright (c) 2015年 Yonyouup Information Technology Co., Ltd. All rights reserved.

//



#import <UIKit/UIKit.h>

#import "YYHomeRecorderRowView.h"

#import "YYBaseViewController.h"

#import "YYHomeBusiDetailViewController.h"

#import "YYCustomAnalysisViewController.h"

#import "YYHomeBusinessViewController.h"

#import "YYHomeModel.h"

#import "WFTextView.h"

#import "WFHudView.h"



#pragma mark 最有价值的生意

@interface YYHomeRecorderTableViewCell : UITableViewCell



@property (nonatomic, strong) UILabel *h_titleLable;

@property (nonatomic, strong) UILabel *h_contentLable;

@property (nonatomic, strong) UIButton *h_arrrowButton;



@property (nonatomic, strong) UIImageView *h_imgview;



- (void)hanleData:(NSString *)contentStr;

@end



#pragma mark 具体生意

@protocol YYHomeRecorderSecondTableViewCellDelegate <NSObject>



- (void)secondTableIntoNextPage:(YYBaseViewController *)baseVC;

- (void)selectChangeScroll;//删除选择框



@end



@interface YYHomeRecorderSecondTableViewCell : UITableViewCell<YYHomeRecorderRowViewDelegate,UIScrollViewDelegate>

@property (nonatomic, assign) id<YYHomeRecorderSecondTableViewCellDelegate> delegate;

@property (nonatomic, strong) NSArray *h_dataArray;



- (void)handleData:(NSArray *)opportunitySummary;

@end



#pragma mark 客户分类信息

@protocol YYHomeRecorderThirdTableViewCellDelegate <NSObject>



- (void)thirdTableIntoNextPage:(YYBaseViewController *)baseVC;



@end



@interface YYHomeRecorderThirdTableViewCell : UITableViewCell

@property (nonatomic, assign) id<YYHomeRecorderThirdTableViewCellDelegate> delegate;

@property (nonatomic, strong) UIScrollView *h_scrollView;



//加载数据

- (void)handleData:(NSArray *)accountSummary;

@end



#pragma mark 客户生命周期

@interface YYHomeRecorderThirdOneTableViewCell : UITableViewCell

@property (nonatomic, strong) UILabel *h_titleLable;

@property (nonatomic, strong) UIButton *h_arrrowButton;

@end



#pragma mark 记录

@interface YYHomeRecorderForthTableViewCell : UITableViewCell<WFCoretextDelegate>

@property (nonatomic, strong) UIImageView *h_leftImg;

@property (nonatomic, strong) UILabel *h_contentLable;

@property (nonatomic, strong) UILabel *h_timeLable;



@property (nonatomic, strong) NSMutableArray *h_dataArray;



/*

 * 是侧滑出现的右视图时,h_rightImg出现,h_timeLable隐藏

 *

 */

@property (nonatomic, strong) UIButton *h_rightImgButton;

- (void)handelData:(YYDynamicResultListModel *)resultListModel;

@end



@interface YYHomeRecorderLeftViewTableViewCell : UITableViewCell

@property (nonatomic, strong) UILabel * H_leftLable;

@property (nonatomic, strong) UIButton *h_arrowButton;

@end


4, OC开发常用的其他设计模式

4.1单例:通常用于用户数据、行为的管理

+ (id)shareUserManager{

    //Singleton instance

    static SCRMUserManager *userManager;

   

    //Dispatching it once.

    static dispatch_once_t onceToken;



    dispatch_once(&onceToken, ^{

        //  Initializing keyboard manger.

        userManager = [[self alloc] init];

        userManager.clientDevice = [[YYClientDeviceModel alloc] init];

        userManager.accountInfo = [[AccountInfoModel alloc] init];

    });

   

    //Returning kbManager.

    return userManager;

}

4.2 观察者:类互相依赖,或一对多,接受响应的情况下使用。NSNotificationCenter && NSNotification 同名配对使用,如: 

[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_LOGIN_LOGOUT object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginOrLogout:) name:NOTIFICATION_LOGIN_LOGOUT object:nil];

4.3代理:为其他对象提供一种代理以控制对这个对象的访问。
4.3.1 a类实现了b类里定义的协议,那a类实际上也是b类型的代理。
4.3.2 代理和BLOCK的选择(详见:website upgrading…)

  •   a如果对象有超过一个以上不同的事件源,使用delegation。
  •   b如果一个对象是单例,不要使用delegation”
  •   c如果对象的请求带有附加信息,更应该使用delegation
  •  d如果你使用block去请求一个可能失败的请求,你应当只使用一个block。如下:
[fetcher makeRequest:^(id result) {

                               // do something with result

} error:^(NSError *err) {

                                // Do something with error

}];

 三、设计模式与代码重构


设计模式是封装、继承、与多态,是编写代码之前需要考虑的问题。在编写代码过程中,特别是明捷开发的过程中会有很多重复代码出现,既增加了代码导致可读性减弱,再需求变更时往往由于编写过程中代码没有注释,越来越多的代码在一个方法里堆叠,导致最终只有编写代码的人才能读懂代码,并进行修改。

1,方法传参

只有参数(特别是参数为类对象时)是变动的时候,才需传参,否则便可放入方法里,在调用时减少获取参数进行传递。如:

UIBarButtonItem *item = [self.navigationItem.rightBarButtonItems objectAtIndex:0];

UIButton *buttonItem = item.customView; 

[self rightViewHidden:buttonItem];

- (void)rightViewHidden:(UIButton *)sender {

…

}

 这里该这么写, 

[self rightViewHidden]

- (void)rightViewHidden {

    UIBarButtonItem *item = [self.navigationItem.rightBarButtonItems objectAtIndex:0];

    UIButton *buttonItem = item.customView;

    …

}

   2,数据、视图初始化

- (void)viewDidLoad {

    [super viewDidLoad];

  

    [self initData];

    [self initView];

}

 3减少没有意义的参数

如正整形数据类型TAG等,定义有意义的名称进行代替:

typedef NS_ENUM(NSUInteger, HeaderViewTag) {

    HeaderViewTagBackImage = 400,     // 背景图

    HeaderViewTagImportantImage,//重要生意图标

    HeaderViewTagBussinessName, // 生意名称

    HeaderViewTagBussinessAmount,// 生意金额

    HeaderViewTagBussinessAvatar,// 客户头像

    HeaderViewTagBussinessCustomer,// 客户名称

    HeaderViewTagDiscoverTime, // 发现日期

    HeaderViewTagEstimateTime   // 预计成交日期

};

 4,简化中间过程增加公用类可扩展性


YYTypeChooseViewController的tableView.frame计算:

- (UITableView *)tableView
{
    if (!_tableView) {
        _tableView = [[UITableView alloc] init];
        
    if([self needObjNumber]){
        if (_objType == YYObjTypeClue) {
            _tableView.frame = CGRectMake(0,- (kTypeCellHeight*3)-64, self.view.width, kTypeCellHeight *3);
        } else {
            if (_isMaster) {
                _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kTypeCount)-64, self.view.width, kTypeCellHeight *kTypeCount);

            }else{

                _tableView.frame = CGRectMake(0, - (kTypeCellHeight*(kTypeCount-1))-64, self.view.width, kTypeCellHeight *(kTypeCount-1));

            }
        }
    }else if(_objType == YYObjTypeBriefing){
        _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kBriefingTypeCount)-64, self.view.width, kTypeCellHeight *kBriefingTypeCount);
    }else if (_objType == YYObjTypeRelationCustomer){
        _tableView.frame = CGRectMake(0,- (kTypeCellHeight*kRelaitonCount), self.view.width, kTypeCellHeight *kRelaitonCount);
    }else if(_objType == YYObjTypeBusinessDept || _objType==YYObjTypeLifeForm || _objType == YYObjTypeBusinessStage){
        _tableView.frame = CGRectMake(0, -(kTypeCellHeight*kBusinessDeptTypeCount)-64, self.view.width, kTypeCellHeight *kBusinessDeptTypeCount);
    }else if (_objType == YYObjTypeAttList || _objType == YYObjTypeAttListTa){
        _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kBriefingTypeCount)-64, self.view.width, kTypeCellHeight *kBriefingTypeCount);
    }else if (_objType == YYObjTypeAttChartAll ||_objType == YYObjTypeAttChartExternal||_objType == YYObjTypeAttChartInner)
    {
        _tableView.frame = CGRectMake(0, - (kTypeCellHeight*2)-64, self.view.width, kTypeCellHeight *2);
    }


    _tableView.tableFooterView = [UIView new];

    _tableView.delegate = self;

    _tableView.dataSource = self;

    _tableView.backgroundColor = [UIColor colorWithRed:255.0 green:255.0 blue:255.0 alpha:0.5];

    }

    return _tableView;
    
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    self.navigationController.navigationBar.translucent = NO;


    [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{

        if([self needObjNumber]){
            if (_objType == YYObjTypeClue) {

                _tableView.frame = CGRectMake(0,- (kTypeCellHeight*3)-64, self.view.width, kTypeCellHeight *3);

            } else {

            if (_isMaster) {

                _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kTypeCount)-64, self.view.width, kTypeCellHeight *kTypeCount);

            }else{

                _tableView.frame = CGRectMake(0, - (kTypeCellHeight*(kTypeCount-1))-64, self.view.width, kTypeCellHeight *(kTypeCount-1));

            }
        }

        }else if(_objType == YYObjTypeBriefing){

            _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kBriefingTypeCount)-64, self.view.width, kTypeCellHeight *kBriefingTypeCount);

        }else if (_objType == YYObjTypeRelationCustomer){

            _tableView.frame = CGRectMake(0,- (kTypeCellHeight*kRelaitonCount), self.view.width, kTypeCellHeight *kRelaitonCount);

        }else if(_objType == YYObjTypeBusinessDept || _objType==YYObjTypeLifeForm || _objType == YYObjTypeBusinessStage){

            _tableView.frame = CGRectMake(0, -(kTypeCellHeight*kBusinessDeptTypeCount)-64, self.view.width, kTypeCellHeight *kBusinessDeptTypeCount);

        }else if (_objType == YYObjTypeAttList || _objType == YYObjTypeAttListTa){

            _tableView.frame = CGRectMake(0, - (kTypeCellHeight*kBriefingTypeCount)-64, self.view.width, kTypeCellHeight *kBriefingTypeCount);

        }else if (_objType == YYObjTypeAttChartAll ||_objType == YYObjTypeAttChartExternal||_objType == YYObjTypeAttChartInner)
        {
            _tableView.frame = CGRectMake(0, - (kTypeCellHeight*2)-64, self.view.width, kTypeCellHeight *2);
        }

    } completion:^(BOOL finished) {
    }];
}

此类起初只有两三个显示类型,后来随着开发的深入,显示类型越来越多,判断开始变得越来越复杂。认真查看代码,此处不过根据各种类型以及各种类型时用友显示的controller的导航栏是否透明,来计算tableView高度,计算中心点位置,从而使tableView处于正确位置。于是重构为: 

- (UITableView *)tableView

{

    if (!_tableView) {
        _tableView = [[UITableView alloc] init];
        CGRect frame = self.view.frame;
        [_tableView setFrame:CGRectMake(0, -frame.size.height, frame.size.width, frame.size.height)];
        _tableView.tableFooterView = [UIView new];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.backgroundColor = [UIColor clearColor];
    }

    return _tableView;

}

 

- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    self.navigationController.navigationBar.translucent = NO;
    [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{

        CGRect tableViewFrame = self.tableView.frame;

        if ([self navBarAlphaIsZero]){
            tableViewFrame.origin.y = 64;
        }
        else{
            tableViewFrame.origin.y = 0;
        }

        [self.tableView setFrame:tableViewFrame];

    } completion:^(BOOL finished) {       
    }];

}

此类起初只有两三个显示类型,后来随着开发的深入,显示类型越来越多,判断开始变得越来越复杂。认真查看代码,此处不过根据各种类型以及各种类型时用友显示的controller的导航栏是否透明,来计算tableView高度,计算中心点位置,从而使tableView处于正确位置。于是重构为:  

//导航栏是否透明

- (BOOL)navBarAlphaIsZero {

    return (self.objType == YYObjTypeCustomer || self.objType == YYObjTypeBusiness || self.objType == YYObjTypeContact || self.objType == YYObjTypeBriefing || self.objType == YYObjTypeAttList ||self.objType == YYObjTypeAttListTa);

}

- (UITableView *)tableView

{

    if (!_tableView) {
        _tableView = [[UITableView alloc] init];
        CGRect frame = self.view.frame;
        [_tableView setFrame:CGRectMake(0, -frame.size.height, frame.size.width, frame.size.height)];
        _tableView.tableFooterView = [UIView new];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.backgroundColor = [UIColor clearColor];

    }

    return _tableView;

}

 

- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    self.navigationController.navigationBar.translucent = NO;
    [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{

        CGRect tableViewFrame = self.tableView.frame;
        if ([self navBarAlphaIsZero]){
            tableViewFrame.origin.y = 64;
        }
        else{
            tableViewFrame.origin.y = 0;
        }
        [self.tableView setFrame:tableViewFrame];
    } completion:^(BOOL finished) {
    }];

}

 
这里简单举几个重构的例子。“设计模式”是在编写代码前应该考虑的问题,而“重构”则是在代码编写过程中,功能实现完成后,去优化。如果说编程界只有一个真理,那一定是“改变”,我们应该计划改变,迎接改变,同时挑战改变增强代码可读性,这样才能不断提高自己作为一名程序员的职业素养的一件事。

 



参考书目:
《Head First, Design patterns》、《Objectvie-C 编程之道,iOS设计模式解析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

群野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值