17. 面向对象的特征

news2025/1/16 19:54:40

一、面向对象的三大特征

  面向对象的三大特征指的是 封装继承多态

  封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。

  继承(inheritance)的基本思想是,可以基于已有的类创建新类。继承以存在的类就是复用(继承)这些类的方法,而且可以增加一些新的属性和方法,使新类能够适应新的情况。

  多态(polynirphic)就是一个类的多种形态。

二、封装性

2.1、什么是封装性

  所谓的封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或对象开放,向没必要开放的类或对象异常信息。封装性就是隐藏内部对象的复杂性,只对外公开简单的接口。便于外部调用,从而提高系统的可扩展性、可维护性。通俗的来说,把该隐藏的隐藏起来,该暴露的暴露出来。

  当我们创建一个类时,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。此时,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其它制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,只能通过方法进行限制条件的添加。同时,我们需要避免用户在使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的(private)。此时,针对于属性就体现了封装性。

  使用封装后,确实增加了类的定义的复杂程度,但是它也确保了数据的安全性。我们隐藏了属性名,使调用者无法任意修改对象中的属性。并且我们增加了 getter() 和 setter() 方法,可以很好的控制属性是否为只读的。如果希望属性是只读的,则可以直接去掉 setter() 方法,如果希望属性不能被外部访问,则可以直接去掉 getter() 方法。而且在使用 setter() 方法设置属性时,可以增加数据的验证,确保数据的值是正确的。在使用 getter() 方法获取属性,使用 setter() 方法设置属性时,可以在读取属性和修改属性的同时做一些其它的处理。

2.2、封装性的体现

  我们将类的属性(xxx)隐藏,同时,提供的公共的方法类获取(getter())和设置(setter(self))此属性的值。在 Python 中,我们可以为对象的属性使用双下划线开头 __xxx,双下划线开头的属性,是对象的隐藏属性,隐藏属性值只能在类的内部访问,无法通过对象访问。

class Person:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name
  
    def set_name(self,name):
        self.__name = name

    def get_age(self):
        return self.__age
  
    def set_age(self,age):
        if age >= 0:
            self.__age = age

    def __show_info(self):
        print(f"name: {self.__name}, age: {self.__age}")

  如果我们直接访问类中的隐藏属性,会报以下错误:

p1 = Person("Sakura",10)
print(p1.__name)
AttributeError: 'Person' object has no attribute '__name'

  此时,我们可以通过公共的 getter() 方法和 setter() 方法调用类的私有属性:

