继承,抽象类与接口

面向对象的三大特性

继承

多态

封装

一 继承

 

什么是继承

 

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类

python中类的继承分为:单继承和多继承

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass

查看继承

>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

 

提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

 

# 猫类 : 吃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()#实例化猫吃的
hei.eat()#小黑吃
tom.climb()#猫爬树
hei.watch()#狗看家

 

 

继承与抽象(先抽象再继承)

 

抽象即抽取类似或者说比较像的部分。

 

抽象分成两个层次: 

 

1.将奥巴马和梅西这俩对象比较像的部分抽取成类; 

 

2.将人,猪,狗这三个类比较像的部分抽取成父类。

 

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

 

 

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

 

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

 

 

 

 

 

 

派生

人狗大战例子

 

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.__init__(self,name,aggr,hp)#调用父类的,属性
        super().__init__(name,aggr,hp)#super父类属性

    def attack(self,dog):  # 自定义方法
        print('%s打了%s'%(self.name,dog.name))
        dog.hp -= self.aggr

class Dog(Animal):#定义狗类
    def __init__(self,name,kind,aggr,hp):
        self.kind = kind   # 派生属性
        Animal.__init__(self,name,aggr,hp)
    def bite(self,person):
        print('%s咬了%s'%(self.name,person.name))
        person.hp -= self.aggr

 

 

 

 

 

在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时

 

我们不可能从头开始写一个类B,这就用到了类的继承的概念。

 

通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

 

 

练习

 

class Foo:#定义一个父类
    def __init__(self):#定义父类的属性
        self.func()

    def func(self):#定义父类的方法
        print('in Foo')

class Son(Foo):#定义儿子函数
    def func(self):#定义儿子的方法
        print('in son')

f = Foo()#调用父类属性#结果in Foo
Son()#调用儿子的属性#结果in son

 

 

 

 

super

在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.

在python3中,子类执行父类的方法也可以直接用super方法.

class Animal: # 动物
    def __init__(self,name,aggr,hp):  #方法 动态属性  内置的双下方法
        self.name = name    # 对象属性 实例属性
        self.aggr = aggr
        self.hp = hp
    def eat(self):
        print('in Animal eat')

class Person(Animal):    # 类名 Person
    def __init__(self,name,sex,aggr,hp):
        self.sex = sex   # 派生属性
        # Animal.__init__(self,name,aggr,hp)#父类的对象属性,实例属性
        # 单继承中 super会寻找父类
        # 且在使用super调用父类方法的时候不需要再传self参数
    def eat(self):
        print('in Person eat')
        # Animal.eat(self)
        # super().eat()
alex = Person('alex','不详',1,250)
print(alex.__dict__)显示出Alex对象的所有属性
# Animal.eat(Alex)#在Person中没有Animal的eat属性所以就在父类中找eat
# super(Person,alex).eat()

 

继承

 

1.单继承

 

class A: #创建一个A类
    def wahaha(self):print('in A') 

class B(A):#继承父类A#B中没有就从父类A中找
    def wahaha(self):print('in B')

class C(B):#继承父类B#C中没有就从父类D中找
    def wahaha(self):print('in C')

class D(C):pass#继承父类C##D中没有就从父类C中找
    # def wahaha(self):print('in D')

d = D()#实例化D类
d.wahaha()

# 不要发生循环继承
# 依赖倒置原则 :
# 高层模块不应该依赖低层模块

 

2.多继承

 

 

class A:pass#创建一个A类
    # def qqxing(self):
    #     print('in A')

class B:#创建一个B类
    def qqxing(self):
        print('in B')

class C:#创建一个C类
    def qqxing(self):
        print('in C')

class D(B,A,C):#D类继承B类、A类、C类
    pass
    # def qqxing(self):
    #     print('in D')

d = D()#D类实例化
d.qqxing()#D中没有就去B中找,B中没有就去A中找,A中没有就去C中找广度优先

 

 

 

class A:#创建一个A类
    def wangwang(self):
        print('in A')
class B(A):#创建一个B类,继承A类
    def wangwang(self):
        super().wangwang()    # 'in C '
        print('in B')
class C(A):#创建一个C类,继承A类
    def wangwang(self):
        print('in C')
class D(B,C):pass#创建一个D类,继承B类和C类

d = D()#实例化一个D类
d.wangwang()

 

 

💎钻石继承 

 

