Kenny 肉桂的主页

记录自己的进步


  • 首页

  • 归档

  • 标签

  • 搜索
close

自用提高效率的三方框架

发表于 2015-08-13   |   分类于 开发环境相关   |  

CXPhotoBrowser

项目地址

功能比较完善的图片浏览器,使用方式符合大众使用习惯。作者说是收到MWPhotoBrowser的启发

功能点:

  • 自定义的导航条
  • 自定义工具条
  • 网络下载图片能力,占位图片

但是已经好久没有更新了,但是个人在使用中过程中没有发现什么问题

MWPhotoBrowser

项目地址

一个简单易用的图片、视频浏览器。可定义网格、标题和 选择范围

MagicalRecord

项目地址

对 ‘CoreData’ 的封装,非常简单易用,避免了CoreData繁琐的使用方式

CocoaLumberjack

项目地址

配合Xcode color插件,让控制台打印带色彩,根据不同的级别,配置不同的色彩。

DateTools

项目地址

简化日期操作的分类

Masonry

项目地址

不多说,手码一族必备布局库

MBProgressHUD

项目地址

提示用户框

MJRefresh

项目地址

一句话给ScrollView及其子类(UITableview UICollectionView 等)添加上拉下拉刷新功能

SDWebImage

项目地址

加载网络图片,本地图片缓存神器

VPImageCropper

项目地址

图片裁剪框架

我项目中Layout模块的解析

发表于 2015-08-12   |   分类于 重构、模式、架构   |  

context

从上次确定使用抽象工厂模式, 到最终项目实现,采用了建造者模式. 因为如果使用抽象工厂模式,将每个模块的layout抽象, 会导致产品体系非常复杂, 难以使用和维护.

阅读全文 »

从简单工厂到工厂模式

发表于 2015-08-10   |   分类于 重构、模式、架构   |  

context

上次的文章,用简单工厂,解决了生产布局类的问题. 但是,上次的方式针对一个模块是可以的,当模块多了,每个类负责的属性就太多了.这是一个需要重构的点.

准备工作

首先,将类重命名, 比如 原来的:

GUILayoutFactory -> GUILoginLayoutFactory

原来的布局产品名称也是应该修改的,但是就不贴出来了,一个原则就是:不要让类名把你自己弄迷糊了即可.

重构

下面是我工厂模式的结构图,目前工厂只有 Login这一个,项目中有更多,但是没必要都在此列举.

文件说明图

当把图画出来, 很容易看出来, 需要进行重构了. 因为在 GUILoginLayoutFactory 存在着逻辑判断代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)


typedef NS_ENUM(NSUInteger, GUIDeviceType) {

GUIDeviceType4S = 1,
GUIDeviceType5,
GUIDeviceType6,
GUIDeviceType6P,
GUIDeviceTypeNotFound

};

上面的代码是当时在应用简单工厂的时候,将逻辑下放到工厂中得.现在它存在于我们的 GUILoginLayoutFactory中,这个工厂还会有同级的兄弟工厂.它们也需要这种判断来生产具体的 layou产品,比如GUIDiaryLayoutFactory,那么,现在应该采用将这些代码提升到父类的重构手法. GUILoginLayoutFactory 的父类是我们的 “ 总工厂 “ 也就是 GUILayoutFactory

现在 GUILayoutFactory的头文件代码是:

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
#import <Foundation/Foundation.h>
@class GUILayout;

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)


typedef NS_ENUM(NSUInteger, GUIDeviceType) {

GUIDeviceType4S = 1,
GUIDeviceType5,
GUIDeviceType6,
GUIDeviceType6P,
GUIDeviceTypeNotFound

};

@interface GUILayoutFactory : NSObject

-(GUILayout *)layout;
+(GUILayout *)layout;

@end

养成好习惯,每次小重构后都进行编译.

客户端调用代码:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];

GUILayoutFactory * layoutFactory = [GUILoginLayoutFactory new];
GUILayout * layout = [layoutFactory layout];

NSLog(@"\n %f \n %f \n %f \n %f",layout.textFieldWidth,layout.textFieldHeight,layout.loginButtonWidth,layout.loginButtonHeight);
}

