【Objective-C 篇】 ☞ 6. 封装、继承、组合与聚合、多态
面向对象的三大特性: 封装, 继承, 多态
1. 封装
1.1 基本概念
将零散的东西组合起来。
- 广义上封装指:将代码封装成函数,将实例变量和方法封装成类,将类封装成框架....
- 面向对象中的封装指:封装属性和方法放在一个对象中,只给外界公开访问的接口,而且把具体实现隐藏起来。
1.2 封装的好处
提高可读性,可维护性,可扩展性
1.3 OC中的封装
OC语言天然就是封装好的。
定义一个类时,@interface部分就是给外界公开的访问接口。@implementation部分就是隐藏起来的具体实现。
.h文件中写的是公开的接口
.m文件中写的是隐藏的实现
//私有方法,只要不在头文件的接口部分声明的方法就是私有方法
2. 继承
2.1 概念
继承是一种代码复用技术(代码重复使用)。是类与类之间的一种关系(“is a”关系)。
B类继承了A类。A类叫B类的父类,B类叫A类的子类。
其他语言中还有基类,派生类的概念
2.2 继承的方式
单继承 OC, Java...., Swift 单继承指一个类只能有一个父类.
多继承 C++支持多继承 多继承指一个类可以有多个父类.
OC语言中的类在一颗树上,只有一个祖宗NSObject; swift不只有一颗树,是一片森林。
2.3 OC中继承的语法
@interface 类名 : 父类名
@end
2.4 什么情况下用继承
理论上:
如果两个类之间拥有is a关系,这两个类应该是继承关系。
狗是动物 Dog is a Animal.
Animal是父类, Dog是子类
如果两个类之间拥有has a关系,应该用组合或聚合
计算中有一个CPU Computer has a CPU
组合和聚合是另一种类与类之间的关系
实际开发中使用继承:
先写的父类,还是先写的子类?//都可以
2.5 抽象类
C++: 纯虚函数,没有函数体的函数。存在纯虚函数的类是抽象类,不可以实例化了对象。
Java: 抽象方法和抽象类, abstract来声明
OC: OC语言中没有抽象类和抽象方法的语法。
派生:在子类中添加新的属性和方法
2.6 重写:子类对父类的方法不满意时,可重写父类中的方法
隐藏:当子类重写父类的方法后,子类中将有两个同名的方法,而从父类中继承的方法不能在类外被调用
2.6.1 概念
1) override 重写(覆盖): 子类不满意从父类中继承来的方法,重新将此方法实现了。
要求:方法名和父类一样,参数类型一样,返回值一样。只有方法的实现不一样。
2) overload 重载: OC中并不存在overload。
overload的概念是在一个范围内(比如一个类中),出现多个方法名相同的方法,这些方法的参数类型不同,导致可以同时出现,我们说,这些方法之间形成了重载的关系。
OC中不允许同一个范围存在多个方法名相同的方法。
-(id)initWithName:(NSString *)name andAge:(NSUInteger)age;
-(id)initWithName:(NSString *)name andGender:(BOOL)gender; //OC中方法名不同
OC中只有重写,没有重载
方法的重写:子类对父类继承的方法不满意,可以在子类中重写父类的方法。
如果重写父类的方法,优先调用子类的方法,如果子类没有重写父类的方法,则调用父类的方法。
2.6.2 注意
虽然父类中的属性是公开的,但生成的实例变量却是私有的,在子类中不能访问
2.6.3 普通方法的重写
如同perimeter, area, show
方法名相同,参数类型相同,返回值相同
2.6.4 属性的重写
如同TRSqaure中的width,height属性
2.6.5 特殊方法的继承和重写
1) 初始化方法
在OC中初始化方法是会被继承的。
继承来的初始化方法有些可以用,有些不能用。
如果在子类中,继承自父类的初始化方法不能用(不能完成要求的初始化任务),在子类中就需要重写这个初始化方法。
2) 类方法(工厂方法)
类方法也可以被继承
工厂方法自然也可以被继承,但直接继承的工厂方法名不匹配,实际开发中很少这样用。子类最好提供自己的工厂方法。
2.7 继承的缺陷
1) 提高了程序的复杂度,维护性和扩展性降低。
2) 破坏类的封装性 慎用继承!
2.8 为什么使用继承?
1) 代码复用
将子类重复的代码抽象到父类中
2) 制定规范(规则)
NSObject
3) 为了多态
没有继承,就没有多态
组合与聚合
类与类之间的一种关系,比较常见;主要作用:代码复用!
1.1 什么是组合
表示两个对象之间是整体和部分的强关系,是“contains(包含) a”关系,要求两个类同生共死。
生命周期完全一致,同生共死。部分的生命周期不能超越整体!
例如:一个窗口内有按钮、标签,当窗口关闭时,窗口与按钮、标签同时销毁。
1.2 组合的定义:
@interface BRButton : NSObject @end @interface BREdit : NSObject @end @interface BRWindow : NSObject /** * 组合是两个类的强关系(要求定义成成员变量) * 1.在对象初始化时给实例变量赋值(同生共死) * 2.不能在类的外部对实例变量访问/赋值 */ { BRButton *button; BREdit *edit; } @end |
组合Demo:
1.3 组合的优缺点:
优点:
1)当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象是不可见的。
2)当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码,不需要修改当前对象类的代码。
3)当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。
缺点:
容易产生过多的对象
为了能组合多个对象,必须仔细对接口进行定义。
1.4 什么是聚合
表示两个对象之间是整体和部分的弱关系,是”has a”关系,不要求两个类同生共死。
生命周期不一致,一个类无法控制另一个类的生死。部分的生命周期可以超越整体。
例如:电脑和鼠标,电脑被销毁时,鼠标可以留下在其他电脑上继续使用。
1.5 聚合的定义
@interface BRMouse : NSObject @end |
@interface BRComputer : NSObject //聚合是两个类的弱关系,在类的外部给实例变量赋值(要求定义成属性) @property BRMouse *mouse; @end |
聚合Demo:
1.6 聚合的优缺点:
优点:
1)被包含的对象通过包含它们的类来访问
2)很好的封装
3)被包含的对象内部细节不可见
4)可以在运行时动态定义聚合的方式
缺点:
1)系统可能会包含太多对象
2)当使用不同的对象时,必须小心定义的接口。
组合和聚合的生命周期不一样,组合是同生共死(关系紧密);聚合没有特别的关系。
1.7 类与类之间常见的三种关系:继承、组合、聚合
1) 继承(继承的主要目的:代码复用,制定规范,为了多态) 慎用继承!(is a关系才用继承,否则滥用继承)
2) 组合和聚合(单纯的为了代码复用)
组合和聚合的主要目的:是为了代码的复用。
应用场景:(代码的重复使用。如果想使用别人的代码,组合、聚合在一起就可以了)
实际开发中,如果为了复用代码,提倡使用组合或聚合来复用,而不用继承。即是把一个类作为另一个类的属性。(整体与部分)
一个类想使用另一个类的方法?怎么实现...
1.继承:我继承你,我就可以直接调你的方法(提高程序的复杂度…不建议用!)
2.组合/聚合:把你变成我的一部分(即你是我的属性),我可以通过访问你去调用你的方法![我.你 你的方法];
组合:关系很紧密,同生共死。(cpu焊在主板上,主板坏了cpu也就坏了;绑在一起同生共死!)
聚合:关系很疏远,不同生共死。(计算机是计算机,cpu是cpu;这个cpu可以用给这个计算机,也可以用给那个计算机,cpu可以从主板上拔下来给另一个计算机用)
3. 多态(Polymorphism)
3.1 什么是多态
多种形态,引用的多种形态。对于一个引用变量,可以指向任何类的对象 (一个对外接口,多个内在实现)
父类的引用指向本类或任何子类的对象,会调用不同的方法(表现出多种形态,这种表现叫多态)
TRAnimal *animal = [[TRDog alloc]init];//父类的引用指向子类的对象 [animal eat];//狗啃骨头 (调用子类重写的方法) animal = [[TRCat alloc]init]; [animal eat];//猫吃鱼 |
多态的表现:同一个引用变量s,调用相同的方法(show),显示不同的结果。
TRShape *s=[[TRRect alloc]init]; //父类的引用指向子类的对象 [s show]; //显示矩形 调用子类重写的方法 s=[TRCircle alloc]init]; [s show]; //显示圆形 |
3.2 编译期类型和运行期类型
编译器编译时,引用的类型叫编译期类型。
程序运行时,引用指向的类型叫运行期类型。
程序编译时按编译期类型找方法编译,程序运行时按运行期类型找方法调用。
父类的引用指向子类的对象时,只能调用父类中有的方法。
因为编译器在编译时是按父类来检查的,虽然运行时调用的是子类的方法,但是编译时是按父类来检查的。
3.3 为什么用多态
为什么用父类的引用指向子类的对象:
为了写出更加能用(通用),兼容性更好的代码。
3.4 多态的各种表现【用途】
1)【参数多态】经常在方法的参数表现多态:参数类型使用父类型,可以传任何子类的对象
NSObject *和id类型做为方法的参数区别在于:
NSObject *类型的引用只能调用NSObject类中有的方法。
id类型的引用可以调用任何存在(不能瞎写)的方法。
(一般不用NSObject *类型,用id类型替代。id有风险的,编译器根本不检查对错)
/** 方法的参数 表现多态 */ void showAnimal(TRAnimal *animal) { [animal eat]; [animal sleep]; //[animal watchHome];//父类中没有这个方法编译不通过 } int main() { @autoreleasepool { TRDog *dog = [[TRDog alloc]init]; showAnimal(dog); showAnimal([TRCat new]); } return 0; } |
2)【返回值多态】方法的返回值上表现多态
3)【数组多态】在数组和集合中表现的多态
//数组 表现多态 [多个对象同时执行eat方法] TRAnimal *animals[3] = {[TRAnimal new], [TRDog new], [TRCat new]}; for (int i = 0; i < 3; i++) { [animals[i] eat]; } |
多态无处不在!
多态Demo