继承,组合

1.什么是继承

继承是一种关系,通过继承关系,一个对象可以直接使用另一个对象拥有的内容,例如王思聪继承王建林,王思聪就可以使用王健林拥有的财产!

被继承的一方称之为父,即王健林; 继承的一方称之为子,即王思聪

OOP继承描述的是两个类之间的关系,通过继承,一个类可以直接使用另一个类中已定义的方法和属性;

被继承的称之为父类或基类,继承父类的类称之为子类;

在python3中创建类时必然继承另一个类,如果没有显式的指定父类,则默认继承object类; object是根类 所有类都直接或间接的继承object

2.为什么需要继承

1.减少代码重复

2.为多态提供必要的支持,(关于多态下节会详细讨论!)

3.使用继承:

在类名后面的括号中指定要继承的父类名称​ class 类名(父类名):

案例:在选课系统中,有老师和学生两种角色,老师拥有姓名,性别,年龄,学生也拥有姓名,性别,年龄,使用面向对象编程思想,可以将老师和学生定义为两个为不同的类

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        
class Student:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

两个类中的内容完全一致,则可以通过继承来重用代码

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
​
class Student(Teacher):  #指定Teacher类继承Student类
    pass
​
#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

4.继承与抽象

问题:

继承描述的是子类与父类之间的关系,在上面的例子中,Student继承Teacher完成了代码的重用,但是很明显老师类不是学生类的父类,学生类也不属于老师类,这样的继承关系在逻辑上是错误的;OOP的概念来自于现实世界,所以继承应当遵循现实世界的逻辑;

现在暂且不考虑逻辑错误,来看这样一个情况:

TeacherStudent由于存在相同的属性,为了减少重复代码,让两个逻辑上没有继承关系的类,产生了继承关系,如果后期Teacher类中增加了教学的方法,由于继承关系的存在,学生类也会拥有教学的方法,这是不合理的;

答:

应当将TeacherStudent中完全相同的部分抽取出来,放到另一个类中,并让Teacher与Student去继承它,这个类称之为公共父类 ,但是这个类与实际的业务需求是无关的在现实中也不实际存在,它的作用仅仅是存储相同代码以减少重复;这一过程我们称之为抽象;

综上所述,正确思路是:先抽象在继承

# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):  #指定Teacher类继承Person类
    pass
class Student(Person):  #指定Student类继承Person类
    pass
​
#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度),每个类之干自己的事情,多个类相同的事情交给父类来干

继承的另一种使用场景:

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

不需要从头开始写一个类B,这就用到了类的继承的概念。

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

用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分甚至大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,大大缩短了软件开发周期,对大型软件开发来说,意义重大

5.存在继承关系后的属性查找

一个类必然继承另一个类,被继承的类也有可能继承了其他类,相当于C继承B,B又继承A

此时查找属性的顺序是:

对象本身的名称空间 - > 类的名称空间 -> 父类的名称空间 -> 父类的父类名称空间 ->...object类

会沿着继承关系一直往后查找,直到找到为止,由于object是所有类的根类,所以如果找不着最后都会查找object类!

class Foo:
    def f1(self):
        print('Foo.f1')
​
    def f2(self):
        print('Foo.f2')
        self.f1()
​
class Bar(Foo):
    def f1(self):
        print('Bar.f1')
​
​
b=Bar()
b.f1()
#输出 Bar.f1
b.f2()
#输出 Foo.f2

6.派生与覆盖

什么是派生

当父类提供的属性无法完全满足子类的需求时,子类可以增加自己的属性或非法,或者覆盖父类已经存在的属性,此时子类称之为父类的派生类;

什么是覆盖

在子类中如果出现于父类相同的属性名称时,根据查找顺序,优先使用子类中的属性,这种行为也称为覆盖

Person类派生出来的Teacher

# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):  #指定Teacher类继承Person类
    # Teacher类从Person类中继承到了say_hi方法 但是,老师打招呼时应当说出自己的职业是老师,所以需要
    # 定义自己的不同的实现方式
    def say_hi(self):
        print("hi i am a Teacher")
        #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        #上一行代码与父类中完全相同,可以直接调用父类提供的方法
        Person.say_hi(self)
# 创建Teacher对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
#输出 hi i am a Teacher
#     my name is Jack age is 20 gender is man

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

7.子类中重用父类的方法

很多情况下 子类中的代码与父类中仅有小部分不同,却不得不在子类定义新的方法,这时候可以在子类中调用父类已有的方法,来完成大部分工作,子类仅需编写一小部分与父类不同的代码即可

在子类中有两种方式可以重用父类中的代码

1.使用类名直接调用 ,该方式与继承没有关系,即时没有继承关系,也可以调用

2.使用super()

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power
​
     def run(self):
         print('开动啦...')
​
class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line
​
    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        super(Subway,self).run()
​
class Mobike(Vehicle):#摩拜单车
    pass
​
line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()

8.经典类与新式类

即使没有直接继承关系,super仍然会按照mro继续往后查找

而第一种方式明确指定了要到哪一个类中去查找,找不到则直接抛出异常

#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass
​
c=C()
c.test() #打印结果:from B
​
​
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

*当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。如果每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)

9.组合

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Equip: #武器装备类
     def fire(self):
         print('release Fire skill')
​
class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
     camp='Noxus'
     def __init__(self,nickname):
         self.nickname=nickname
         self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性
r1=Riven('锐雯雯')
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法

组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,

1.继承的方式

通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2.组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...

10.继承实现的原理

1.继承顺序

在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)

如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

这里谈到的广度优先不是简单的从左到右,像图中标识的依然会按照深度一层一层上找,但是如果下一个要找的类与继承列表中的其他类存在相同父类(就像EF有共同父类G),则不会查找公共父类,这一次深度查找结束,开始下一条查找路径(C -> F),

2.继承实现原理

对于你定义的每一个类,python通过一个算法算出一个查找顺序存放在(MRO)列表中,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:

F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。不需要深究这个算法的原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类

猜你喜欢

转载自blog.csdn.net/qq_36811322/article/details/87700875
今日推荐