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

    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容