《52个有效方法》笔记4——接口与API设计

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 

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 

@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 

@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 

@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;
        }

    }
}

你可能感兴趣的:(《52个有效方法》笔记4——接口与API设计)