前言:
單一職責(zé)原則提供了一個(gè)代碼開(kāi)發(fā)準(zhǔn)則擦俐,即要求在編寫(xiě)類,抽象槽驶,接口時(shí),要使其功能職責(zé)單一純粹鸳兽,將導(dǎo)致其變更的因素縮降至最低掂铐。如果一個(gè)類有多個(gè)職責(zé),就相當(dāng)于把多個(gè)職責(zé)耦合在一起了揍异。然而代碼設(shè)計(jì)的其中一個(gè)原則就是希望低耦合全陨,所以任何高耦合的設(shè)計(jì)都是應(yīng)該盡力避免的,這樣當(dāng)修改一個(gè)功能時(shí)衷掷,才可以顯著降低對(duì)其他功能的影響辱姨。
代碼示例:一個(gè)類負(fù)責(zé)多項(xiàng)職責(zé)的場(chǎng)景
在編碼過(guò)程中開(kāi)發(fā)了功能類C負(fù)責(zé)多個(gè)不同的職責(zé):職責(zé)P1,P2戚嗅,...Pn雨涛。由于需求變更需要更改職責(zé)P1的代碼 邏輯來(lái)滿足新的業(yè)務(wù)需求,任務(wù)完成后才發(fā)現(xiàn)更改職責(zé)P1導(dǎo)致了原本能夠正常運(yùn)行的職責(zé)P2發(fā)送故障懦胞。而修復(fù)職責(zé)P2又不得不更改職責(zé)P1的邏輯替久,導(dǎo)致這個(gè)現(xiàn)象的原因是功能類C的職責(zé)不夠單一,職責(zé)P1與職責(zé)P2耦合在一起導(dǎo)致的躏尉。
場(chǎng)景一:
類 Factory 可以用來(lái)生產(chǎn) X蚯根,Y 兩種產(chǎn)品,生產(chǎn)過(guò)程需要通過(guò) preprocess 方法對(duì)原材料預(yù)處理胀糜,示例代碼如下:
class Factory:
def preprocess(self, material):
return "+*+" + material + "+*+"
def processX(self, material):
return self.preprocess(material) + "加工成:產(chǎn)品X"
def processY(self, material):
return self.preprocess(material) + "加工成:產(chǎn)品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
現(xiàn)由于市場(chǎng)供求關(guān)系發(fā)生了變化颅拦,需要優(yōu)化產(chǎn)品X的生成方案,需將原材料的預(yù)處理方式從 "++" 變成 "##"教藻,示例代碼如下:
class Factory:
def preprocess(self, material):
return "#*#" + material + "#*#"
def processX(self, material):
return self.preprocess(material) + "加工成:產(chǎn)品X"
def processY(self, material):
return self.preprocess(material) + "加工成:產(chǎn)品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
從上面結(jié)果發(fā)現(xiàn)距帅,在使產(chǎn)品X可以達(dá)到預(yù)期生產(chǎn)要求的同時(shí),也導(dǎo)致了產(chǎn)品Y發(fā)生了變化括堤,但是產(chǎn)品Y的變化并不在預(yù)期當(dāng)中锥债,這就導(dǎo)致了程序運(yùn)行錯(cuò)誤甚至是崩潰。為了避免這種情況的發(fā)生,我們?cè)诰幋a過(guò)程中哮肚,應(yīng)當(dāng)注重類中各個(gè)職責(zé)間的解耦和增強(qiáng)功能類的內(nèi)聚性從而實(shí)現(xiàn)類職責(zé)的單一性登夫。切斷職責(zé) Process 與職責(zé) preprocess 之間的關(guān)聯(lián)(解耦),然后將職責(zé) preprocess 封裝到功能類 C1 中允趟,將職責(zé) process封裝到功能類 C2 中恼策,使職責(zé)preprocess與職責(zé)process分別由各自的獨(dú)立的類來(lái)完成(內(nèi)聚)。按照單一職責(zé)原則可以將上面的代碼按照以下方式進(jìn)行分解潮剪,示例代碼如下:
from abc import abstractmethod, ABCMeta
class AFactory(metaclass=ABCMeta):
@abstractmethod
def preprocess(self, material):
pass
class FactoryX(AFactory):
def preprocess(self, material):
return "+#+" + material + "+*+"
def process(self, material):
return self.preprocess(material) + "加工成: 產(chǎn)品X"
class Factory(AFactory):
def preprocess(self, material):
return "+*+" + material + "+#+"
def process(self, material):
return self.preprocess(material) + "加工成: 產(chǎn)品Y"
if __name__ == "__main__":
x = FactoryX()
print(x.process("原材料"))
y = FactoryY()
print(y.process("原材料"))
由代碼可見(jiàn)涣楷,優(yōu)化產(chǎn)品X的生產(chǎn)方案,不會(huì)影響產(chǎn)品Y抗碰。
類的“職責(zé)擴(kuò)散”
在編碼過(guò)程中經(jīng)常會(huì)出現(xiàn)“職責(zé)擴(kuò)散”的現(xiàn)象狮斗,表現(xiàn)為比如類C原本只有單個(gè)職責(zé) func1,但由于需求變更或其他原因弧蝇,職責(zé)func1 被細(xì)分為職責(zé)func11碳褒,職責(zé)func12 等等,進(jìn)而導(dǎo)致類C無(wú)法正常工作看疗;或由于軟件的迭代升級(jí)沙峻,類的某一職責(zé)會(huì)被分化為顆粒度更細(xì)的多個(gè)職責(zé),這種分化又分為橫向細(xì)分和縱向細(xì)分兩種形式两芳。
場(chǎng)景2
類Factory 負(fù)責(zé)將原材料多次處理后生產(chǎn)出產(chǎn)品X摔寨,示例代碼如下:
class Factory:
def preprocess(self, materal):
return "-*-" + materal + "-*-"
def process(self, materal):
return self.preprocess(materal) + "加工ing."
def package(self, materal):
return self.process(materal) + "打包ing."
def processX(self, materal):
return self.package(materal) + "產(chǎn)品X"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
橫向細(xì)分
由于產(chǎn)品線拓展,類 Factory 增加生產(chǎn)產(chǎn)品Y怖辆,產(chǎn)品Y與產(chǎn)品X除了包裝不同之外是复,其他都一樣,換湯不換藥竖螃。秉持代碼復(fù)用的原則佑笋,我們對(duì)工廠類 Factory 進(jìn)行擴(kuò)展,示例代碼如下:
class Factory:
def preprocess(self, materal):
return "-*-" + materal + "-*-"
def process(self, materal):
return self.preprocess(materal) + "加工ing'
def package(self, materal):
return self.process(materal) + "打包ing"
def processX(self, materal):
return self.package(materal) + "產(chǎn)品X"
def processY(sefl, materal):
return self.package(materal) + "產(chǎn)品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
縱向細(xì)分
由于產(chǎn)品線擴(kuò)展斑鼻,類Factory 除了生產(chǎn)產(chǎn)品X蒋纬,還生產(chǎn)X的半成品,不需要貼上產(chǎn)品X的LOGO坚弱。這種場(chǎng)景下我們直接調(diào)用 Factory 類中的 package 方法即可實(shí)現(xiàn)蜀备。示例代碼如下:
f = Factory()
w = f.package("原材料")
print(w)
這種之前的問(wèn)題又出現(xiàn)了,無(wú)論是橫向細(xì)分還是縱向細(xì)分荒叶,假設(shè)需將產(chǎn)品X 的原材料預(yù)處理方案做改變碾阁,那么同樣也會(huì)牽連到其他的生產(chǎn)過(guò)程。所以牽一發(fā)而動(dòng)全身的問(wèn)題依然存在些楣。單一職責(zé)要求我們?cè)诰帉?xiě)類脂凶,抽象類宪睹,接口時(shí)。要使其功能職責(zé)單一純粹蚕钦,這樣導(dǎo)致其變更的因素才能縮減到最少亭病。基于這一原則對(duì)類 Factory 我們重新調(diào)整一下實(shí)現(xiàn)方案嘶居。首先對(duì)類 Factory 中四個(gè)職責(zé)進(jìn)行抽象罪帖。示例代碼如下:
from abc import ABCMeta, abstractmethod
class ab_preprocess(metaclass=ABCMeta):
@abstractmethod
def preprocess(self, materal):
pass
class ab_process(metaclass=ABCMeta):
@abstractmethond
def process(self, materal):
pass
class ab_package(metaclass=ABCMeta):
@abstractmethod
def package(self, materal):
pass
class ab_product(metaclass=ABCMeta):
@abstractmethod
def product(self, materal):
pass
繼續(xù)創(chuàng)建四個(gè)職責(zé)類,分別實(shí)現(xiàn)這四個(gè)抽象類邮屁。
class PreProcess(ab_preprocess):
def preprocess(self, material):
return "-*-" + materail + ","
class Process(ab_process):
def __init__(self, preprocess):
self.preprocess = preprocess
def process(self, material):
return self.preprocess.preprocess(material) + "加工."
class Package(ab_package):
def __init__(self, process):
self.process = process
def package(self, material):
return self.process.process(material) + "打包"
class ProductA(ab_product):
def __init__(self, package):
self.package = package
def product(self, material):
return self.package.package(material) + "產(chǎn)品A"
創(chuàng)建產(chǎn)品A:
if __name__ == "__main__":
x = PruductA(Package(Process(PreProcess())))
print(x.product("原材料"))
若橫向擴(kuò)展一個(gè)新的產(chǎn)品線B整袁,可以增加一個(gè)新的產(chǎn)品類 ProductB
class ProductB(ab_product):
def __init__(self, package):
self.package = package
def product(sefl, material):
return self.package.package(metarial) + "產(chǎn)品B"
if __name__ == "__main__":
x = PruductA(Package(Process(PreProcess())))
print(x.product("原材料"))
y = PruductB(Package(Process(PreProcess())))
print(y.product("原材料"))
若需適應(yīng)市場(chǎng)需求,改變產(chǎn)品X 的原材料預(yù)處理方案從 "-*-"變?yōu)?-#-"佑吝,在代碼中可以新加入一個(gè)原材料預(yù)處理類 PreProcessA坐昙。示例代碼如下:
# 加入一個(gè)新的預(yù)處理接口
class PreProcessA(ab_preprocess):
def preprocess(self, material):
return "-#-" + material + ","
if __name__ == "__main__":
x = PruductA(Package(Process(PreProcess())))
print(x.product("原材料"))
y = PruductB(Package(Process(PreProcess())))
print(y.product("原材料"))
w = PruductB(Package(Process(PreProcessA())))
print(w.product("原材料"))
結(jié)合上述代碼輸出可知,改變產(chǎn)品A的原材料處理方式并沒(méi)有影響到產(chǎn)品Y的正常生產(chǎn)芋忿。同時(shí)若想生產(chǎn)產(chǎn)品X的半成品的話炸客,不需要更改生產(chǎn)代碼,只需直接調(diào)用類 Package 即可盗飒,依然不會(huì)對(duì)其他邏輯造成影響。
if __name__ == "__main__":
x = ProductA(Package(Process(PreProcess())))
print(x.product("原材料"))
x = Package(Process(PreProcess()))
print(x.package("原材料"))
寫(xiě)在最后
單一職責(zé)原則并不是單單的將類的功能進(jìn)行顆谅穑化拆分逆趣,并且拆分的越細(xì)越好。雖然這樣可以保證類功能職責(zé)的單一性嗜历,但是也會(huì)導(dǎo)致類的數(shù)量暴增宣渗,也會(huì)造成類的調(diào)用繁瑣,類間關(guān)系交織混亂梨州,后期維護(hù)困難等問(wèn)題痕囱。所以單一職責(zé)原則并不是要求類的功能拆分的越細(xì)越好。但是具體需要把職責(zé)細(xì)化到哪一步暴匠,取決于項(xiàng)目需求和業(yè)務(wù)的復(fù)雜度鞍恢,單一職責(zé)不是刻板的教條,編碼的主旨思想還是高內(nèi)聚每窖,低耦合帮掉,所以單一職責(zé)原則需要靈活的運(yùn)用。