从 C++ 到 Objective-C(7):继承

简单继承

Objective-C 也有继承的概念,但是不能多重继承。不过,它也有别的途径实现类似多重继承的机制,这个我们后面会讲到。

C++ Objective-C
class Foo : public Bar,
            protected Wiz
{
}
@interface Foo : Bar // 单继承
// 如果要同时“继承” Wiz,需要使用另外的技术
{
}
@end

在 C++ 中,一个类可以继承自一个或多个类,使用publicprotected以及private修饰符。子类的函数如果要调用父类的版本,需要使用::运算符,例如Bar::Wiz::等。

在 Objective-C 中,一个类只能继承一个父类,并且只能是public的(这和 Java 是一致的)。同样类似 Java,如果你要在子类中调用父类的函数,需要使用super

多重继承

Java 同样不允许多重继承。但是它提供了interface来模拟多重继承。类似的,Objective-C 也有同样的机制,这就是协议protocol和分类 categories。我们将在后面的内容详细讲述这两种技术。

虚拟性

虚方法

在 Objective-C 中,所有方法都是虚的,因此,没有virtual关键字或其等价物。

虚方法重定义

在 Objective-C 中,你可以定义一个没有在@interface块里面声明的方法。但这并不是一种替代private的机制,因为这种方法实际是能够被调用的(回想下,Objective-C 中方法的调用是在运行期决定的)。不过,这确实能够把接口定义变得稍微干净了一些。

这并不是一种坏习惯,因为有时你不得不重定义父类的函数。由于所有方法都是虚的,你无需像 C++ 一样在声明中显式写明哪些函数是virtual的,这种做法就成为一种隐式的重定义。很多继承自NSObject的方法都是是用这种方法重定义的。例如构造方法init,析构方法deallocUIView类的drawRect:等等。这样的话,接口就变得更简洁,更易于阅读。不好之处就是,你不能知道究竟哪些方法被重定义了。

纯虚方法则是使用正式协议 formal protocols 来实现。

虚继承

Objective-C 中不允许多重继承,因此也就没有虚继承的问题。

协议

Java 和 C# 使用接口 interface 的概念来弥补多重继承的不足。Objective-C 也使用了类似的机制,成为协议 protocol。在 C++ 中,这种概念是使用抽象类。协议并不是真正的类:它只能声明方法,不能添加数据。有两种类型的协议:正式的 formal 和非正式的 informal。

正式协议

正式协议的方法,所有实现这个协议的类都必须实现。这就是一种验证,也就是说,只要这个类说实现这个协议,那么它肯定可以处理协议中规定的方法。一个类可以实现任意多个协议。

C++

class MouseListener
{
public:
    virtual bool mousePressed(void) = 0; // 纯虚方法
    virtual bool mouseClicked(void) = 0; // 纯虚方法
};

class KeyboardListener
{
public:
    virtual bool keyPressed(void) = 0; // 纯虚方法
};

class Foo : public MouseListener,  public KeyboardListener {...}
// Foo 必须实现 mousePressed, mouseClicked 和 keyPressed
// 然后 Foo 就可以作为鼠标和键盘的事件监听器

Objective-C

@protocol MouseListener

-(BOOL) mousePressed;
-(BOOL) mouseClicked;

@end

@protocol KeyboardListener

-(BOOL) keyPressed;

@end

@interface Foo : NSObject <MouseListener, KeyboardListener>
{
...
}
@end
// Foo 必须实现 mousePressed, mouseClicked 和 keyPressed
// 然后 Foo 就可以作为鼠标和键盘的事件监听器

C++ 中,协议可以由抽象类和纯虚函数实现。C++ 的抽象类要比 Objective-C 的协议强大的多,因为抽象类可以带有数据。

Objective-C 中,协议是一个特殊的概念,使用尖括号 <…> 表明。注意,尖括号在 Objective-C 中不是模板的意思,Objective-C 中没有类似 C++ 模板的概念。

一个类也可以不经过协议声明,直接实现协议规定的方法。此时,conformsToProtocol:方法依然返回NO。出于性能考虑,conformsToProtocol:方法只检查类接口的声明,不会一个方法一个方法的对比着检查。conformsToProtocol:的返回值并不会作为是否调用方法的依据。下面是这个方法的原型:

-(BOOL) conformsToProtocol:(Protocol*)protocol
// Protocol 对象可以由 @protocol(协议名) 返回

实现了正式协议的对象的类型同协议本身是兼容的。这一机制可以作为协议的筛选操作。例如:

// 下面方法是 Cocoa 提供的标准方法
// 方法参数可以是任意类型 id,但是必须兼容 NSDraggingInfo 协议
-(NSDragOperation) draggingEntered:(id )sender;

可选方法

有时我们需要这么一种机制:我们的类需要实现一部分协议中规定的方法,而不是整个协议。例如在 Cocoa 中,代理的概念被广泛使用:一个类可以给定一个辅助类,由这个辅助类去完成部分任务。

一种实现是将一个协议分割成很多小的协议,然后这个类去实现一个协议的集合。不过这并不具有可操作性。更好的解决方案是使用非正式协议。在 Objective-C 1.0 中就有非正式协议了,Objective-C 2.0 则提出了新的关键字@optional@required,用以区分可选方法和必须方法。

@protocol Slave

@required // 必须部分
-(void) makeCoffee;
-(void) duplicateDocument:(Document*)document count:(int)count;

@optional // 可选部分
-(void) sweep;

@required // 又是一个必须部分
-(void) bringCoffee;
@end

非正式协议

非正式协议并不是真正的协议,它对代码没有约束力。非正式协议允许开发者将一些方法进行归类,从而可以更好的组织代码。所以,非正式协议并不是协议的宽松版本。另外一个相似的概念就是分类。

让我们想象一个文档管理的服务。假设有绿色、蓝色和红色三种文档,一个类只能处理蓝色文档,而Slave类使用三个协议manageBlueDocuments,manageGreenDocumentsmanageRedDocumentsSlave可以加入一个分类DocumentsManaging,用来声明它能够完成的任务。分类名在小括号中被指定:

@interface Slave (DocumentsManaging)

-(void) manageBlueDocuments:(BlueDocument*)document;
-(void) trashBlueDocuments:(BlueDocument*)document;

@end

任何类都可以加入DocumentsManaging分类,加入相关的处理方法:

@interface PremiumSlave (DocumentsManaging)

-(void) manageBlueDocuments:(BlueDocument*)document;
-(void) manageRedDocuments:(RedDocument*)document;

@end

另一个开发者就可以浏览源代码,找到了DocumentsManaging分类。如果他觉得这个分类中有些方法可能对自己,就会检查究竟哪些能够使用。即便他不查看源代码,也可以在运行时指定:

if([mySlave respondsToSelector:@selector(manageBlueDocuments:)])
    [mySlave  manageBlueDocuments:document];

严格说来,除了原型部分,非正式协议对编译器没有什么意义,因为它并不能约束代码。不过,非正式协议可以形成很好的自解释性代码,让 API 更具可读性。

Leave a Reply