python是一种面向对象的程序设计语言,面向对象的编程原则
本文主要详细介绍python的面向对象编程设计原理中的单责任原理。本文中的示例代码非常详细,具有一定的参考价值。感兴趣的朋友可以参考一下,希望能帮到你。
00-1010 I、封装(1)什么是封装(2)封装和访问(3)私有化和访问控制(1)、属性和方法私有化(2)变量名压缩(3)、方法重载(4)属性引用:getter、setter和property (2)单一职责原则(1)不符合单一职责原则的例子(2)单一职责原则(3)
目录
封装是面向对象编程的重要特征之一。
一,封装
封装是抽象对象的过程,它包含对象的属性和行为细节,并提供对它们的公共访问。
这样做有几个好处:
分离和实施。可以直接使用公共接口,但不需要考虑内部如何实现。具有内部状态隐藏机制,可以实现信息/状态隐藏。
(一)什么是封装
就面向对象编程而言,类是实现对象抽象的手段,封装的实现就是将对象的属性和行为抽象成类中的属性和方法。
举个例子:
对象AudioFile需要有一个文件名,还需要能够播放和停止播放。如果按类封装,它类似于以下实现:
音频文件:类
def __init__(自身,文件名):
self.filename=文件名
def play(自己):
打印(播放.)
定义停止(自我):
打印(停止播放.)
self参数必须是传入类方法的第一个(最左边的)参数;Python会通过这个参数自动填充实例对象(也就是调用这个方法的主体)。这个参数不一定要叫self,但是它的位置才是重点(C或Java程序员可能更喜欢叫它This,因为在这些语言中,名字反映的是同一个概念。在Python中,这个参数总是需要显式的)。
封装之后,能轻松实现访问:
if __name__==__main__:
File_name=金刚葫芦娃. mp3
当前文件=音频文件(文件名=文件名)
打印(当前文件.文件名)
current_file.play()
current_file.stop()
金刚葫芦娃. mp3
打金刚葫芦娃. mp3.
停止播放金刚葫芦娃. mp3.
同时能在外部修改内部的属性:
if __name__==__main__:
File_name=金刚葫芦娃. mp3
当前文件=音频文件(文件名=文件名)
打印(当前文件.文件名)
current_file.play()
current_file.stop()
current _ file . filename= Shuk and beta . ogg
打印(当前文件.文件名)
current_file.play()
c
urrent_file.stop()
>>>
金刚葫芦娃.mp3
playing 金刚葫芦娃.mp3...
stop playing 金刚葫芦娃.mp3...
舒克与贝塔.ogg
playing 舒克与贝塔.ogg...
stop playing 舒克与贝塔.ogg...
(三)私有化与访问控制
尽管能通过外部修改内部的属性或状态,但有时出于安全考虑,需要限制外部对内部某些属性或者方法的访问。
一些语言能显式地指定内部属性或方法的有效访问范围。比如在 Java 中明确地有public
、private
等关键字提供对内部属性与方法的访问限制,但 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, "Im 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):
"""Im 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 只是代码优化后的实际的结果,它本身并不是一个目标。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT软件开发工作室的更多内容!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。