面向对象三大特性之继承与多态
面向对象的三大特性是指:封装、继承和多态
一、继承
1.1、继承的定义
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
1.2、python中类的继承分为:单继承和多继承
#定义父类 class ParentClass1: pass class ParentClass2: pass # 在python3中 所有的类都继承自object print(ParentClass1.__bases__) #结果: (<class 'object'>,) #子类 class ChildClass1(ParentClass1): # 单继承 pass class ChildClass2(ParentClass1,ParentClass2): # 多继承 pass print(ChildClass1.__bases__) # 会显示该类的一个父类 #结果: (<class '__main__.ParentClass1'>,) print(ChildClass2.__bases__) # 会显示该类的所有父类 #结果: (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
1.3、继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这就用到了类的继承的概念。
# 猫类 : 吃eat 喝drink 睡sleep 爬树climb # 狗类 : 吃eat 喝drink 睡sleep 看家watch #父类 宠物类 class Pet: def __init__(self,name,kind,food): self.name = name self.kind = kind self.food = food #吃 def eat(self): print('%s吃%s'%(self.name,self.food)) #喝 def drink(self): print('%s在喝水'%self.name) #睡 def sleep(self): print('%s在睡觉' % self.name) #子类 猫类 class Cat(Pet): #猫类特有方法 def climb(self): # 派生方法 print('%s在爬树' % self.name) #子类 狗类 class Dog(Pet): #狗类特有方法 def watch(self): # 派生方法 print('%s在看家' % self.name) # 实例化 tom = Cat('Tom','暹罗猫','猫粮') # 子类使用名字(方法和静态变量),如果在子类中没有,就使用父类的\ # Cat('Tom','暹罗猫','猫粮') 实例化 # 实例化这个类 # 创建一个空对象 # 执行__init__方法:子类没有用父类的 hei = Dog('小黑','2哈','狗粮') tom.eat() #Tom吃猫粮 hei.eat() #小黑吃狗粮 tom.climb() #Tom在爬树 hei.watch() #小黑在看家
1.4、派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
#父类 动物类 class Animal: # 动物 def __init__(self,name,aggr,hp): #方法 动态属性 内置的双下方法 self.name = name # 对象属性 实例属性 self.aggr = aggr self.hp = hp #子类 人类 class Person(Animal): # 类名 Person def __init__(self,name,sex,aggr,hp): self.sex = sex # 派生属性 #继承自父类的属性(Animal和super()二选其一即可) Animal.__init__(self,name,aggr,hp) super().__init__(name,aggr,hp) #派生 人的攻击方法 def attack(self,dog): # 自定义方法 print('%s打了%s %s 点血'%(self.name,dog.name,self.aggr)) dog.hp -= self.aggr #子类 狗类 class Dog(Animal): def __init__(self,name,kind,aggr,hp): self.kind = kind # 派生属性 #继承自父类的属性 Animal.__init__(self,name,aggr,hp) super().__init__(name,aggr,hp) #派生 狗的撕咬方法 def bite(self,person): print('%s咬了%s %s 点血'%(self.name,person.name,self.aggr)) person.hp -= self.aggr # 首先创建一个Person的对象, # 初始化:找init方法,自己有调用自己的 # 父类和子类拥有同名的方法时,子类的对象只会调用子类的 # 如果想要调用父类的方法,需要 父类名.方法名(self,其他参数) #实例化 alex = Person('alex','不详',10,250) print(alex.__dict__) #{'sex': '不详', 'name': 'alex', 'aggr': 10, 'hp': 250} hei = Dog('小黑','teddy',260,10000) print(hei.__dict__) #{'kind': 'teddy', 'name': '小黑', 'aggr': 260, 'hp': 10000} alex.attack(hei) #alex打了小黑 10 点血 hei.bite(alex) #小黑咬了alex 260 点血
1.5、super()方法
在python3中,子类执行父类的方法也可以直接用super方法.
#父类 class A: def hahaha(self): print('A') #子类 class B(A): def hahaha(self): #继承父类 super().hahaha() #A #super(B,self).hahaha() #A.hahaha(self) print('B') a = A() b = B() b.hahaha() #B super(B,b).hahaha() #A
1.6、继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
1.7、接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
#阿里支付 class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) #苹果支付 class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) #调用支付功能的函数 def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) #支付宝支付 p = Alipay() pay(p,200) #支付宝支付了200元
开发中容易出现的问题
#阿里支付 class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) #苹果支付 class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) #微信支付 class Wechatpay: def fuqian(self,money): ''' 实现了pay的功能,但是名字不一样 ''' print('微信支付了%s元'%money) #调用支付能的函数 def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) #使用微信支付 p = Wechatpay() pay(p,200) #执行会报错 # 报错结果: # 'Wechatpay' object has no attribute 'pay' # 原因:微信支付函数为fuqian,不是pay
使用规范接口
# 在Python里面 没有接口的概念 # java里 from abc import ABCMeta,abstractmethod # 接口类 : 接口类就是一个规范 接口类一般是项目设计人员写好的规范 class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): ''' :param money: :return: ''' raise NotImplementedError # 微信支付 class WeChatPay(Payment): def pay(self,money): print('通过微信支付了%s元钱'%(money)) # 支付宝支付 class AliPay(Payment): def pay(self,money): print('通过支付宝支付了%s元钱'%(money)) # 苹果pay class ApplePay(Payment): # 正确的支付接口 def pay(self,money): print('通过苹果支付了%s元钱' % (money)) # 错误的支付接口 def fuqian(self,money): print('通过苹果支付了%s元钱' % (money)) # 调用支付功能的函数 def pay(pay_obj,money): # 程序的归一化设计 pay_obj.pay(money) #实例化对象 wp = WeChatPay() #微信 alp = AliPay() #支付宝 app = ApplePay() #苹果 #调用支付功能付款 pay(alp,100) #通过支付宝支付了100元钱 pay(wp,100) #通过微信支付了100元钱 pay(app,100) #通过苹果支付了100元钱 # 接口类是一个规范 # 多种支付方式,每一种支付方式都是一个类, # 每一个类中定义一个支付方法完成支付功能 # 由于每一种支付方式在程序中表现出来的就是支付类的对象 # 拿到每一个支付对象都需要调用支付方法 # 为了方便支付方法的调用 # 需要统一一个入口 pay函数 # Interface # 在python里没有接口类这种数据类型,没有接口类专门的语法 # 但是 可以通过继承abc模块实现接口的功能
1.8、开发原则
依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
1.9、抽象类
什么是抽象类
抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
在python中实现抽象类
#一切皆文件 import abc #利用abc模块实现抽象类 #父类 抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 #子类 文本数据类 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') #子类 硬盘数据类 class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') #子类 进程数据 class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') #实例化子类对象 wenbenwenjian=Txt() #文本数据 yingpanwenjian=Sata() #硬盘数据 jinchengwenjian=Process() #进程数据 #调用数据的访问方法 #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() #文本数据的读取方法 yingpanwenjian.write() #硬盘数据的读取方法 jinchengwenjian.read() #硬盘数据的读取方法 print(wenbenwenjian.all_type) #file print(yingpanwenjian.all_type) #file print(jinchengwenjian.all_type) #file # 在java里 有区别 # java的接口规定里面的方法一定不能实现(一句代码也不能写) # 抽象类 单继承 # 无论接口类 还是抽象类 其实都是一种面向对象编程的开发规范 # 只是在接口类或者抽象类中 去约束继承它的子类必须实现某些方法 # 对于java代码来说:如果发生多继承 那么一定是接口类 且里面的方法都不能实现 # 如果在方法里有了实现 那么一定是单继承 的抽象类 # 但是对于python来说 就没有这些约束 # 因为python没有接口的概念 # 对于类的继承 没有多继承的限制 # 实际上abc模块是帮我实现抽象类的方法,只是我们用它来模仿接口类的效果了 # 在python中,只要metaclass = ABCMeta 定义了抽象方法(@abctractmethod) # 这个类就不能被实例化 # 你可以说他是一个抽象类
1.10、抽象类与接口类
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
from abc import ABCMeta,abstractmethod # 飞行接口 class FlyAnimal(metaclass=ABCMeta): @abstractmethod def fly(self): pass # print(11111) #可以写代码 # 游泳接口 class SwimAnimal(metaclass=ABCMeta): @abstractmethod def swim(self): pass # 走路接口 class WalkAnimal(metaclass=ABCMeta): @abstractmethod def walk(self): pass #天鹅类 会飞会游泳会走路 class Swan(SwimAnimal,WalkAnimal,FlyAnimal): # 飞 def fly(self):pass # 游泳 def swim(self):pass # 走 def walk(self):pass # 企鹅 会游泳会走路 class Penguin(SwimAnimal,WalkAnimal): # 游泳 def swim(self):pass # 走 def walk(self):pass #麻雀 会飞会走路 class Bird(FlyAnimal,WalkAnimal): # 飞 def fly(self):pass # 走 def walk(self):pass #实例化对象 swan1 = Swan() #天鹅 qq = Penguin() #企鹅 bird = Bird() #麻雀 # 接口类不能被实例化 FlyAnimal() #报错 SwimAnimal() #报错 WalkAnimal() #报错 #报错信息 # Abstract class 'WalkAnimal' with abstract methods instantiated
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass #实例化对象 f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
print(F.mro()) #等同于F.__mro__ #结果: [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>,
<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
1.12、继承小结
1)、继承的作用
减少代码的重用
提高代码可读性
规范编程模式
2)、抽象类与接口类
1.多继承问题 在继承抽象类的过程中,我们应该尽量避免多继承; 而在继承接口的时候,我们反而鼓励你来多继承接口 2.方法的实现 在抽象类中,我们可以对一些抽象方法做出基础实现; 而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
3)、钻石原则
新式类:广度优先
经典类:深度优先
1、定义
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
from abc import ABCMeta,abstractmethod class Animal(metaclass=ABCMeta): #同一类事物:动物 @abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
2、多态性
一 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性示例代码
peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
3、鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度
例如:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass