【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也就坏了;绑在一起同生共死!)

聚合:关系很疏远,不同生共死。(计算机是计算机,cpucpu;这个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

  

    

    

 

posted @ 2017-03-03 13:27  专注·精彩  阅读(1080)  评论(0编辑  收藏  举报