广告
返回顶部
首页 > 资讯 > 后端开发 > Python >python面向对象编程设计原则之单一职责原则详解
  • 871
分享到

python面向对象编程设计原则之单一职责原则详解

2024-04-02 19:04:59 871人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录一,封装(一)什么是封装(二)封装与访问(三)私有化与访问控制1,属性与方法的私有化2,变量名压缩3,方法重载(四)属性引用:getter、setter 与 property二,

一,封装

封装是面向对象编程思想的重要特征之一。

(一)什么是封装

封装是一个抽象对象的过程,它容纳了对象的属性和行为实现细节,并以此对外提供公共访问。

这样做有几个好处:

  • 分离使用与实现。可直接使用公共接口,但不需要考虑它内部具体怎么实现。
  • 拥有内部状态隐藏机制,可实现信息/状态隐藏。

(二)封装与访问

就面向对象编程来说,类就是实现对象抽象的手段,封装的实现,就是将对象的属性与行为抽象为类中属性与方法。

举个例子:

对象 AudioFile ,需要有文件名,还需要能播放与停止播放。用类封装的话,就类似于下面这个实现:

class AudioFil:
    def __init__(self, filename):
        self.filename = filename
    def play(self):
        print("playing...")
    def stop(self):
        print("stop playing...")

self参数必须是传入类方法的第一个(最左侧)参数;python 会通过这个参数自动填入实例对象(也就是调用这个方法的主体)。这个参数不必叫self,其位置才是重点(c++或Java程序员可能更喜欢把它称作this,因为在这些语言中,该名称反应的是相同的概念。在Python中,这个参数总是需要明确的)。

封装之后,能轻松实现访问:

if __name__ == "__main__":
    file_name = "金刚葫芦娃.mp3"
    current_file = AudioFil(filename=file_name)
    print(current_file.filename)
    current_file.play()
    current_file.stop()
>>>
金刚葫芦娃.mp3
playing 金刚葫芦娃.mp3...
stop playing 金刚葫芦娃.mp3...

同时能在外部修改内部的属性:

if __name__ == "__main__":
    file_name = "金刚葫芦娃.mp3"
    current_file = AudioFil(filename=file_name)
    print(current_file.filename)
    current_file.play()
    current_file.stop()
    current_file.filename = "舒克与贝塔.ogg"
    print(current_file.filename)
    current_file.play()
    current_file.stop()
>>>
金刚葫芦娃.mp3
playing 金刚葫芦娃.mp3...
stop playing 金刚葫芦娃.mp3...
舒克与贝塔.ogg
playing 舒克与贝塔.ogg...
stop playing 舒克与贝塔.ogg...

(三)私有化与访问控制

尽管能通过外部修改内部的属性或状态,但有时出于安全考虑,需要限制外部对内部某些属性或者方法的访问。

一些语言能显式地指定内部属性或方法的有效访问范围。比如在 Java 中明确地有 publicprivate 等关键字提供对内部属性与方法的访问限制,但 python 并提供另一种方式将它们的访问范围控制在类的内部:

  • _ 或 __来修饰属性与方法,使之成为内部属性或方法。
  • 用 __method-name__ 来实现方法重载。

1,属性与方法的私有化

举个例子:

class AudioFil:
    def __init__(self, filename):
        self._filename = filename
    def play(self):
        print(f"playing {self._filename}...")
    def stop(self):
        print(f"stop playing {self._filename}...")

if __name__ == "__main__":
    file_name = "金刚葫芦娃.mp3"
    current_file = AudioFil(filename=file_name)
    print(current_file._filename)
    current_file.play()
    current_file.stop()

在这里插入图片描述

注意 _filename 的格式,单下划线开头表明这是一个类的内部变量,它提醒程序员不要在外部随意访问这个变量,尽管是能够访问的。

更加严格的形式是使用双下划线:

class AudioFil:
    def __init__(self, filename):
        self.__filename = filename
    def play(self):
        print(f"playing {self.__filename}...")
    def stop(self):
        print(f"stop playing {self.__filename}...")

