您好,登錄后才能下訂單哦!
今天小編給大家分享一下python的單一職責(zé)原則怎么實(shí)現(xiàn)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
封裝是面向?qū)ο缶幊趟枷氲闹匾卣髦弧?/p>
封裝是一個(gè)抽象對(duì)象的過(guò)程,它容納了對(duì)象的屬性和行為實(shí)現(xiàn)細(xì)節(jié),并以此對(duì)外提供公共訪問(wèn)。
這樣做有幾個(gè)好處:
分離使用與實(shí)現(xiàn)。可直接使用公共接口,但不需要考慮它內(nèi)部具體怎么實(shí)現(xiàn)。
擁有內(nèi)部狀態(tài)隱藏機(jī)制,可實(shí)現(xiàn)信息/狀態(tài)隱藏。
就面向?qū)ο缶幊虂?lái)說(shuō),類(lèi)就是實(shí)現(xiàn)對(duì)象抽象的手段,封裝的實(shí)現(xiàn),就是將對(duì)象的屬性與行為抽象為類(lèi)中屬性與方法。
舉個(gè)例子:
對(duì)象 AudioFile ,需要有文件名,還需要能播放與停止播放。用類(lèi)封裝的話,就類(lèi)似于下面這個(gè)實(shí)現(xiàn):
class AudioFil: def __init__(self, filename): self.filename = filename def play(self): print("playing...") def stop(self): print("stop playing...")
self
參數(shù)必須是傳入類(lèi)方法的第一個(gè)(最左側(cè))參數(shù);Python 會(huì)通過(guò)這個(gè)參數(shù)自動(dòng)填入實(shí)例對(duì)象(也就是調(diào)用這個(gè)方法的主體)。這個(gè)參數(shù)不必叫self,其位置才是重點(diǎn)(C++或Java程序員可能更喜歡把它稱(chēng)作this,因?yàn)樵谶@些語(yǔ)言中,該名稱(chēng)反應(yīng)的是相同的概念。在Python中,這個(gè)參數(shù)總是需要明確的)。
封裝之后,能輕松實(shí)現(xiàn)訪問(wèn):
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...
同時(shí)能在外部修改內(nèi)部的屬性:
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...
盡管能通過(guò)外部修改內(nèi)部的屬性或狀態(tài),但有時(shí)出于安全考慮,需要限制外部對(duì)內(nèi)部某些屬性或者方法的訪問(wèn)。
一些語(yǔ)言能顯式地指定內(nèi)部屬性或方法的有效訪問(wèn)范圍。比如在 Java 中明確地有 public
、private
等關(guān)鍵字提供對(duì)內(nèi)部屬性與方法的訪問(wèn)限制,但 python 并提供另一種方式將它們的訪問(wèn)范圍控制在類(lèi)的內(nèi)部:
用_
或 __
來(lái)修飾屬性與方法,使之成為內(nèi)部屬性或方法。
用 __method-name__
來(lái)實(shí)現(xiàn)方法重載。
舉個(gè)例子:
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 的格式,單下劃線開(kāi)頭表明這是一個(gè)類(lèi)的內(nèi)部變量,它提醒程序員不要在外部隨意訪問(wèn)這個(gè)變量,盡管是能夠訪問(wèn)的。
更加嚴(yán)格的形式是使用雙下劃線:
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 的格式,雙下劃線開(kāi)頭表明這是一個(gè)類(lèi)的內(nèi)部變量,它會(huì)給出更加嚴(yán)格的外部訪問(wèn)限制,但還是能夠通過(guò)特殊手段實(shí)現(xiàn)外部訪問(wèn):
# print(current_file.__filename) print(current_file._AudioFil__filename)
_ClassName__attributename
總之,這種私有化的手段“防君子不防小人”,更何況這并非是真的私有化——偽私有化。有一個(gè)更加準(zhǔn)確的概念來(lái)描述這種機(jī)制:變量名壓縮。
Python 支持變量名壓縮(mangling,起到擴(kuò)展作用)的概念——讓類(lèi)內(nèi)某些變量局部化。
壓縮后的變量名通常會(huì)被誤認(rèn)為是私有屬性,但這其實(shí)只是一種把類(lèi)所創(chuàng)建的變量名局部化的方式而已:名稱(chēng)壓縮并無(wú)法阻止類(lèi)外代碼對(duì)它的讀取。
這種機(jī)制主要是為了避免實(shí)例內(nèi)的命名空間的沖突,而不是限制變量名的訪問(wèn)。因此,壓縮過(guò)的變量名最好稱(chēng)為“偽私有”,而不是“私有”。
類(lèi)內(nèi)部以 _
或 __
開(kāi)頭進(jìn)行命名的操作只是一個(gè)非正式的慣例,目的是讓程序員知道這是一個(gè)不應(yīng)該修改的名字(它對(duì)Python自身來(lái)說(shuō)沒(méi)有什么意義)。
python 內(nèi)置的數(shù)據(jù)類(lèi)型自動(dòng)地支持有些運(yùn)算操作,比如 + 運(yùn)算、索引、切片等,它們都是通過(guò)對(duì)應(yīng)對(duì)象的類(lèi)的內(nèi)部的以 __method-name__
格式命名的方法來(lái)實(shí)現(xiàn)的。
方法重載可用于實(shí)現(xiàn)模擬內(nèi)置類(lèi)型的對(duì)象(例如,序列或像矩陣這樣的數(shù)值對(duì)象),以及模擬代碼中所預(yù)期的內(nèi)置類(lèi)型接口。
最常用的重載方法是__init__
構(gòu)造方法,幾乎每個(gè)類(lèi)都使用這個(gè)方法為實(shí)例屬性進(jìn)行初始化或執(zhí)行其他的啟動(dòng)任務(wù)。
方法中特殊的self
參數(shù)和__init__
構(gòu)造方法是 Python OOP的兩個(gè)基石。
舉個(gè)例子:
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》
一些語(yǔ)言使用私有屬性的方式是通過(guò) getter 與 setter 來(lái)實(shí)現(xiàn)內(nèi)部屬性的獲取與設(shè)置。python 提供 property
類(lèi)來(lái)達(dá)到同樣的目的。舉個(gè)例子:
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" # 調(diào)用setx print(c.x) # 調(diào)用getx del c.x # 調(diào)用delx
property
的存在讓對(duì)屬性的獲取、設(shè)置、刪除操作自動(dòng)內(nèi)置化。
更加優(yōu)雅的方式是使用@property
裝飾器。舉個(gè)例子:
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
現(xiàn)在需要處理一些音頻文件,除了一些描述性的屬性之外,還擁有播放、停止播放和信息存儲(chǔ)這三項(xiàng)行為:
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 = "姚禮忠、吳應(yīng)炬" current_file = AudioFile(filename=file_name,author=author_name) current_file.save(filename="info_list")
這個(gè)類(lèi)能夠正常工作。
注意觀察 save 方法,在保存文件信息之前,它做了一些格式化的工作。顯然后面的工作是“臨時(shí)添加”的且在別的文件類(lèi)型中可能也會(huì)用到。
隨著項(xiàng)目需求的變更或者其他原因,經(jīng)常會(huì)在方法內(nèi)部出現(xiàn)這種處理邏輯的擴(kuò)散現(xiàn)象,即完成一個(gè)功能,需要新的功能作為前提保障。
從最簡(jiǎn)單的代碼可重用性的角度來(lái)說(shuō),應(yīng)該將方法內(nèi)可重用的工作單獨(dú)提出來(lái):
至于公共功能放在哪個(gè)層次,請(qǐng)具體分析。
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')
但是,給改進(jìn)后的代碼在遇到功能變更時(shí),任然需要花費(fèi)大力氣在原有基礎(chǔ)上進(jìn)行修改。比如需要提供信息搜索功能,就可能出現(xiàn)這種代碼:
class AudioFile: ... def save(self, filename): ... def search(self, filename, key=None): ...
如果后期搜索條件發(fā)生變更、或者再新增功能,都會(huì)導(dǎo)致類(lèi)內(nèi)部出現(xiàn)功能擴(kuò)散,將進(jìn)一步增加原有代碼的復(fù)雜性,可讀性逐漸變差,尤其不利于維護(hù)與測(cè)試。
單一職責(zé)原則(Single-Responsibility Principle,SRP)由羅伯特·C.馬丁于《敏捷軟件開(kāi)發(fā):原則、模式和實(shí)踐》一書(shū)中提出。這里的職責(zé)是指類(lèi)發(fā)生變化的原因,單一職責(zé)原則規(guī)定一個(gè)類(lèi)應(yīng)該有且僅有一個(gè)引起它變化的原因,否則類(lèi)應(yīng)該被拆分。
該原則提出對(duì)象不應(yīng)該承擔(dān)太多職責(zé),如果一個(gè)對(duì)象承擔(dān)了太多的職責(zé),至少存在以下兩個(gè)缺點(diǎn):
一個(gè)職責(zé)的變化可能會(huì)削弱或者抑制這個(gè)類(lèi)實(shí)現(xiàn)其他職責(zé)的能力;
當(dāng)客戶端需要該對(duì)象的某一個(gè)職責(zé)時(shí),不得不將其他不需要的職責(zé)全都包含進(jìn)來(lái),從而造成冗余代碼或代碼的浪費(fèi)。
舉個(gè)例子:一個(gè)編譯和打印報(bào)告的模塊。想象這樣一個(gè)模塊可以出于兩個(gè)原因進(jìn)行更改。
首先,報(bào)告的內(nèi)容可能會(huì)發(fā)生變化。其次,報(bào)告的格式可能會(huì)發(fā)生變化。這兩件事因不同的原因而變化。單一職責(zé)原則說(shuō)問(wèn)題的這兩個(gè)方面實(shí)際上是兩個(gè)獨(dú)立的職責(zé),因此應(yīng)該在不同的類(lèi)或模塊中。
總之,單一職責(zé)原則認(rèn)為將在不同時(shí)間因不同原因而改變的兩件事情結(jié)合起來(lái)是一個(gè)糟糕的設(shè)計(jì)。
看一下修改后的代碼:
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 = "姚禮忠、吳應(yīng)炬" 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")
但這樣將拆分代碼,是不是合理的選擇?
從封裝的角度看來(lái)說(shuō),它的目的就是在對(duì)外提供接口的同時(shí),提高代碼的內(nèi)聚性和可重用性,但功能大而全的封裝更加的不安全。
單一職責(zé)原則通過(guò)拆分代碼實(shí)現(xiàn)更低的耦合性和更高的可重用性,但過(guò)度拆分會(huì)增加對(duì)象間交互的復(fù)雜性。
關(guān)于兩這的結(jié)合,有一些問(wèn)題需要事先注意:
需求的粒度是多大?
維護(hù)的成本有多高?
作為面向?qū)ο缶幊痰幕A(chǔ)概念與實(shí)踐原則,二者實(shí)際上是因果關(guān)系——如果一個(gè)類(lèi)是有凝聚力的,如果有一個(gè)更高層次的目的,如果它的職責(zé)符合它的名字,那么 SRP 就會(huì)自然而然地出現(xiàn)。SRP 只是代碼優(yōu)化后的實(shí)際的結(jié)果,它本身并不是一個(gè)目標(biāo)。
以上就是“python的單一職責(zé)原則怎么實(shí)現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。