• 面向对象的三大特性: 继承,封装,多态


    目录

    面向对象之继承

    继承的语法

    继承的本质

    名字的查找顺序

    1. 在不继承的情况下名字的查找顺序

    2. 单继承的情况下名字的查找顺序

    3. 多继承的情况下名字的查找顺序

    非菱形继承

    菱形继承

    经典类与新式类

    派生方法

    面向对象三大特性之封装

    property伪装属性

    面向对象三大特性之多态

    优点:

    弊端:​​​​​​​

    鸭子类型

    总结


    面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念是类和对象。

    面向对象程序设计方法是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,也即使得描述问题的问题空间与问题的解决方案空间在结构上尽可能一致,把客观世界中的实体抽象为问题域中的对象。

     面向对象程序设计以对象为核心,该方法认为程序由一系列对象组成。类是对现实世界的抽象,包括表示静态属性的数据和对数据的操作,对象是类的实例化。对象间通过消息传递相互通信,来模拟现实世界中不同实体间的联系。在面向对象的程序设计中,对象是组成程序的基本模块.

    面向对象有三大特性:继承,封装 和 多态, 其中继承是我们后期用的比较多的特性之一.

    面向对象之继承

    继承性, 其主要指的是两种或者两种以上的类之间的联系与区别。继承,顾名思义,是后者延续前者的某些方面的特点,而在面向对象技术则是指一个对象针对于另一个对象的某些独有的特点、能力进行复制或者延续。如果按照继承源进行划分,则可以分为单继承(一个对象仅仅从另外一个对象中继承其相应的特点)与多继承(一个对象可以同时从另外两个或者两个以上的对象中继承所需要的特点与能力,并且不会发生冲突等现象);如果从继承中包含的内容进行划分,则继承可以分为四类,分别为取代继承(一个对象在继承另一个对象的能力与特点之后将父对象进行取代)、包含继承(一个对象在将另一个对象的能力与特点进行完全的继承之后,又继承了其他对象所包含的相应内容,结果导致这个对象所具有的能力与特点大于等于父对象,实现了对于父对象的包含)、受限继承、特化继承。

    继承的目的就是为了节省代码

    继承的语法

    1. class 类名(类名):
    2. pass

    1. 定义类的时候在类名后面加括号

    2. 括号内填写你需要继承的类名

    3. 括号内可以填写多个父类, 逗号隔开即可.

    我们将被继承的类称之为: 父类或者基类,或者超类

    将继承的类称之为: 子类或者派生类

    1. class MyClass(F1,F2,F3):
    2. pass

    继承的本质

    抽象>> 将多个类共同的数据或功能抽取出来形成一个基类

    继承>> 从上往下白嫖哥哥积累里边的资源

    对象: 数据和功能的结合体

    类:  多个对象相同的数据和功能的结合体

    父类: 多个类相同的数据和功能的结合体

    名字的查找顺序

    1. 在不继承的情况下名字的查找顺序

            先从自身查找, 没有的化再去产生该对象的类中查找.  >>> 从对象到类

    1. class Student:
    2. school = '清华大学'
    3. def choice_course(self):
    4. print('正在选课')
    5. stu1 = Student()
    6. print(stu1.school) # 对象查找school 自身名称空间没有 所以查找的是类的 清华大学
    7. stu1.school = '北京大学' # 在自身的名称空间中产生了新的school
    8. """对象点名字并写了赋值符号和数据值 那么操作的肯定是自己的名称空间"""
    9. print(stu1.school) # 北京大学
    10. print(Student.school) # 清华大学

    2. 单继承的情况下名字的查找顺序

    先从对象自身查找, 然后是产生该对象的类, 然后是一个个父类. >>> 对象到类再到父类

    1. class A:
    2. # name = 'from A'
    3. pass
    4. class B(A):
    5. # name = 'from B'
    6. pass
    7. class C(B):
    8. # name = 'from C'
    9. pass
    10. class MyClass(C):
    11. # name = 'from MyClass'
    12. pass
    13. obj = MyClass()
    14. # obj.name = '找到了!!!'
    15. print(obj.name)

    3. 多继承的情况下名字的查找顺序

    菱形继承

    [最后不会鬼宗道一个我们自定义的类上]

    遵循深度优先原则, 每个分支都走到底, 在切换

    菱形继承

    [最后归宗到一个我们自定义的类上]

    遵循广度优先原则,[前面几个分支都不会走到最后一个类, 只有最后一个分支才会走最后一个类]

     也可以借助pycharm 使用mro()方法插卡该类产生的对象名字查找顺序

    总结: 涉及到对象查找名字几乎都是对象自身>>>>类>>>.父类

    经典类与新式类

    经典类即不继承object或者其子类的类[什么都不继承]

    新式类即继承了object或者其子类的类

    在python3中所有的类默认都会继承object,也就是说py3里只有新式类. 

    而在py2里却有新式类和经典类, 不过由于经典类并没有什么核心功能,便就不再延用了. 

    我们在定义类的时候, 如果没有想要继承的父类, 一般推荐的写法如下

    1. class MyClass(object):
    2. pass

    其目的就是为了兼容python2, python3

    派生方法

    子类继承了父类, 在子类中定义类与父类一模一样的方法,并且扩展了该方法的功能, 这个特点就是类的派生.

    子类调用父类的方法,利用关键字 super().父类的方法(), 在此基础上进行数据的拦截,,添加,原路返回等步骤, 达到修改父类的目的. 

    派生方法案例

    1. import datetime
    2. import json
    3. d = {
    4. 't1': datetime.datetime.today(),
    5. 't2': datetime.date.today()
    6. }
    7. res = json.dumps(d)
    8. print(res)

    上述代码会报错, 无法正常序列化

    1. raise TypeError(f'Object of type {o.__class__.__name__} '
    2. TypeError: Object of type datetime is not JSON serializable

    因为json序列化python数据类型是有限制的

    PYTHONJSON
    dict    object
    list, tuplearray
    str string 
    int, floatnumber        
     True true  
    Falsefalse 
    None null 


    解决方式1: 

    手动将不符合数据类型要求的数据转成符合要求的

    1. d = {
    2. 't1': str(datetime.datetime.today()),
    3. 't2': str(datetime.date.today())
    4. }
    5. res = json.dumps(d)
    6. print(res)

    解决方式2:利用派生方法

    1. """
    2. class JSONEncoder:
    3. pass
    4. dumps(obj,cls=None):
    5. if cls == None:
    6. cls = JSONEncoder
    7. return cls(...) # JSONEncoder()
    8. 查看JSONEncoder源码发现序列化报错是有default方法触发的
    9. raise TypeError(f'Object of type {o.__class__.__name__} '
    10. f'is not JSON serializable')
    11. 我们如果想要避免报错 那么肯定需要对default方法做修改(派生)
    12. """
    1. class MyJsonEncode(json.JSONEncoder):
    2. def default(self, o):
    3. '''o就是json即将要序列化的数据'''
    4. if isinstance(o, datetime.datetime):
    5. return o.strftime('%Y-%m-%d %H:%M:%S')
    6. elif isinstance(o, datetime.date):
    7. return o.strftime('%Y-%m-%d')
    8. # 如果是可以序列化的类型 那么不做任何处理 直接让它序列化即可
    9. return super().default(o)
    10. res = json.dumps(d, cls=MyJsonEncode)
    11. print(res)
    12. json.dumps(d, cls=MyJsonEncode)

    面向对象三大特性之封装

    封装其实就是将数据或者功能隐藏起来, 隐藏的目的不是昂用户无法使用, 而是给这些隐藏的数据开设特定的接口, 让用户通过接口才能使用我们在接口中添加的一些额外操作

    在类定义阶段就使用__双下划线开头的名字 都是隐藏的属性

    后续类和对象都无法直接获取

     在python中不会真正的限制任何代码, 隐藏的属性如果真需要访问,需要通过变形处理 __变量名>>> _类名__变量名

    使用变形之后的名字去访问隐藏的数据就失去了隐藏的含义

    1. class Student(object):
    2. __school = '清华大学'
    3. def __init__(self, name, age):
    4. self.__name = name
    5. self.__age = age
    6. # 专门开设一个访问学生数据的通道(接口)
    7. def check_info(self):
    8. print("""
    9. 学生姓名:%s
    10. 学生年龄:%s
    11. """ % (self.__name, self.__age))
    12. # 专门开设一个修改学生数据的通道(接口)
    13. def set_info(self,name,age):
    14. if len(name) == 0:
    15. print('用户名不能为空')
    16. return
    17. if not isinstance(age,int):
    18. print('年龄必须是数字')
    19. return
    20. self.__name = name
    21. self.__age = age
    22. stu1 = Student('yietong', 22)
    23. stu1.set_info('','happy new year')
    1. 我们编写python很多时候都是大家墨守成规的东西 不需要真正的限制
    2. class A:
    3. _school = '清华大学'
    4. def _choice_course(self):
    5. pass

    property伪装属性

    简单的理解为将方法伪装成数据

    obj.name   只需要数据点名字即可取到值

    obj.func()   这个方法还需要加括号

    伪装之后就可以将func方法伪装成数据 obj.func

    举个例子,计算人体BMI指数[ 这个方法更像是人的数据]

    1. class Person:
    2. def __init__(self,name,weight,height):
    3. self.name = name
    4. self.weight = weight
    5. self.height = height
    6. @property
    7. def BMI(self):
    8. return self.weight/(self.height**2)
    9. p = Person('yietong',58,160)
    10. print(p.BMI)
    1. class Foo:
    2. def __init__(self, val):
    3. self.__NAME = val # 将属性隐藏起来
    4. @property
    5. def name(self):
    6. return self.__NAME
    7. @name.setter
    8. def name(self, value):
    9. if not isinstance(value, str): # 在设定值之前进行类型检查
    10. raise TypeError('%s must be str' % value)
    11. self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME
    12. @name.deleter
    13. def name(self):
    14. raise PermissionError('Can not delete')
    15. obj = Foo('yietong')
    16. # print(obj.name)
    17. # obj.name = 666
    18. # print(obj.name)
    19. del obj.name

    面向对象三大特性之多态

    从宏观的角度来讲,多态性是指在面向对象技术中,当不同的多个对象同时接收到同一个完全相同的消息之后,所表现出来的动作是各不相同的,具有多种形态;从微观的角度来讲,多态性是指在一组对象的一个类中,面向对象技术可以使用相同的调用方式来对相同的函数名进行调用,即便这若干个具有相同函数名的函数所表示的函数是不同的。

    多态即一种事务的多种形态, 但是相同的功能应该有相同的名字.

    前提条件:

               A:要有继承关系。

              B:要有方法重写。

              C:要有父类引用指向子类对象。

    多态间的成员特点:
             方法有重写,而变量没有

             成员变量

                                编译看左边,运行看左边。

             成员方法

                                编译看左边,运行看右边。

            注意:多态有三种体现形式

                       类多态

                       抽象类多态

                       接口多态

    优点:

             提高代码的扩展性和可维护性。

    弊端:

             父类引用不能使用子类特有功能。

             基本类型:隐式转换(小到大),强制转换(大到小)。

             引用类型:向上转型(小到大),向下转型(大到小)。

    1. class Animal
    2. {
    3. public void sing()
    4. {
    5. System.out.println("动物在唱歌!");
    6. }
    7. }
    8. class Dog extends Animal
    9. {
    10. public void sing()
    11. {
    12. System.out.println("狗在唱歌!");
    13. }
    14. }
    15. class Cat extends Animal
    16. {
    17. public void sing()
    18. {
    19. System.out.println("猫在唱歌!");
    20. }
    21. public void eat()
    22. {
    23. System.out.println("猫在吃饭!");
    24. }
    25. }
    26. public class PolyTest
    27. {
    28. public static void main(String[] args)
    29. {
    30. //向上类型转换
    31. Cat cat = new Cat();
    32. Animal animal = cat;
    33. animal.sing();
    34. //向下类型转换
    35. Animal a = new Cat();
    36. Cat c = (Cat)a;
    37. c.sing();
    38. c.eat();
    39. //编译错误
    40. //用父类引用调用父类不存在的方法
    41. //Animal a1 = new Cat();
    42. //a1.eat();
    43. //编译错误
    44. //向下类型转换时只能转向指向的对象类型
    45. //Animal a2 = new Cat();
    46. //Cat c2 = (Dog)a2;
    47. }
    48. }

    python提供了一种强制性的操作, 

    1. import abc
    2. 指定metaclass属性, 将类设置为抽象类, 抽象类本身只是用来约束子类的, 不能被实例化.
    3. class Animal(metaclass=abc.ABCMeta):
    4.         @abc.abstractmethod   # 该装饰器限制子类必须定义应该名为talk 的方法
    5.         def talk(self)   # 抽象方法中无需实现具体的功能
    6.         pass
    7. class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
    8.     def talk(self):
    9.        pass
    10.     def run(self):
    11.         pass
    12. obj = Person()

    鸭子类型

    鸭子类型指的是面向对象中,子类不需要显示的继承某个类,只要有某个类的方法和属性,那它就属于这个类。

    例如:鸭子类bob有两个方法 speak 和 run

    有一只普通鸭子 ,ann,如果它也是鸭子, 它需要继承bob类,只要继承了bob类就什么都不用写了,普通鸭子类ann的对象就是bob鸭子这种类型. 如果不继承,普通鸭子ann的对象就不是鸭子这种类型。

    但是python不推崇这个, 他推崇的鸭子类型指的是》》 不需要显示的继承某个类, 只要我的类中有run和speak方法, 那么我就是鸭子这个类, 即 : 走路像鸭子,说话像鸭子他就是鸭子

    但是, 如果使用python鸭子类型的写法, 如果方法写错了,他就不是这个类型了, 会出问题

    为了解决这个问题, python提供了两种解决措施

    1, abc模块: 装饰后,必须重写方法, 不重写就报错

    2, drf源码中使用的, 父类中写这个方法, 但是没有具体实现,直接抛出异常。 

    总结

      封装即隐藏对象的属性和实现细节,仅对外提供公共访问方式,保证了模块具有较好的独立性,使得程序维护修改较为容易。继承提高了代码的复用性,多态提高代码的扩展性和可维护性。三者共同达到了一个目的:使得对应用程序的修改带来的影响更加局部化,程序更加健壮,而且易维护、更新和升级。

  • 相关阅读:
    【Linux】翻山越岭——进程地址空间
    深化校企合作|云畅科技与湖南女子学院签订校企合作协议
    React——相关js库以及使用React开发者工具调试
    Java -- Cron表达式构建
    Linux下JSON解析工具
    9月16日计算机视觉基础学习笔记——认识机器视觉
    【无标题】
    kaggle服务器使用问题
    力扣(LeetCode)82. 删除排序链表中的重复元素 II(C语言)
    面试官:“ES6中新增的Set方法去重你会吗?”我:“看文章就知道了”
  • 原文地址:https://blog.csdn.net/weixin_67531112/article/details/126787645