p1 = Person("Sakura",10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

p1.set_age(12)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

p1.set_age(-10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())

  其实,隐藏属性是假隐藏,只不过是 Python 自动为属性改了一个名字。实际上,Python 将名字修改为 _类型__属性名

p1 = Person("Sakura",10)
p1._Person__age = 12
print(p1._Person__age)
p1._Person__show_info()

使用双下划线开头的属性,实际上依然可以在外部访问。在开发中,我们可以将一些私有属性以下划线开头(实际上是公开的属性),告诉开发人员没有特殊需要不要修改私有属性。

2.3、property装饰器

  property 装饰器,用来将一个 getter() 方法,转换为对象的属性,添加了 property 装饰器以后,我们就可以向调用属性一样使用 getter() 方法。使用 property 装饰的方法必须和属性名是一样的。setter() 方法的装饰器为:@属性名.setter;

class Person:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        print("get_name(self)方法执行了")
        return self.__name
  
    @name.setter
    def name(self,name):
        print("set_name(self,name)方法执行了")
        self.__name = name
  
    @name.deleter
    def name(self):
        print("del_name(self,name)方法执行了")

p = Person("Sakura")
print(p.name,end="\n\n")

p.name = "Mikoto"
print(p.name,end="\n\n")

del p.name

  在一些较老版本的 Python 解释器中,我们可以通过如下的方法实现。

class Person:
    def __init__(self,name):
        self.__name = name

    def get_name(self):
        print("get_name(self)方法执行了")
        return self.__name
  
    def set_name(self,name):
        print("set_name(self,name)方法执行了")
        self.__name = name
  
    def del_name(self):
        print("del_name(self,name)方法执行了")

    name = property(get_name, set_name, del_name)

p = Person("Sakura")
print(p.name,end="\n\n")

p.name = "Mikoto"
print(p.name,end="\n\n")

del p.name

三、继承性

3.1、什么是继承性

  继承,其基本思想就是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。

  通过继承可以直接让子类获取父类的方法和属性,避免编写重复性的代码,并且也符合 OCP 原则。所以,我们经常需要通过继承来对一个类进行扩展。

3.2、继承性的应用

  使用继承后,我们可以减少代码的冗余,提高了代码的复用性,并且使用继承后,便于功能的拓展。继承的出现让类与类之间产生了 is-a 的关系。

  在 Python 中,继承性的格式如下:

class A(父类):
    pass

  其中,A 类代表 子类(派生类、subclass),B 类代表 父类(基类、superclass)。一旦 子类 A 继承 父类 B 之后,子类 A 中就获取 父类 B 中声明的所有的结构:属性、方法(包括特殊方法);子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的拓展;

class Animal:
    def run(self):
        print("动物在跑")

    def sleep(self):
        print("动物在睡觉")

class Dog(Animal):
    def bark(self):
        print("汪汪汪")

dog = Dog()

dog.run()
dog.sleep()
dog.bark()
print()

# isinstance()检查一个对象是否是一个类的实例
# 如果这个类是这个对象的父类,也会返回true
print(isinstance(dog,Dog))
print(isinstance(dog,Animal))
print()

# issubclass()检查一个类是或否是另一个类的子类
print(issubclass(Dog,Animal))

如果在创建类时,省略了父类,则默认父类为 object,它是所有类的父类,所有类都继承 object;

3.3、方法重写

  如果在子类中有和父类同名的方法,则通过子类实例调用方法时,会调用子类的方法而不是父类的方法,这个特点我们成为 方法重写(覆盖,override)。

  当我们调用一个对象的方法时,它会优先去当前对象中寻找是否具有该方法,如果有则直接调用,如果没有则取当前对象的父类中寻找,如果父类中有则直接调用父类中的方法,如果没有则取父类中的父类中寻找,以此类推,直到找到 object,如果依然没有找到则报错。

class Animal:
    def run(self):
        print("动物在跑")

    def sleep(self):
        print("动物在睡觉")

class Dog(Animal):
    def run(self):
        print("狗在跑")

    def bark(self):
        print("汪汪汪")

dog = Dog()

dog.run()
dog.sleep()
dog.bark()

3.4、super()方法的使用

  父类中所有结构:属性 和 方法(包括特殊方法)都会被子类继承。如果我们希望直接调用父类中的结构,我们可以使用 super() 方法获取当前类的父类,并且通过 super() 调用父类方法时,不需要传递 self。

class Animal:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name

class Dog(Animal):
    def __init__(self,name,age):
        # 直接调用父类中的__init__(self)来初始化父类中定义的属性
        super().__init__(name)
        self.__age = age

    @property
    def age(self):
        return self.__age
  
    @age.setter
    def age(self,age):
        self.__age = age

dog = Dog("旺财",3)
print("name: ", dog.name, ", age: ", dog.age)

3.5、多重继承

  在 Python 中是支持多重继承的,也就是我们可以为一个类同时指定多个父类。我们可以在类名的 () 后面添加多个类,来实现多重继承。多重继承会使子类同时拥有多个父类,并且或获取所有父类中的方法。如果多个父类中有同名的方法,则会先在第一个父类中寻找,然后找第二个,然后找第三个,…,前面父类的方法会覆盖后面父类的方法。

class A:
    def test(self):
        print("AAA")

class B:
    def test(self):
        print("BBB")

class C(A,B):
    pass

# 类名.__bases__这个属性可以获取当前类的所有父类
print(C.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(C.mro())

c = C()
c.test()

super() 在调用父类的时候,它需要计算出当前到底调用哪个父类,在 Python 中实现这个功能的算法叫 C3 算法;

  在 Python 中子类可以同时继承多个父类的属性,这样可以最大限度地重用代码,但是使用多继承会导致扩展性变差,并且有可能会导致棱形问题(钻石问题)。棱形问题(钻石问题)指的是一个子类继承的多个父类汇集到一个非 object 类的身上。
在这里插入图片描述

class A:
    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(A):
    def test(self):
        print("from F")

class G(D,E,F):
    pass

# 类名.__bases__这个属性可以获取当前类的所有父类
print(G.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(G.mro())
obj = G()
obj.test()

  如果真的涉及到一个子类不可避免使用多个父类的属性,应该使用 Mixins 机制。Mixin 类表示某一功能,而不是某个物品,Python 对于Mixin 类的命名方式一般以 Mixin,able,ible 为后缀。我们可以将相关的功能放在一个 Mixin 类中,如果有多个不同功能,那可以写多个 Mixin 类,一个类可以继承多个 Mixin 类,但应该只继承一个表示其归属含义的父类,以确保遵循继承的 “is-a” 原则。Mixin 类不应该依赖于子类的实现,子类即便没有继承这个 Mixin 类,也可以照常工作,只是缺少了某个功能而已。

class Vehicle:
    pass

class FlyerMixin:
    def fly(self):
        pass

class CivilAircraft(FlyerMixin,Vehicle):
    pass

class Helicopter(FlyerMixin,Vehicle):
    pass

class Car(Vehicle):
    pass

四、多态性

  多态性指的是一个事物的多种形态;同样的行为(函数),传入不同的对象,得到不同的状态;多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象。

  它的格式如下:

class A:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name
  
class B:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
  
    @name.setter
    def name(self,name):
        self.__name = name

a = A("Sakura")
b = B("Mikoto")

# 对于say_hello()这个函数来说,只要对象中含有name属性,它就可以作为参数传递
# 这个函数并不会考虑对象的类型,只要有name属性即可
def say_hello(obj):
    print("你好,%s" %obj.name)

say_hello(a)
say_hello(b)
import abc

# 使用模块abc统一所有子类的标准
class Animal(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("汪汪汪!")

class Cat(Animal):
    def speak(self):
        print("喵喵喵!")

def make_noise(animal):
    animal.speak()

#animal = Animal()       # 不能实例化抽象类自己

dog = Dog()
cat = Cat()

make_noise(dog)
make_noise(cat)

看上去调用相同的方法,但实际上需要这个看这个对象是父类还是子类创建的对象,如果是父类创建的对象,一定调用父类中定义的方法,如果是子类创建的对象,那么就要看子类是否重写了父类的方法,如果子类要是重写了父类的方法,那么会调用子类的方法,如果子类没有重写父类方法,那么会调用父类的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2256518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache Dolphinscheduler可视化 DAG 工作流任务调度系统

Apache Dolphinscheduler 关于 一个分布式易扩展的可视化 DAG 工作流任务调度系统。致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开箱即用。 DolphinScheduler 的主要特性如下: 易于部署,提供四种部署方式&am…

第二部分:基础知识 6.函数 --[JavaScript 新手村:开启编程之旅的第一步]

JavaScript 函数是可重用的代码块,用于执行特定任务。函数可以接受参数(输入数据),并且可以返回一个值。JavaScript 提供了多种定义函数的方式,下面将详细介绍这些方式,并给出一些示例。 1. 函数声明 下面…

我眼中的“懂重构”(一)

初识重构 2017年的时候,领导让我看公司的一本书《重构——改善代码的既有设计》,这是一本JAVA版本的,前后看了2遍。那时候看书因为不懂看的格外仔细。我只是那时候不懂,然而多年后的今天我仍然发现很多人对重构充满误解。在刚进入…

机器学习详解(3):线性回归之代码详解

文章目录 1 数据预处理2 构建线性回归模型并绘制回归线初始化方法前向传播:forward_propagation代价函数:cost_function反向传播:backward_propagation参数更新:update_parameters训练方法:train代码运行结果 3 使用Py…

基于openzeppelin插件的智能合约升级

一、作用以及优点 部署可升级合约,插件自动部署proxy和proxyAdmin合约,帮助管理合约升级和交互;升级已部署合约,通过插件快速升级合约,脚本开发方便快捷;管理代理管理员的权限,只有proxyAdmin的…

游戏引擎学习第36天

仓库 :https://gitee.com/mrxiao_com/2d_game 回顾之前的内容 在这个程序中,目标是通过手动编写代码来从头开始制作一个完整的游戏。整个过程不使用任何库或现成的游戏引擎,这样做的目的是为了能够全面了解游戏执行的每一个细节。开发过程中&#xff0…

试题转excel;pdf转excel;试卷转Excel,word试题转excel

一、问题描述 一名教师朋友,偶尔会需要整理一些高质量的题目到excel中 以往都是手动复制搬运,几百道题几乎需要一个下午的时间 关键这些事,枯燥无聊费眼睛,实在是看起来就很蠢的工作 就想着做一个工具,可以自动处理…

16-01、JVM系列之:内存与垃圾回收篇(一)

JVM系列之:内存与垃圾回收篇(一) ##本篇内容概述: 1、JVM结构 2、类加载子系统 3、运行时数据区之:PC寄存器、Java栈、本地方法栈一、JVM与JAVA体系结构 JAVA虚拟机与JAVA语言并没有必然的联系,它只是与特…

2030. gitLab A仓同步到B仓

文章目录 1 A 仓库备份 到 B 仓库2 B 仓库修改main分支的权限 1 A 仓库备份 到 B 仓库 #!/bin/bash# 定义变量 REPO_DIR"/home/xhome/opt/git_sync/zz_xx_xx" # 替换为你的本地库A的实际路径 REMOTE_ORIGIN"http://192.168.1.66:8181/zzkj_software/zz_xx_xx.…

服务器上部署前端页面-实现IP+端口/index.html在线访问你的网页

首先一点,不管是那个框架开发的网页前端,最后都需要Build,构建完毕以后都是原始的HTML CSS JS 三样文件! 所以不管用原始的三剑客(HTML CSS JS)开发的前端还是用各类框架开发的前端界面(只是让开发简单…

树莓派 PICO RP2040 MACOS 使用

文章参考: Developing in C on the RP2040: macOS | Wellys Dev 这里会提示报错:ln: /bin/picotool: Operation not permitted 参考:Mac ln命令报错:Operation not permitted_ln operation not permitted-CSDN博客 放在 usr/lo…

顶顶通电话机器人开发接口对接大语言模型之实时流TTS对接介绍

大语言模型一般都是流式返回文字,如果等全部文字返回了一次性去TTS,那么延迟会非常严重,常用的方法就是通过标点符号断句,返回了一句话就提交给TTS。随着流TTS的出现,就可以直接把大模型返回的文字灌给流TTS&#xff0…

git使用-创建本地仓库、绑定远程仓库

文章目录 1. 创建git仓库2. commit提交到本地3. 创建远程仓库4. 关联远程仓库5. push代码至远程仓库6. 完成初始化 git作为版本控制工具,在开发过程中经常使用到。这里以github为例,简单介绍下仓库的创建及绑定,方便忘记了能快速的想起来。 1…

JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)

目录 JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码) 一、为什么要使用Array.sort() 二、Array.sort() 的使用与技巧 1、基础语法 2、返回值 3、…

为什么Unity里的变体数和UWA工具测出来的不一样

1)为什么Unity里的变体数和UWA工具测出来的不一样 2)使用TextureArray为什么会导致L1 Cache Miss率变高 3)Gfx.PresentFrame耗时异常高 4)AO方案中哪个更适合移动端 这是第412篇UWA技术知识分享的推送,精选了UWA社区的…