是的, 调用端需要知晓具体的工厂.比如这里的 GUILoginFactory才能拿到具体的产品.

对比简单工厂

原来使用简单工厂实现的时候,如果要增加新产品.那么需要:

  1. 添加具体的产品(Layout类)
  2. 在工厂类中增加一个分支语句

客户端是无需修改和知道工厂内部的细节的.

现在使用工厂模式,如果要增加新产品:

  1. 添加具体的产品(Layout类)
  2. 添加具体产品的工厂类(LayoutFactory类)
  3. 客户端进行判断和需要知晓新的工厂类

加重了客户端的负担.

总结来说:

  • 简单工厂最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择(或者是其他条件)动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖 .

  • 但是如果添加新产品,要修改工厂类,违背了”开放-封闭原则”

  • 工厂模式让子类工厂决定实例化哪个类.让产品类的实例化延迟到了子工厂.

  • 新增加产品的时候, 整个工厂的体系是没有修改的,只是扩展出一个子类.

  • 工厂模式,客户端需要知晓具体的工厂子类

两者没有好坏, 我们需要根据自己的实际情景来选择模式进行应用.

当遇到严格要求的UI-实现

发表于 2015-08-09   |   分类于 重构、模式、架构   |  

说明

因为公司项目负责,所以这个代码将用Demo的形式建立,然后引入到项目中.然后对项目的调用进行重构.

创建项目文件

文件清单:

  • 产品父类 GUILayout 包含各种属性
  • iPhone4s/4 布局类 GUIIP4SLayout 继承 GUILayout
  • iPhone5s/5 布局类 GUIIP5Layout 继承 GUILayout
  • iPhone6 布局类 GUIIP6Layout 继承 GUILayout
  • iPhone6 plus 布局类 GUIIP6PLayout 继承 GUILayout
  • 工厂类 GUILayoutFactory

代码详解

首先是 GUILayout,它定义了所有需要的属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>

@interface GUILayout : NSObject

@property (assign, nonatomic) CGFloat textFieldHeight;
@property (assign, nonatomic) CGFloat textFieldWidth;
@property (assign, nonatomic) CGFloat loginButtonWidth;
@property (assign, nonatomic) CGFloat loginButtonHeight;

@end

#import "GUILayout.h"

@implementation GUILayout

@end

它的布局子类,在 init方法中,对布局属性进行了初始化,因为代码几乎一样,所以拿一个类说明:(为了以后区分,我给不同的布局类不同的值:
4/4s 值都是 4 ,5/5s 值都是5 ,6 值都是6 ,6 plus值都是 60)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "GUILayout.h"

@interface GUIIP4SLayout : GUILayout

@end

#import "GUIIP4SLayout.h"

@implementation GUIIP4SLayout

- (instancetype)init {
if (self = [super init]) {

self.textFieldHeight = 4;
self.textFieldWidth = 4;
self.loginButtonHeight = 4;
self.loginButtonWidth = 4;

}
return self;
}

@end

然后是工厂类:

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
#import <Foundation/Foundation.h>
@class GUILayout;
@interface GUILayoutFactory : NSObject

-(GUILayout*)createLayout;
+(GUILayout*)createLayout;


@end


#import "GUILayoutFactory.h"
#import "GUIIP4SLayout.h"
#import "GUIIP6Layout.h"
#import "GUIIP6PLayout.h"
#import "GUIIP5Layout.h"

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)


typedef NS_ENUM(NSUInteger, GUIDeviceType) {

GUIDeviceType4S = 1,
GUIDeviceType5,
GUIDeviceType6,
GUIDeviceType6P,
GUIDeviceTypeNotFound

};

@implementation GUILayoutFactory

-(GUILayout*)createLayout{

switch ([self deviceType]) {
case GUIDeviceType4S:
return [GUIIP4SLayout new];
break;
case GUIDeviceType6:
return [GUIIP6Layout new];
break;
case GUIDeviceType6P:
return [GUIIP6PLayout new];
break;
case GUIDeviceType5:
return [GUIIP5Layout new];
case GUIDeviceTypeNotFound:
return nil;

}

return nil;


}

+(GUILayout*)createLayout{

return [[GUILayoutFactory new] createLayout];

}

