继承 封装 多态
1.1继承
编写面向对象的程序时,你所创建的类和对象之间存在一定的关系。他们协同工作才能实现程序相应的功能。创建一个新类时,通常需要定义新类以区别与其他类及现有类。使用继承可以定义一个具有父类所有功能的新类,它继承了父类的这些功能。 继承是面向对象的一个核心概念。在Objective-C的继承体系中,位于最顶层的根类是NSObject我们定义的所有类都是它的子类。子类也叫扩展类或派生类。我们之前使用的分数类Fraction就是NSObject类的派生类。
1.1.1 为什么使用继承
继承使得子类可以从父类中获得一些属性和已有方法。要注意的是如果子类中要直接使用父类继承过来的实例变量,那么必须将变量声明在接口部分中,而在实现部分声明的变量,子类无法继承使用。在实现部分声明和synthesize的实例变量都是私有的,子类不能直接访问,需要提供设置值和取值方法才可以访问这些变量。
1.1.2继承的语法格式:
- #import <Foundation/Foundation.h>
- @interface ClassA:NSObject
- @end
- @implementation ClassA
- @end
1.2封装
封装(Encapsulation)主要的用意是依照题意,设计出数据与方法,并将其组合成一所谓的自定义类别类型,其好处是数据已不是次等公民,可以和方法平起平坐,而且数据也受到保护,因为只有类别下定义的方法才可以直接使用。一般的程序语言,如C 程序语言,大都是以函数(function)为主导,数据是次等的,但是如果数据是错误,不管函数编写多么的好,还是会产生所谓的无用输入,无用输出(Garbage In Garbage Out,GIGO)。而且由于每个人的编写风格不同,有人喜欢使用全局变量,有人定义变量时,所使用的名称都是短小的,而且也没有加注释的习惯,造成在维护上需要付出相当多的资源。有鉴于此,面向对象程序设计定义一个规则,将数据(data)和方法(method)都视为一等公民,也就是重要性相同,并利用类别(class),将数据和方法加以封装(encapsulation), 这有如感冒药,里面的药受到胶囊的保护,不易受外界的破坏。此处所说的方法,相当于一般程序语言的函数。有些作者将数据与方法分别以数据成员(data member)和成员函数 (member function)称之。
Objective-C 将数据和方法封装成类别表示,至于类别要有哪些数据和方法,完全视题目而定。在面向对象的术语中,称类别为萃取式数据类型(Abstract Data Type,ADT),我不想将它译为抽象数据类型,因为本来就很抽象了,越翻越抽象。其实以另一种名称来说可能较为清楚易懂,那就是用户自定义的数据类型(user-defined data type),其根据题意定出它需要哪些数据及其方法,所以可以说类别相当于一般程序语言所谈的数据类型。而对象(object)是什么呢?简单的说就是属于某一类别的变量。若我们已定义水果的类别, 则可说香蕉、梨子、李子、苹果等等皆是属于水果的对象。或是已定义汽车的类别,则保时捷、宝马、法拉利可说是汽车的对象。您可以将对象看成一般程序式程序语言的变量,而类别就是一般程序式程序语言的数据类型。对象
也可以视为类别的实体(instance)。封装主要的功能是将数据隐藏起来,只有此类别所属的方法,亦即实体方法(instance
method),才能直接存取数据,所以也可以说是在保护数据,不会被外界任意的存取,所以就大大的降低数据的误用,且很容易的知道错误在哪里,从而省下很多的维护成本。面向对象程序设计中的方法可分为两种,一为上述的实体方法,二为类别方法(class method)。主要的差异在于实体方法需要有一对象去引发,而类别方法可以由类别名称触发之。
一、如何将数据与方法封装起来
我们以范例来加以说明,从输入两个整数,计算其总和,之后将其输出。为了比较起见,首先以一般的程序设计方式,亦即不是以封装的方式来编写,如范例程序class99 所示: 范例程序class99
//class99 #import <Foundation/Foundation.h>
int main (intargc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init]; int num1, num2, total=0; //input num1 = 100; num2 = 200; }
//process total = num1 + num2;
//output NSLog(@"num1=%d, num2=%d", num1, num2); NSLog(@"total=%d", total); [pool drain]; return 0; 输出结果
num1=100, num2=200 total = 300
若将上一范例程序以封装的方式来编写的话,则程序将如下所示:
范例程序class100 //class100
#import<Foundation/Foundation.h>
@interfacemyClass : NSObject { int num1, num2; }
-(void) setData;
-(int) sum;
-(void) output;
@end
@implementationmyClass
-(void) setData { num1=100; num2=200; }
-(int) sum { return (num1+num2); }
-(void) output { }
NSLog(@"num1=%d, num2=%d", num1, num2);
@end
int main (intargc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePoolalloc] init];
myClass *obj = [[myClassalloc] init]; int total;
}
[objsetData]; total = [obj sum];
[obj output];
NSLog(@"total=%i", total);
[obj release];
[pool drain];
return 0;
1.3多态
一、什么是多态:
多态:不同对象以自己的方式响应相同的消息的能力叫做多态。
由于每个类都属于该类的名字空间,这使得多态称为可能。类定义中的名字和类定义外的名字并不会冲突。类的实例变量和类方法有如下特点:
-
和C语言中结构体中的数据成员一样,类的实例变量也位于该类独有的名字空间。
-
类方法也同样位于该类独有的名字空间。与C语言中的方法名不同,类的方法名并不是一个全局符号。一个类中的方法名不会和其他类中同样的方法名冲突。两个完全不同的类可以实现同一个方法。
方法名是对象接口的一部分。对象收到的消息的名字就是调用的方法的名字。因为不同的对象可以有同名的方法,所以对象必须能理解消息的含义。同样的消息发给不同的对象,导致的操作并不相同。
多态的主要好处就是简化了编程接口。它容许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字。这样,编程接口就是一些抽象的行为的集合,从而和实现接口的类的区分开来。
Objective-C支持方法名的多态,但不支持参数和操作符的多态。
二、在Objective-C中如何实现多态
在Objective-C中是通过一个叫做selector的选取器实现的。在Objective-C中,selector有两个意思, 当用在给对象的源码消息时,用来指方法的名字。它也指那个在源码编译后代替方法名的唯一的标识符。 编译后的选择器的类型是SEL有同样名字的方法、也有同样的选择器。你可以使用选择器来调用一个对象的方法。
选取器有以下特点:
* 所有同名的方法拥有同样的选取器
* 所有的选取器都是不一样的
(1) SEL和@selector
选择器的类型是 SEL。@selector指示符用来引用选择器,返回类型是SEL。
例如:
SEL responseSEL;
responseSEL = @selector(loadDataForTableView:);
可以通过字符串来得到选取器,例如:
responseSEL = NSSelectorFromString(@"loadDataForTableView:");
也可以通过反向转换,得到方法名,例如:
NSString *methodName = NSStringFromSelector(responseSEL);
(2) 方法和选取器
选取器确定的是方法名,而不是方法实现。这是多态性和动态绑定的基础,它使得向不同类对象发送相同的消息成为现实;否则,发送 消息和标准C中调用方法就没有区别,也就不可能支持多态性和动态绑定。
另外,同一个类的同名类方法和实例方法拥有相同的选取器。
(3) 方法返回值和@参数类型
消息机制通过选取器找到方法的返回值类型和参数类型,因此,动态绑定(例:向id定义的对象发送消息)需要同名方法的实现拥有相 同返回值类型和相同的参数类型;否则,运行时可能出现找不到对应方法的错误。
有一个例外,虽然同名灶方法和实例方法拥有相同的选取器,但是它们可以有不同的参数类型和返回值类型。