if __name__ == "__main__":
    file_name = "金刚葫芦娃.mp3"
    current_file = AudioFil(filename=file_name)
    print(current_file.__filename)	#AttributeError: 'AudioFil' object has no attribute '__filename'
    current_file.play()
    current_file.stop()

注意 __filename 的格式,双下划线开头表明这是一个类的内部变量,它会给出更加严格的外部访问限制,但还是能够通过特殊手段实现外部访问:

    # print(current_file.__filename)
    print(current_file._AudioFil__filename)

_ClassName__attributename

总之,这种私有化的手段“防君子不防小人”,更何况这并非是真的私有化——伪私有化。有一个更加准确的概念来描述这种机制:变量名压缩。

2,变量名压缩

Python 支持变量名压缩(mangling,起到扩展作用)的概念——让类内某些变量局部化。

压缩后的变量名通常会被误认为是私有属性,但这其实只是一种把类所创建的变量名局部化的方式而已:名称压缩并无法阻止类外代码对它的读取。

这种机制主要是为了避免实例内的命名空间的冲突,而不是限制变量名的访问。因此,压缩过的变量名最好称为“伪私有”,而不是“私有”。

类内部以 _ 或 __ 开头进行命名的操作只是一个非正式的惯例,目的是让程序员知道这是一个不应该修改的名字(它对Python自身来说没有什么意义)。

3,方法重载

python 内置的数据类型自动地支持有些运算操作,比如 + 运算、索引、切片等,它们都是通过对应对象的类的内部的以 __method-name__ 格式命名的方法来实现的。

方法重载可用于实现模拟内置类型的对象(例如,序列或像矩阵这样的数值对象),以及模拟代码中所预期的内置类型接口。

最常用的重载方法是__init__构造方法,几乎每个类都使用这个方法为实例属性进行初始化或执行其他的启动任务。

方法中特殊的self参数和__init__构造方法是 Python OOP的两个基石

举个例子:

class AudioFil:
    def __init__(self, filename):
        self.__filename = filename
    def __str__(self):
        return f"我是《{self.__filename}》"
    def play(self):
        print(f"playing {self.__filename}...")
    def stop(self):
        print(f"stop playing {self.__filename}...")

if __name__ == "__main__":
    file_name = "金刚葫芦娃.mp3"
    current_file = AudioFil(filename=file_name)
    print(current_file)		#>>> 我是《金刚葫芦娃.mp3》

(四)属性引用:getter、setter 与 property

一些语言使用私有属性的方式是通过 getter 与 setter 来实现内部属性的获取与设置。python 提供 property 类来达到同样的目的。举个例子:

class C:
    def __init__(self):
        self._x = None
    def getx(self) -> str:
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

if __name__ == '__main__':
    c = C()
    c.x = "ccc" # 调用setx
    print(c.x)  # 调用getx
    del c.x     # 调用delx

property的存在让对属性的获取、设置、删除操作自动内置化。

更加优雅的方式是使用@property装饰器。举个例子:

class C:
    def __init__(self):
        self._x = None
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

if __name__ == '__main__':
    c = C()
    c.x = "ccc"
    print(c.x)  
    del c.x

二,单一职责原则

(一)一个不满足单一职责原则的例子

现在需要处理一些音频文件,除了一些描述性的属性之外,还拥有播放、停止播放和信息存储这三项行为:

class AudioFile:
    def __init__(self, filename, author):
        self.__filename = filename
        self.__author = author
        self.__type = self.__filename.split(".")[-1]
    def __str__(self):
        return f"我是《{self.__filename}》"
    def play(self):
        print(f"playing {self.__filename}...")
    def stop(self):
        print(f"stop playing {self.__filename}...")
    def save(self, filename):
        content = {}
        for item in self.__dict__:
            key = item.split("__")[-1]
            value = self.__dict__[item]
            content[key] = value
        with open(filename+".txt", "a") as file:
            file.writelines(str(content)+'\n')

if __name__ == '__main__':
    file_name = "金刚葫芦娃.mp3"
    author_name = "姚礼忠、吴应炬"
    current_file = AudioFile(filename=file_name,author=author_name)
    current_file.save(filename="info_list")