class A:#创建一个A类
    def wangwang(self):
        print('in A')

class B(A):pass#创建一个B类,继承A类
    def wangwang(self):
        print('in B')

class C(A):pass#创建一个C类,继承A类
     def wangwang(self):
        print('in C')

class D(B):pass#创建一个D类,继承B类
     def wangwang(self):
         print('in D')

class E(C):pass#创建一个E类,继承C类
    def wangwang(self):
        print('in E')

class F(D,E):pass#创建一个F类,继承D类,E类
    def wangwang(self):
        print('in F')

f = F()#实例化F
f.wangwang()
print(F.mro())

 

 

python2中的经典继承

 

 

 

 

在py3里 所有的多继承问题 都符合 广度优先算法

 


python3中的所有类 都默认继承object

如果一个类 继承了object 这个类就被称为新式类
没有继承object类 就被称为经典类

深度优先 广度优先 都是一种遍历算法,把这个图中所有的项都走一遍,且不会重复

经典类 遵循 深度优先算法 且没有mro方法 python2
新式类 遵循 广度优先算法 有mro方法 py2 py3
py3 super
单继承中 super就是找父类
多继承 super寻找的轨迹是根据mro(广度优先)顺序的

 

二 抽象类与接口类

接口类

继承有两种用途:

1:继承基类的方法,并且做出自己的改变或者扩展(代码重用)  

2:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

 在Python里面 没有接口的概念

接口类是一个规范
多种支付方式,每一种支付方式都是一个类,
每一个类中定义一个支付方法完成支付功能
由于每一种支付方式在程序中表现出来的就是支付类的对象
拿到每一个支付对象都需要调用支付方法
为了方便支付方法的调用
需要统一一个入口 pay函数

Interface
在python里没有接口类这种数据类型,没有接口类专门的语法
但是 可以通过继承abc模块实现接口的功能

 

from abc import ABCMeta,abstractmethod
# 接口类 : 接口类就是一个规范 接口类一般是项目设计人员写好的
class Payment(metaclass=ABCMeta):   #规范
    @abstractmethod
    def pay(self,money):
        '''
        :param money:
        :return:
        '''
        raise NotImplementedError

# class ApplePay(Payment):# 100
#     def pay1(self,money):pass
# app = ApplePay()
# 微信支付
class WeChatPay(Payment):#微信支付类
    def pay(self,money):#函数有父类中的函数
        print('通过微信支付了%s元钱'%(money))

# 支付宝支付
class AliPay(Payment):
    def pay(self,money):
        print('通过支付宝支付了%s元钱'%(money))

# apple pay
class ApplePay(Payment): 
    def pay(self,money):
        print('通过支付宝支付了%s元钱' % (money))

wp = WeChatPay()#微信支付实例化
alp = AliPay()#阿里支付实例化
app = ApplePay()#苹果支付实例化
pay(alp,100)
pay(wp,100)
pay(app,100)

 

 

实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。

归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程

在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py里使用zope.interface

文档https://zopeinterface.readthedocs.io/en/latest/

设计模式:https://github.com/faif/python-patterns

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
为何要用接口

抽象类

什么是抽象类

    与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

为什么要有抽象类

    如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类是从一堆中抽取相同的内容而来的,内容包括数据属性和函数属性。

  比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。

    从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案

在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)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

 

 

抽象类与接口类

抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。

抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 

在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。

 

在java里 有区别
java的接口规定里面的方法一定不能实现(一句代码也不能写)
抽象类 单继承

无论接口类 还是抽象类 其实都是一种面向对象编程的开发规范
只是在接口类或者抽象类中 去约束继承它的子类必须实现某些方法
对于java代码来说:如果发生多继承 那么一定是接口类 且里面的方法都不能实现
如果在方法里有了实现 那么一定是单继承 的抽象类
但是对于python来说 就没有这些约束
因为python没有接口的概念
对于类的继承 没有多继承的限制
实际上abc模块是帮我实现抽象类的方法,只是我们用它来模仿接口类的效果了

在python中,只要metaclass = ABCMeta 定义了抽象方法(@abctractmethod)
这个类就不能被实例化
你可以说他是一个抽象类

1.多继承问题

在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口

接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。

2.方法的实现

在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现

 

 

posted @ 2018-03-07 16:03  虫洞小鳗鱼  阅读(266)  评论(0编辑  收藏  举报