zoukankan      html  css  js  c++  java
  • 面向对象三大特性 封装 继承 多态 鸭子类型

    1 继承

    1:定义

    继承描叙的是两个类之间的关系,一个类可以直接使用另一个类中已定义的方法和属性;
    被继承的称之为父类或基类,继承父类的类称之为子类;

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

    2 继承的作用

    1.减少代码重复
    
    2.为多态提供必要的支持
    

    3 继承的使用

     1 先抽象在继承

    # 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
    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()
    

     

    2 派生和覆盖

    派生

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

    覆盖  

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

      

    # 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
    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
    

      

    3 子类中重用父类的方法(重点使用super())

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

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

    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()
    '''
    地铁13号线欢迎您
    开动啦...
    '''
    

      

    4 经典类与新式类

    那什么是经典类,什么是新式类呢?

    经典类和新式类的主要区别就是类的继承的方式 ,经典类遵循深度优先的规则,新式类遵循广度优先的规则。至于什么是深度优先什么是广度优先,可以看如下示例:

     

    # Author:Zhang Zhao
    class A(object):
        def __init__(self):
            print('A')
    class B(A):
        pass
        # def __init__(self):
        #     print('B')
    class C(A):
        def __init__(self):
            print('C')
    class D(B,C):
        pass
        # def __init__(self):
        #     print('D')
    r1 = D()
    

     

    在新式类中,D是继承B和C的,按照顺序,首先去找B,如果在B里面能找到实例化对象,便继承B,不再往别的地方寻找,如果没有,就会接着找C,而不是找B的父亲A!

    但是在经典类中,如果B中找不到,它会优先考虑B的父亲A,而不是C。

    在python3中,都是遵循广度优先的规则,在python2.7以前,应该是遵循深度优先的的规则。两种规则没有优劣之分

     

    即使没有直接继承关系,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列表) 

    5 继承的顺序

    对于你定义的每一个类,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.如果对下一个类存在两个合法的选择,选择第一个父类

    6 组合

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

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

    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...

     

    2 封装

    1 定义

    '''
    什么是封装 :对外部隐藏属性和方法  给外部提供使用的接口
    目的:限制外部对内部数据的访问  因为有些数据是机密的  不方便对外透露
    如何封装:  __开头的语法  分为封装属性  和 封装方法
    封装的好处:  封装属性--提高安全性   封装方法:提高便利性
    '''
    

    2 封装属性 方法

    一.封装属性

    对于属性而言,封装就为了限制属性的访问和修改,其目的是为了保护数据安全

    例如:

    学生对象拥有,姓名,性别,年龄,和身份证号,分数;其中身份证是一个相对隐私的数据,不应该让外界访问到;

    分数属性,是一个非常关键的数据,决定学员能不能正常毕业,不应被随意修改;

     

    class Student:
        def __init__(self,name,age,id_card):
            self.name = name
            self.age = age
            self.__id_card = id_card  # 外部就无法通过.id_card 和 .__id_card
        def show_id_card(self):
            print(self.__id_card)
     
    stu = Student('tom',18,'xxxxxxxx')
    print(stu.name)  # tom
    print(stu.age)  # 18
    # print(stu.__id_card)   是没有这个的
    stu.show_id_card()  #  xxxxxxxx
    

      

    对私有属性的访问以及修改

    class Student:
        def __init__(self,name,age,id_card):
            self.name = name
            self.age = age
            self.__id_card = id_card  # 外部就无法通过.id_card 和 .__id_card
     
        def get_id_card(self,pwd):  # 访问被封装的属性  称之为访问器  可以为访问添加条件
            if pwd == '123':
                return self.__id_card
            else:
                print('密码错误')
     
        def set_id_card(self,new_id_card): # 修改被封装的属性称之为设置器
            # 身份证必须是字符 且 是18位
            if isinstance(new_id_card,str) and len(new_id_card) == 18:
                self.__id_card = new_id_card
            else:
                print('新身份证不符合规定')
     
     
    stu = Student('tom',18,'123456789012345678')
    stu.get_id_card('wsx')  # 密码错误
    stu.set_id_card('123456789123456789')  # 无结果
    

      

     

    二.封装方法

    说明这方法可以被内部访问 不应该被外界访问

    一个大的功能很多情况下是由很多个小功能组合而成的,而这些内部的小功能对于用户而言是没有意义的,所以封装方法的目的是为了隔离复杂度;

    例如:

    电脑的开机功能,内部需要启动BIOS,读取系统配置,启动硬盘,载入操作系统,等等一系列复杂的操作,但是用户不需要关心这些实现逻辑,只要按下开机键等待开机即可;

      

    class ATM:
        def withdraw(self):
            self.__user_auth()
            self.__input_money()
            self.__save_record()
        # 输入账号和密码
        # 显示余额
        # 输入取款金额
        # 保存记录
        def __user_auth(self):
            print('输入用户名和密码')
     
        def __input_money(self):
            print('余额为1000000,输入取款金额!')
     
        def __save_record(self):
            print('记录流水......')
     
    atm = ATM()
    atm.withdraw()  #  提现只需要调用withdraw就可以了  不需要一步一步去操作其他三个步骤 简化了外部操作
    

     

    三  封装特性property

    property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
    

     

    @property封装时 特别要注意 封装的属性需要._ 

    property的用途一 :将方法伪装成普通属性

    '''
    作用:将方法伪装成普通属性
    为什么要用property  希望将私有属性的访问方式和普通属性一致  普通属性 点语法
    与property相关的 两个装饰器
     setter
     ​    用点语法 给属性赋值时触发
     
     deleter
     ​    用点语法删除属性时触发
    '''
    

      

    class Teacher:
        def __init__(self,name,age,salary):
            self.name = name
            self.age = age
            self.__salary = salary   #  s实质是在名称空间里换名字为 _Teacher__salary
     
        @property
        def salary(self):   # getter   # 用于访问私有属性的值   也可以访问普通属性
            return self.__salary
     
        @salary.setter  # 用来设置私有属性的值  也可以设置普通属性
        def salary(self,new_salary):
            self.__salary = new_salary
     
        @salary.deleter
        def salary(self):
            del self.__dict__['_Teacher__salary']  # 删除老师薪水部分
     
     
    a = Teacher('jeck',18,50)  # 这是访问私有属性
    print(a.salary)   # 50
     
    a.salary=10  # 赋值时运用 setter
    print(a.salary)  # 10
     
    del a.salary  # 已删除salary部分
    # print(a.salary)  这时打印会报错的
    

      

     

    class Foo:
        def __init__(self,val):
            self.__NAME=val #将所有的数据属性都隐藏起来
    
        @property
        def name(self):
            return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
    
        @name.setter
        def name(self,value):
            if not isinstance(value,str):  #在设定值之前进行类型检查
                raise TypeError('%s must be str' %value)
            self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
    
        @name.deleter
        def name(self):
            raise TypeError('Can not delete')
    
    f=Foo('Jack') 
    
    print(f.name) # 访问property属性
    #输出 Jack
    f.name="Rose" # 修改property属性 抛出异常'TypeError: 10 must be str'
    
    del f.name # 删除property属性 抛出异常'TypeError: Can not delete' 
    

      

    property的用途二  :计算属性

    '''
    有些属性不是固定的  是需要计算出来的 这是也可以用到property
    例如计算BMI  体质指数(BMI)=体重(kg)÷身高^2(m)
    '''
    

      

    class People:
        def __init__(self,name,weight,height):
            self.name=name
            self.weight=weight
            self.height=height
     
        @property
        def BMI(self):
            return self.weight/(self.height**2)
     
        @BMI.setter
        def BMI(self,new_bmi):
            print('bmi不支持自定义')
    p = People('egon',80,1.7)
    print(p.BMI) # 27.68166089965398
    p.BMI = 10  # 运行这步  不会更改值   bmi不支持自定义
    print(p.BMI) #  27.68166089965398 结果还是这个不会变的  计算属性时 setter 不受用
    

      

    3 .如何封装

    在属性名前添加两个下划线__,将其设置为私有的
    

      

    1.封装数据属性实例:网页中折叠

    class Student:
        def __init__(self, name, gender, age, id, score):  # 初始化函数
            self.name = name
            self.gender = gender
            self.age = age
            self.__id = id  # 将id设置为私有的
            self.__score = score  # 将score设置为私有的
    
        def test(self):
            print(self.__id)
            print(self.__score)
    
    
    stu = Student("Jack", "man", 20, "320684198901010001", 780)
    # 1.访问私有属性测试
    # print(stu.id) #	直接访问到隐私数据
    # print(stu.__id) # 换种写法
    # 以上两行代码均输出相似的错误
    # AttributeError: 'Student' object has no attribute 'id'
    # 错误含义 在Student类的对象中没有一个id或__id属性
    
    # 2.修改私有属性测试
    stu.score = 1  # 直接修改私有属性 由于语法特点,相当于给stu对象增加score属性
    stu.__score = 2  # 直接修改私有属性 由于语法特点,相当于给stu对象增加__score属性
    print(stu.score,)
    print(stu.__score)
    # 输出 1
    # 输出 2
    
    # 看起来已经被修改了 调用函数来查看私有属性是否修改成功
    stu.test()
    # 输出 320684198901010001
    # 输出 780
    # 私有的数据没有被修改过
    

      

    3 多态

    1 定义

    多态指的是一类事物有多种形态
    
    例如:
    
    动物有多种形态:
    
    人,狗,猪
    
    在程序中多态指的是,不同对象可以响应相同方法,并可以有自己不同的实现方式
    

      

    案例分析:

    import abc
    class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
        @abc.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')
            
    peo=People()
    dog=Dog()
    pig=Pig()
    
    #peo、dog、pig都是动物,只要是动物肯定有talk方法
    #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
    peo.talk()
    dog.talk()
    pig.talk()
    
    #更进一步,我们可以定义一个统一的接口来使用
    def func(obj):
        obj.talk()
    func(peo)
    func(dog)
    func(pig)
    

     

    那么多态的带来的好处是什么?

    1.增加了程序的灵活性

      以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)*

    2.增加了程序额可扩展性

      通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用*

     

     
    class Cat(Animal): #动物的另外一种形态:猫
        def talk(self):
            print('say miao')
    def func(animal): #对于使用者来说,自己的代码根本无需改动
        animal.talk()
    cat1=Cat() #实例出一只猫
    func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
    say miao
    '''
    这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
    '''
    

      

    4 鸭子类型

    1 定义

    如果一个对象叫声像鸭子,走路像鸭子,长得像鸭子,那它就是鸭子 
    

      

    class PC():
    
        def conntent_device(self, usb_device):
            usb_device.open()
            usb_device.work()
            usb_device.close()
    
    class Mouse:
        # 实现接口规定的所有功能
        def open(self):
            print("mouse opened")
    
        def work(self):
            print("mouse working...")
    
        def close(self):
            print("mouse closed")
    
    mouse = Mouse()
    pc = PC()
    
    pc.conntent_device(mouse)
    
    
    
    class KeyBoard:
        def open(self):
            print("KeyBoard opened")
    
        def work(self):
            print("KeyBoard working...")
    
        def close(self):
            print("KeyBoard closed")
    
    key1 = KeyBoard()
    
    # 如果key1的特征和行为都像USB设备 那就把它当做USB设备来使用
    # 对于使用者而言可以不用关心这个对象是什么类,是如如何是实现,
    pc.conntent_device(key1)
    
    '''
    mouse opened
    mouse working...
    mouse closed
    KeyBoard opened
    KeyBoard working...
    KeyBoard closed
    '''
    

      

     

     

  • 相关阅读:
    返回顶部
    判断元素在数组中
    Vue.js相关知识4-路由
    Vue.js相关知识3-路由
    Vue.js相关知识2-组件
    Vue.js相关知识1
    element表格左右滚动条在总计的上面怎么解决
    JAVA的安装及配置环境变量
    uni-app中使用vuex
    前端常用设计模式
  • 原文地址:https://www.cnblogs.com/wakee/p/13494460.html
Copyright © 2011-2022 走看看