汽车免拆案例 | 2007款宝马650i车发动机偶尔无法起动

故障现象 一辆2007款宝马650i车,搭载N62B48B发动机,累计行驶里程约为26万km。车主反映,发动机偶尔无法起动,故障频率较低,十几天出现1 次,且故障出现时起动机不工作。 故障诊断  接车后试车,…

DataEase 是开源的 BI 工具

DataEase 是开源的 BI 工具,帮助用户快速分析数据并洞察业务趋势,从而实现业务的改进与优化。DataEase 支持丰富的数据源连接,能够通过拖拉拽方式快速制作图表, DataEase 的优势: 开源开放:零门槛&#xf…

【C#设计模式(17)——迭代器模式(Iterator Pattern)】

前言 迭代器模式可以使用统一的接口来遍历不同类型的集合对象,而不需要关心其内部的具体实现。 代码 //迭代器接口 public interface Iterator {bool HashNext();object Next(); } //集合接口 public interface Collection {Iterator CreateIterator(); } //元素迭…

在LabVIEW中如何利用FPGA提升算法性能

在LabVIEW中利用FPGA的性能进行算法开发,能极大提升数据处理和实时计算的速度。LabVIEW提供了针对FPGA的开发工具,如LabVIEW FPGA Module,可以使开发者将算法部署到FPGA硬件上,从而实现并行计算和低延迟操作。以下是如何在LabVIEW…

Day7 苍穹外卖项目 缓存菜品、SpringCache框架、缓存套餐、添加购物车、查看购物车、清空购物车

目录 1.缓存菜品 1.1 问题说明 1.2 实现思路 1.3 代码开发 1.3.1 加入缓存 1.3.2 清除缓存 1.3.2.1 新增菜品优化 1.3.2.2 菜品批量删除优化 1.3.2.3 修改菜品优化 1.3.2.4 菜品起售停售优化 1.4 功能测试 1.4.1 加入缓存 1.4.2 菜品修改 1.5 代码提交 2.缓存套餐 2.1 Spring C…