-(GUIDeviceType)deviceType{

if (kisiPhone4) {
return GUIDeviceType4S;
}else if(kisiPhone5){
return GUIDeviceType5;
}else if(kisiPhone6){
return GUIDeviceType6;
}else if(kisiPhone6Plus){
return GUIDeviceType6P;
}else{
return GUIDeviceTypeNotFound;
}

}
@end
  • 将判断逻辑下放到工厂类中,简化了控制器的调用
  • 定义根据屏幕尺寸判断设备类型的宏
  • 根据枚举类型决定返回的layout类型

结语

好了,这就是完整的代码了.根据自己的需求,修改参数名,可以方便的用到自己的系统中.

当遇到严格要求的UI-模式选择

发表于 2015-08-09   |   分类于 重构、模式、架构   |  

context

公司最近的一个项目,在UI上有比较严格的要求: 每种屏幕下地控件的尺寸要求绝对的精确

实现方案

使用的纯代码 ,加上要适配 iOS 7 ,所以 Size-Class 就不用考虑了.

分析情况,其实控件的相对位置是固定的.开始也是用了 Masonry,只是需要提供不同屏幕下地控件尺寸,间距等内容.

如何更优雅可拓展的实现

当然,你可以在布局的时候,写if-else 和 switch-case来对不同屏幕的尺寸参数赋不同的值. 可是如果以后要修改呢,苹果要抽风推出了新尺寸的iPhone,而你被要求去适配呢? 你岂不是要找遍整个项目修改原来的分支判断.
就是说,以上的实现方式:

  • 维护性差 人工维护成本高,也破坏了开闭原则
  • 扩展性差 如果有新的要求,需要逐个重写.

设计模式选择

对于这种情况,一开始首先是想到了策略模式,因为我考虑每种屏幕都可以算作是一种方案.而至于策略模式是行为模式,也当然可以通过重构技巧把属性替换为方法,完成策略的行为需求.我兴冲冲的绘制了草稿(OC没有合适的UML类图工具,所以用了普通的绘图工具,线条种类不足以完全表示UML类图中得关系)

策略模式

为了规避策略模式需要让客户端(调用者)知道具体策略的缺点(我认为是缺点),我采用重构手法将逻辑下放到context,DeviceType能决定具体的策略,算是简单工厂和策略模式的结合.

但是在绘图的过程中,想到,这有点算过度设计了, 其实我只是需要一个能够保持属性的对象,然后在布局的时候,使用它 ,使用策略模式,意味着我需要把所有的属性的 getter实现,才算符合策略的本意.
这次是一次失败的案例:

  • 对这种级别的方法使用策略,大材小用,无缘故的增加复杂度而已
  • 一开始就不应该偏执的考虑将属性重构为方法,这里只是简单地赋值,不需要拦截其 get 过程

然后想到了,建造者模式: 创建型的模式,很符合我要个布局对象的需求,也给他弄上了个简单工厂,避免了在控制器中出现分支语句:

没画出具体的产品(布局),请见谅.

但是, 这里又有一个问题: 在我以前的经验中, 建造者模式是用来区分过程固定,但是有需要多样化的建造方式的. 在我的场景中,我最复杂的情况也仅仅是赋值而已, 所以,也是不适合的.

最终方案

其实最简单的,往往是最好的.最重要的是弄明白自己需要的. 我仅仅需要几个承载着不同属性的对象.最合适的方案应该是 简单工厂,而且可以把逻辑下放到简单工厂中.这模式太简单了,就不画图了.

总结

在设计模式的选择,甚至是去除设计模式的过程中,需要开发者有足够的经验来做决定. 今天就走了一个弯路,好在及时发现了.如果用策略模式或者建造者编码,将来的维护成本肯定要上升.

下一篇中,将用代码实现.

通过重构实现模板方法模式(Template Method ) 模式篇

发表于 2015-08-06   |   分类于 重构、模式、架构   |  

书接上文

上次使用了一些简单地重构方法,得到的最终代码如下:

阅读全文 »
1…678…13
桂庆

桂庆

Kenny 肉桂的主页 记录自己的进步

75 日志
17 分类
23 标签
RSS
微博
© 2013 - 2017 桂庆
由 Hexo 强力驱动
主题 - NexT.Mist