这个类能够正常工作。

注意观察 save 方法,在保存文件信息之前,它做了一些格式化的工作。显然后面的工作是“临时添加”的且在别的文件类型中可能也会用到。
随着项目需求的变更或者其他原因,经常会在方法内部出现这种处理逻辑的扩散现象,即完成一个功能,需要新的功能作为前提保障。

从最简单的代码可重用性的角度来说,应该将方法内可重用的工作单独提出来:

至于公共功能放在哪个层次,请具体分析。

def info_fORMat(obj):
    content = {}
    for item in obj.__dict__:
        key = item.split("__")[-1]
        value = obj.__dict__[item]
        content[key] = value
    return content
class AudioFile:
    ...
    def save(self, filename):
        content = info_format(self)
        with open(filename+".txt", "a") as file:
            file.writelines(str(content)+'\n')

但是,给改进后的代码在遇到功能变更时,任然需要花费大力气在原有基础上进行修改。比如需要提供信息搜索功能,就可能出现这种代码:

class AudioFile:
    ...
    def save(self, filename):
        ...
    def search(self, filename, key=None):
        ...

如果后期搜索条件发生变更、或者再新增功能,都会导致类内部出现功能扩散,将进一步增加原有代码的复杂性,可读性逐渐变差,尤其不利于维护与测试

(二)单一职责原则

单一职责原则(Single-Responsibility Principle,SRP)由罗伯特·C.马丁于《敏捷软件开发:原则、模式和实践》一书中提出。这里的职责是指类发生变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。

该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  • 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
  • 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。

举个例子:一个编译和打印报告的模块。想象这样一个模块可以出于两个原因进行更改。

首先,报告的内容可能会发生变化。其次,报告的格式可能会发生变化。这两件事因不同的原因而变化。单一职责原则说问题的这两个方面实际上是两个独立的职责,因此应该在不同的类或模块中。

总之,单一职责原则认为将在不同时间因不同原因而改变的两件事情结合起来是一个糟糕的设计。

看一下修改后的代码:

class AudioFile:
    def __init__(self, filename, author):
        self.__filename = filename
        self.__author = author
        self.__type = self.__filename.split(".")[-1]
    def __str__(self):
        return f"我是《{self.__filename}》"
    def play(self):
        print(f"playing {self.__filename}...")
    def stop(self):
        print(f"stop playing {self.__filename}...")

class AudioFileDataPersistence:
    def save(self, obj, filename):
        ...
class AudioFileDataSearch:
    def search(self, key, filename):
        ...

if __name__ == '__main__':
    file_name = "金刚葫芦娃.mp3"
    author_name = "姚礼忠、吴应炬"
    current_file = AudioFile(filename=file_name, author=author_name)
    data_persistence = AudioFileDataPersistence()
    data_persistence.save(current_file, filename="info_list")
    data_search = AudioFileDataSearch()
    data_search.search(file_name, filename="info_list")

但这样将拆分代码,是不是合理的选择?

三,封装与单一职责原则

从封装的角度看来说,它的目的就是在对外提供接口的同时,提高代码的内聚性和可重用性,但功能大而全的封装更加的不安全。

单一职责原则通过拆分代码实现更低的耦合性和更高的可重用性,但过度拆分会增加对象间交互的复杂性。

关于两这的结合,有一些问题需要事先注意:

  • 需求的粒度是多大?
  • 维护的成本有多高?

作为面向对象编程的基础概念与实践原则,二者实际上是因果关系——如果一个类是有凝聚力的,如果有一个更高层次的目的,如果它的职责符合它的名字,那么 SRP 就会自然而然地出现。SRP 只是代码优化后的实际的结果,它本身并不是一个目标。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!  

--结束END--

本文标题: python面向对象编程设计原则之单一职责原则详解

