面向对象的三大特性:封装、继承、多态
面向对象编程简单理解就是:基于模板(类)去创建实体(对象),使用对象完成功能开发。
面向对象包含 3 大主要特性:封装、继承、多态。下面,我们将逐一介绍。
一、封装
封装:将现实世界事物的属性和行为封装到类中,描述为成员变量和成员方法,从而完成程序对现实世界事物的描述。
现实世界中事物的部分属性和行为是不能开放给用户使用的,例如在使用手机时,剩余电量,打电话,拍照等属性和行为对用户开放,而驱动信息,程序调度等属性和行为,对用户而言是隐藏的。
作为现实事物在程序中映射的类,提供了私有成员的形式来支持现实事物有不公开的属性和行为。
定义私有成员的方式:
- 私有成员变量:变量名以 __ 开头( 2 个下划线)
- 私有成员方法:方法名以 __ 开头( 2 个下划线)
注意:私有成员无法被类对象使用(私有变量无法赋值,私有方法无法被调用,强行调用会报错),但是可以被类中其它的成员使用,如下图所示:
代码示例:
#设计类
class Phone:
IMEI=None #序列号
producer=None #厂商
__current_voltage=5 #当前电压
def call_by_5G(self):
if self.__current_voltage>=1: #私有成员变量被其他成员使用
self.__keep_single_core() #私有成员方法被其他成员使用
print('5G通话已开启')
else:
print('通话失败,电量不足')
def __keep_single_core(self):
print('让CPU以单核模式运行节省电量')
#创建对象
phone=Phone()
phone.call_by_5G()
输出:
让CPU以单核模式运行节省电量
5G通话已开启
二、继承
软件更新时,软件包里类的部分,每次更新是重新写这个类,还是在原有基础是增删改动呢?答案显而易见,每次更新都要重写这个类,效率太低了,程序员肯定是在原有基础上进行改动,这就涉及到了类的继承。
继承表示从父类那里继承(复制)来成员变量和成员方法(不含私有),被继承的称为父类,主动继承的称为子类,从父类中继承过来的成员变量和成员方法仍然可以正常使用,继承分为:单继承和多继承。
- 单继承:一个类继承另一个类
- 多继承:一个类继承多个类
2.1单继承
语法:
class 类名称(父类名称):
类的属性
类的行为
代码示例:
#父类
class Phone:
IMEI=None #序列号
producer=None #厂商
def call_by_4g(self):
print('4g通话')
#子类
class Phone2022(Phone): #继承父类
face_id=True #面部识别
def call_by_5g(self):
print('5g通话')
#创建对象
phone=Phone2022()
phone.IMEI=4398799192
print(phone.IMEI)
print(phone.face_id)
phone.call_by_4g()
phone.call_by_5g()
输出:
4398799192
True
4g通话
5g通话
在代码中可以看出,父类中的成员变量IMEI和成员方法call_by_4g在子类对象phone中仍然能正常使用。
2.2多继承
语法:
class 类名称(父类1名称,父类2名称,……,父类n名称):
类的属性
类的行为
多继承的多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)为优先级,即:先继承的保留,后继承的被覆盖。
代码示例:
class NFCReader:
nfc_type='第五代'
producer ='HM'
def read_card(self):
print('读取NFC卡')
def write_card(self):
print('写入NFC卡')
class RomoteControl:
rc_type='红外遥控'
def control(self):
print('红外遥控开启')
class Phone:
IMEI=None #序列号
producer=None #厂商
def call_by_4g(self):
print('4g通话')
class Phone2022(Phone,NFCReader,RomoteControl): #继承多个父类,用逗号隔开
face_id=True #面部识别
def call_by_5g(self):
print('5g通话')
#创建对象
phone=Phone2022()
print(phone.producer)
phone.read_card()
phone.control()
输出:
None
读取NFC卡
红外遥控开启
第一个类NFCReader中成员变量producer ='HM',第三个类Phone中成员变量producer=None,这两个类中都有名为producer的成员变量,并且Phone2022将这两个类都继承了,打印输出phone.producer,输出“None”,因为Phone2022在继承多个父类的时候,父类Phone在左,父类NFCReader在右,原则是先继承的保留,后继承的被覆盖。
2.3复写
子类继承父类的成员属性和成员方法后,如果对其“不满意”,可以进行复写,即:在子类中重新定义同名的属性或方法即可。
代码示例:
class Phone:
IMEI=None #序列号
producer=None #厂商
def call_by_4g(self):
print('4g通话')
class Phone2022(Phone):
IMEI=23552321232
producer='H'
face_id=True #面部识别
def call_by_5g(self):
print('5g通话')
print(self.IMEI)
print(self.producer)
#创建对象
phone=Phone2022()
phone.call_by_5g()
输出:
5g通话
23552321232
H
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员。如果需要使用(被复写过的)原父类成员,则需要特殊的调用方式 ,有2种方式:
方式一 调用父类成员:
使用成员变量:父类名 . 成员变量
使用成员方法:父类名 . 成员方法 (self)
方式二 使用 super() 调用父类成员:
使用成员变量: super(). 成员变量
使用成员方法: super(). 成员方法 ()
代码示例:
class Phone:
IMEI=4349854994 #序列号
producer='M' #厂商
def call_by_4g(self):
print('4g通话')
class Phone2022(Phone):
IMEI=23552321232
producer='H'
face_id=True #面部识别
def call_by_5g(self):
print('5g通话')
# 方式一调用父类成员
print(f'序列号为{Phone.IMEI}')
# 方式二调用父类成员
print(f'厂商为{super().producer}')
#创建对象
phone=Phone2022()
phone.call_by_5g()
输出:
5g通话
序列号为4349854994
厂商为M
注意:只可以在子类内部调用(被复写过的)原父类成员,子类的实体类对象调用默认是调用子类复写的。
三、多态
多态:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。
同样的行为(函数),传入不同的对象,得到不同的状态,如下列代码所示:
class Animal:
def speak(self):
pass #pass 是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思
class Dog(Animal):
def speak(self):
print('汪汪汪')
class Cat(Animal):
def speak(self):
print('喵喵喵')
def make_noise(animal:Animal):
animal.speak()
dog=Dog()
cat=Cat()
make_noise(dog)
make_noise(cat)
输出:
汪汪汪
喵喵喵
父类 Animal 的 speak 方法,是空实现,这种设计的含义是:
- 父类用来确定有哪些方法
- 具体的方法实现,由子类自行决定
这种写法,就叫做抽象类(也可以称之为接口)
- 抽象类:含有抽象方法的类称之为抽象类
- 抽象方法:方法体是空实现的( pass )称之为抽象方法
抽象类多用于做顶层设计(设计标准),以便子类做具体实现,也是对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法
并配合多态使用,获得不同的工作状态。