简单继承
Objective-C 也有继承的概念,但是不能多重继承。不过,它也有别的途径实现类似多重继承的机制,这个我们后面会讲到。
C++
class Foo : public Bar, protected Wiz { }
Objective-C
@interface Foo : Bar // 单继承 // 如果要同时“继承” Wiz,需要使用另外的技术 { } @end
在 C++ 中,一个类可以继承自一个或多个类,使用public
、protected
以及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
,析构方法dealloc
,UIView
类的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
,manageGreenDocuments
和manageRedDocuments
。Slave
可以加入一个分类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 更具可读性。