本文链接: https://www.lsjlt.com/news/141861.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • python面向对象编程设计原则之单一职责原则详解
    目录一,封装(一)什么是封装(二)封装与访问(三)私有化与访问控制1,属性与方法的私有化2,变量名压缩3,方法重载(四)属性引用:getter、setter 与 property二,...
    99+
    2022-11-13
  • C#面向对象设计原则之单一职责原则
    单一职责原则(SRP) 定义:系统中的每一个类都应该只有一个职责。 好处:高内聚、低耦合。 解释说明: 单一职责也就是说我们应该让一个类或一个对象只做一件事情,每个类所要关注的就是自...
    99+
    2022-11-13
  • java面向对象设计原则之单一职责与依赖倒置原则详解
    目录单一职责概念 实现 拓展 依赖倒置原则概念 示例 拓展 单一职责概念 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。交杂不清的职责将使...
    99+
    2022-11-12
  • Java设计模式七大原则之单一职责原则详解
    目录定义案例需求方案一方案二对比分析总结如何遵守单一职责原则定义 单一职责原则(Single Responsibility Principle, SRP),有且仅有一个原因引起类的变...
    99+
    2022-11-13
  • Java设计模式之单一职责原则精解
    1.什么是单一职责原则? 首先我们可以对某个类来说,即一个类应该只负责一项职责。如类A负责两个不同职责: 职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要...
    99+
    2022-11-13
  • Java设计模式中单一职责原则详解
    目录前言一、介绍二、代码演示前言 本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也许一个人独行,可以走的很快,但是一群人结伴而行,才...
    99+
    2023-05-20
    Java 设计模式 Java 单一职责原则
  • java面向对象设计原则之合成复用原则示例详解
    目录概念示例拓展概念 尽量使用合成/聚合,而不是使用继承实现复用。所谓的合成/聚合是指一个对象里持有另外一个类的对象,通过调用这些对象的方法得到复用已有功能的目的。如:报文解译程序中...
    99+
    2022-11-12
  • java面向对象设计原则之接口隔离原则示例详解
    目录概念实现拓展概念 小接口原则,即每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。如下图所示定义了一个接口,包含了5个方法,实现类A用到了3个方法、实现类B用...
    99+
    2022-11-12
  • java面向对象设计原则之里氏替换原则示例详解
    目录概念实现拓展概念 里氏替换原则是任何基类出现的地方,子类一定可以替换它;是建立在基于抽象、多态、继承的基础复用的基石,该原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机...
    99+
    2022-11-12
  • java面向对象设计原则之开闭原则示例解析
    概念 唯一不变的是不断的变化,在软件开发中应该对需求的变化持开放态度,我们要做的就是如何将这种变化对我们现有的成果带来最小的冲击。开闭原则直接面对面向对象程序的目标扩展性和可维护性,...
    99+
    2022-11-12
  • java面向对象设计原则之迪米特法则分析详解
    目录概念使用拓展概念 迪米特法则解决类与类之间耦合度问题,如果类A调用了B类的某一个方法,则这两个类就形成了一种紧耦合的方式,当B类这个方法发生变化时,一定会影响A类的执行结果。迪米...
    99+
    2022-11-12
  • C#开发经验分享:面向对象编程与设计原则
    C#(C Sharp)是一门强大而受欢迎的面向对象编程语言,广泛应用于软件开发领域。在C#开发过程中,了解面向对象编程(OOP)的基本概念和设计原则是非常重要的。面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,并通过对象之间的交...
    99+
    2023-11-22
    面向对象 设计原则 C#开发
  • C#面向对象编程中开闭原则的示例详解
    目录开闭原则C# 示例改进总结在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特&mi...
    99+
    2022-11-13
  • C#面向对象编程中依赖反转原则的示例详解
    在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特·C·马...
    99+
    2022-11-13
  • C#面向对象编程中里氏替换原则的示例详解
    目录里氏替换原则C# 示例糟糕的示范正确的示范总结在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程...
    99+
    2022-11-13
  • C#面向对象编程中接口隔离原则的示例详解
    目录接口隔离原则C# 示例糟糕的示范正确的示范总结在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程...
    99+
    2022-11-13
  • C# 实例解释面向对象编程中的单一功能原则(示例代码)
    在面向对象编程领域中,单一功能原则(Single responsibility principle)规定每个类都应该有且仅有一个单一的功能,并且该功能应该由这个类完全封装起来。 在面...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作