OC中的类簇模式:
OC中的类簇模式和工厂设计模式的思想是一致的。就是若有几个功能接近的类,我们可以给它们定义一个公共的“抽象类”。使用者只需要和这个公用的抽象类来打交道就行了,而不必关心具体某类的细节。
“工厂模式”这个名字就非常形象:我们可能会使用“刀”,“斧”,“锤子”等各种工具,人类一开始都是自产自用的,也就是说自己造刀斧等工具,但是这样的话使用者得了解、掌握各种工具的生产方法或者使用方式,这对使用者而言是及其麻烦的。社会渐渐发展,开始出现了专门制造这些个工具的“工厂”,只需要告诉它我们需要什么样的工具,工厂就会给我们什么样的工具。比如我想要把锤子,只需要告诉工厂,我需要的工具是一把锤子,那工厂内部会开动它们的“锤子”生产部门来生产出锤子。
** 看个代码例子: **
Staff类就是个工厂类,它是“抽象基类”,我们需要返回什么样的员工,只需要传入员工类型type参数就行了,具体是怎么生成的,我们不必耗费精力去关心,Staff这个工厂类内部会去实现,我们只需等着结果就行了。
#import "ViewController.h"
#import "Staff.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Staff *staff = [Staff staffWithType:StaffType_Coder];
[staff doSomework];
NSLog(@"----%@----",[staff class]);
}
@end
// Staff.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, StaffType)
{
StaffType_Coder,
StaffType_Designer,
};
@interface Staff : NSObject
// 传入type参数,生成不同类型的员工
+ (Staff *)staffWithType:(StaffType)type;
// 做一些工作...
- (void)doSomework;
@end
// Staff.m
#import "Staff.h"
#import "CoderStaff.h"
#import "DesignerStaff.h"
@implementation Staff
// 传入type参数,生成不同类型的员工
+ (Staff *)staffWithType:(StaffType)type
{
switch (type)
{
case StaffType_Coder:
return [CoderStaff new];
break;
case StaffType_Designer:
return [DesignerStaff new];
break;
default:
break;
}
}
// 做一些工作...
- (void)doSomework
{
NSLog(@"the staff is working...");
}
// 为了避免调用者直接调用基类的初始化方法,可以返回nil,或者抛出异常提醒调用者。
- (instancetype)init
{
return nil;
}
@end
所谓设计模式,就是在长期实践过程中琢磨出的更好的一种方式,不是说非使用设计模式不可,非用设计模式其他方式不可实现。其实像上面的例子,我们完全可以把CoderStaff、DesignerStaff这些类提供给调用者,在其中分别实现相应的初始化方法。但是这样的话,调用者就得了解每个类提供的方法等,而“类簇”妙就妙在让调用者不必劳心关注太多细节,需要什么告诉我,我给你。
关于初始化方法
我们通常在自己写的独立的控件等时需要给外部提供调用接口,当然首要的是初始化方法,而且通常要提供多个初始化方法。
其中,我们应定义一个“全能初始化方法”,即该初始化方法能为对象提供足够的信息以完成创建。而其他初始化方法在内部其实都调用它。
// Rectangle.h
#import <Foundation/Foundation.h>
@interface Rectangle : NSObject
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
- (instancetype)initWithWidth:(float)width andHeight:(float)height;
@end
// Rectangle.m
#import "Rectangle.h"
@implementation Rectangle
- (instancetype)init
{
return [self initWithWidth:100.f andHeight:20.f];
}
- (instancetype)initWithWidth:(float)width andHeight:(float)height
{
self = [super init];
if(self)
{
_width = width;
_height = height;
}
return self;
}
@end
Rectangle类提供了initWithWidth:andHeight:
这个初始化方法(它算该类的全能初始化方法)。但是不排除调用者调用了init
这个模式初始化方法,所以我们重写它,在其内部调用全能初始化方法,传入默认值的参数。
假如,此时又定义了一个子类Square。它继承于基类Rectangle。
// Square.h
#import "Rectangle.h"
@interface Square : Rectangle
- (instancetype)initWithLength:(float)length;
@end
Square.h暴露给外部一个initWithLength:
的初始化方法。
// Square.m
#import "Square.h"
@implementation Square
- (instancetype)init
{
self = [super init];
return [self initWithLength:100.f];
}
// 重写父类的初始化方法
- (instancetype)initWithWidth:(float)width andHeight:(float)height
{
float resultLength = MAX(width, height);
return [self initWithLength:resultLength];
}
- (instancetype)initWithLength:(float)length
{
return [self initWithWidth:length andHeight:length];
}
@end
需要注意的是,既然Square继承于基类Rectangle,那调用者也有调用基类的initWithWidth:andHeight:
这个初始化方法的可能性。要么,我们可以像上面一样,返回一个以width和height俩较大值为边长的正方形。但也可从另外一个角度想:认为这属于调用者调用错误,而抛出异常提醒调用者不该使用父类的初始化方法。
尽量使用不可变对象。
“尽量使用不可变对象”旨在安全考量。
** 有时我们只想在类内部修改数据,但不想令这些数据为外人所改动这时,我们可以把暴露给外部的属性声明为readonly,但在内部重新声明为readwrite。**
// Staff.m
#import <Foundation/Foundation.h>
@interface Staff : NSObject
@property (nonatomic, copy, readonly)NSString *staffId; // 工号
@property (nonatomic, copy, readonly)NSString *salay; // 薪水
@end
// Staff.m
#import "Staff.h"
#import "CoderStaff.h"
#import "DesignerStaff.h"
@interface Staff ()
@property (nonatomic, copy, readwrite)NSString *staffId;
@property (nonatomic, copy, readwrite)NSString *salay;
@end
@implementation Staff
@end
在Staff.m的Extension(拓展)中重新声明属性为readwirite。现在,只能在Staff内部设置这些属性值,外部仅是可读的。
** 注意 :** 即便上面这样写了,但是还是有法子在外部修改属性值的,比如可以使用KVC修改属性值。不过,没有绝对的安全,上面这样写总归要规范严谨得多。
- (void)viewDidLoad
{
[super viewDidLoad];
Staff *staff = [[Staff alloc] init];
[staff setValue:@"10000.00" forKey:@"salary"];
NSLog(@"salary----%@",staff.salary);
}
// 2016-03-11 00:39:34.791 WangDemo310[5844:106406] salary----10000.00
** 不要把可变的集合类作为属性公开,而应该提供相关方法,以此来修改对象中可变的集合类 **
比如下面Person类,它是可以进行添加、删除好友操作的,但是作为属性暴露给外部的是一个不可变的(只可读的)的NSArray,而在内部却是
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy)NSString *personId;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, strong)NSArray *myFriendsArr;
- (instancetype)initWithPerson:(NSString *)personId andName:(NSString *)name;
- (void)addFriend:(Person *)perspon;
- (void)removeFriend:(Person *)person;
@end
// Person.m
#import "Person.h"
@interface Person ()
{
NSMutableArray *_friendsMarr; // 内部维护的其实是个可变的数组
}
@end
@implementation Person
- (instancetype)initWithPerson:(NSString *)personId andName:(NSString *)name
{
self = [super init];
if(self)
{
_personId = personId;
_name = name;
_friendsMarr = [[NSMutableArray alloc] init];
}
return self;
}
- (NSArray *)myFriendsArr
{
return [_friendsMarr copy]; // _friendsMarr本是可变的,进行copy操作,成为不可变的,然后返回给外部。
}
- (void)addFriend:(Person *)perspon
{
[_friendsMarr addObject:perspon];
}
- (void)removeFriend:(Person *)person
{
[_friendsMarr removeObject:person];
}
@end
在Peson内部维护的其实是个可变的NSMutableArray,可以进行add和remove操作,但通过copy动作后暴露给外部却是一个不可变的NSArray。这和第一个例子的思想其实是一致的:** 只给外部暴露需要暴露的,而且暴露的东西得不可变,外人不能更改。 **
关于OC中的错误、异常
首先,OC中的异常应该尽量少用,因为“自动引用计数”不是异常安全的。即当抛出异常时,本应在作用域末尾进行释放的代码便不会执行了,这就会造成内存泄露。
所以,异常只应该用于极其严重的错误,比如本应使用子类方法,调用者却使用了“抽象类”基类的方法,这时应该重写基类的这些方法,在其中抛出异常,提醒调用者不能调用基类的方法。
对于一般性错误,可以返回nil和或者0什么的提示调用者这样做有问题。
而NSError,可以把导致错误的信息回馈给调用者。
NSError常见用法:
1.在代理方法或者block回调中把错误信息传递给调用者;
2.经由方法的“输出参数”返回给调用者,(NSError **)指向指针的指针。
- (BOOL)doSomething:(NSError **)error;
传递给方法的参数是个指针,该指针又指向另外一个指向NSError对象的指针。或者也可以把它当成一个直接指向NSError对象的指针。
** 该方法一般返回BOOL类型的结果,表示操作成功与否。这么一来,该方法既可以返回表示该操作成功与否的bool值,而且还能经由“输出参数”把NSError对象回传给调用者,告诉调用者该操作返回NO的错误细节。**
// 调用
NSError *error = nil;
BOOL result = [self doSomething:&error];
if(error){
// 说明有错误,此时result一般为NO
NSLog(@"错误细节:%@",error.domain);
}
- (BOOL)doSomething:(NSError **)error
{
if(/* there was a error*/1) // 当出现错误时
{
if(error){ // 并且存在error参数
*error = [NSError errorWithDomain:@"xxx" code:-1 userInfo:nil]; // 生成error对象
return NO;
}else{
return YES;
}
}
}