1. 继承
类和对象协同工作才能使程序实现功能。处理类和对象的关系时,尤其要重视OOP的两个方面。第一个方面是继承,创建一个新类时,通常需要根据它与现有类的区别来定义。使用继承可以定义一个具有父类所有功能的新类,即它继承了父类的功能。另一个和类有关的OOP技术是复合,也就是在对象中可以再引用其他对象。对象引用其他对象时,可以引用其他对象提供的特性,这就是复合。
1.1为何使用继承
UML(Unified Modeling Language, 统一建模语言)是一种用图表来表示类、类的内容以及它们之间关系的常见方式。UML使用末端带有箭头的竖线表示继承关系。
编程时如果出现很多的重复内容,就意味着这是一个失败的架构。OOP中的继承表明一个类从另一个类—它的父类或超类(superclass)—中获取了某些特性。
1.2继承的语法格式
某些编程语言(例如C++)具有多继承性,也就是一个类可以直接从两个或多个类继承而来。但是Objective-C不支持多继承。如果你尝试在Objective-C中使用多继承(多继承的形式类似于以下语句@interface Circle : NSObject, PrintableObject) 是无法通过编译器审核的。
只有代码精简,Bug才无处藏身。
在方法的定义中不写任何内容或返回一个虚值都是可以通过编译的。
移植和优化代码的方式称为重构。进行重构时,会通过移植某些代码来改进程序的架构,而不必改变代码的行为和运行结果。通常开发周期包括向代码中添加某些特性,然后通过重构删除所有重复的代码。有时在面向对象的程序中添加新特性之后,程序反而变得更简单。
超类是继承的类。父类是超类的另一种表达方式。子类是执行继承的类。孩子类是子类的另一种表达方式。如果要想改变方法的实现,需要重写(override)继承的方法。代码运行时,Objective-C会确保调用的是重写过的方法。
1.3 继承的工作机制
1.3.1 方法调度
当代码发送消息时,Objective-C的方法调度机制将在当前的类中搜索相应的方法。如果无法在接收消息的类文件中找到相应的方法,它就会在该对象的超类中进行查找。必要时这种机制将会在继承链的每一个类重复地执行此操作,一直找到最顶层的超类(NSObject)中。如果在最顶层的NSObject类中也没有找到该方法,则会出现一个运行时错误,同时还会出现一个编译时(Compile-time)警告信息。
调度程序通过指针来查找正确的代码。
1.3.2 实例变量
在创建一个新类时,其对象首先会从它的超类继承实例变量,然后根据自身情况来添加自己的实例变量。
NSObject类声明了一个名为isa的实例变量,该变量保存一个指向对象当前类的指针。使用更具体种类的对象来代替一般类型,这种能力被称为多态性。每个方法调用都获得了一个名为self的隐藏参数,它是一个指向接收消息的对象的指针,通过self参数来寻找所需要的实例变量。self指向继承链中第一个类中的第一个实例变量,所以,self一般指向的是isa。
编译器使用“基地址加偏移”的机制实现奇妙的功能。有了对象的基地址,即第一个实例变量的首个字节在内存中的位置,再在该地址上加上偏移地址,编译器就可以查找其他实例变量的位置。每个实例变量与对象的基地址都有一个偏移位置。
1.4重写方法
在制作全新的子类时,经常会添加自己的方法。有时你会添加一个能够向类中引入特有功能的新方法,有时你会替换或改进某个超类定义的现有方法。你也可以选择不添加新特性,而是创建一个子类并通过它重写继承自超类的行为。
Objective-C提供了一种方法,让你既可以重写方法的实现,又能调度超类中的实现方式。当需要超类实现自身的功能,同时在之前或之后执行某些额外的工作时,这种机制非常有用。为了调用继承的方法在父类中的实现,需要使用super作为方法调用的目标。Super既不是参数也不是实例变量,而是由Objective-C编译器提供的一种神奇的功能。当你向super发送消息时,实际上是在请求OC向该类的超类发送消息。如果超类中没有定义该消息,OC会向平常一样继续在继承链上一级中查找。
调用继承的方法可以确保获得方法实现的所有特性。继承是在两个类之间建立关系的一种方式,可以避免许多重复的代码。
复合(让不同的对象协同工作的一种方式)— 建立类之间关系的另外一种方式。
复合是通过包含作为实例变量的对象指针实现的。只有对象间的组合才能叫做复合。诸如int、float、enum和struct等基本类型只被认为是对象的一部分。
如果在类中没有包含实例变量,便可以省略掉接口定义中的花括号。
2.1 自定义NSLog()
NSLog()可以使用%@格式说明符来输出对象。
NSLog()处理%@说明符时,会询问参数列表中相应的对象以得到这个对象的描述。从技术上来讲,也就是NSLog()给这个对象发送了description消息,然后对象的description方法生成一个NSString并将其返回。NSLog()就会在输出结果中包含这个字符串。在类中提供description方法就可以自定义NSLog()会如何输出对象。在自定义的description方法中,你可以选择返回一个字面量值NSString , 也可以构造一个用来描述该对象各类信息的字符串。
在Cocoa中,NSArray类管理的是对象的集合,它的description方法提供了数组自身的信息,例如数组中对象的个数和每个对象所包含的描述。对象的描述是通过向数组中的每个对象分别发送description消息来获得的。
每一个类实例对象都会为指向它的实例变量的指针分配内存,真正包含在类实例对象中的并不是它的实例变量,只是内存中存在的其他对象的引用指针。
使用new创建新对象时,系统其实在后台执行了两个步骤:第一步,为对象分配内存,即对象获得一个用来存放实例变量的内存块;第二步,自动调用init方法,使该对象进入可用状态。
为了让超类将所有需要的初始化工作一次性完成,你需要调用[super init]。init方法返回的值(id类型数据,即泛型对象指针)就是被初始化的对象。将[super init]返回的结果赋给self是OC的惯例。这样做的目的是为了防止超类在初始化过程中返回的对象与一开始创建的不一致。
2.2 存取方法
存取方法是用来读取或改变某个对象属性的方法。为对象中的变量赋值的方法称为setter方法。mutator方法是用来更改对象状态的。getter方法为代码提供了通过对象自身访问对象属性的方式。
如果要对其他对象中的属性进行操作,应该尽量使用对象提供的存取方法,绝对不能直接改变对象里面的值。存储方法是程序间接工作的另一个例子。(通过对象自身访问对象属性或者对对象属性进行设置)
存取方法总是成对出现的。一个用来设置属性的值,另一个用来读取属性的值。有时只有一个getter方法(用于只读属性,例如磁盘上文件的大小)或者只有一个setter方法(例如设置密码)也是合理的。
对于存取方法的命名,Cocoa有自己的惯例。setter方法根据它所更改的属性的名称来命名,并加上前缀set。getter方法则以其返回的属性名称命名。
2.2.1 设置对象属性的存取方法
在OC中所有对象间的交互都是通过指针实现的。
防御式编程是一种很好的编程思想。防御式编程能够在开发早期发现错误。例如用通用代码来检查数组的索引下表是否超出范围。数组越界是程序开发中常见的一种错误。数组的下表范围(0~数组长度-1),若使用超出这个范围内的索引值便会访问到内存中的随机值,由此产生的Bug会导致程序crash。
重构(重构程序,改进内部结构,并不影响它的外部行为)。
2.3 复合还是继承?
继承的类之间建立的关系为“is a”(是一个)。如果可以说“X是一个Y”, 那就可以使用继承。复合的类之间建立的关系为“has a”(有一个)。如果可以说“X有一个Y”,那么就可以使用复合。应当在适当的时机使用继承和复合特性。
复合是OOP的基础概念,我们通过这种技巧来创建引用其他对象的对象。存取方法,既为外部对象提供了改变其属性的途径,同时又能保护实例变量本身。 存取方法和复合是密不可分的,因为我们通常都会为复合的对象编写存取方法。有两种类型的存取方法:setter方法和getter方法,前者告诉对象将属性改为什么,后者要求对象提供属性的值。对于返回属性值的存取方法,名称中不能使用get这个词。
网友评论