前言:
单一职责原则提供了一个代码开发准则,即要求在编写类,抽象,接口时,要使其功能职责单一纯粹,将导致其变更的因素缩降至最低。如果一个类有多个职责,就相当于把多个职责耦合在一起了。然而代码设计的其中一个原则就是希望低耦合,所以任何高耦合的设计都是应该尽力避免的,这样当修改一个功能时,才可以显著降低对其他功能的影响。
代码示例:一个类负责多项职责的场景
在编码过程中开发了功能类C负责多个不同的职责:职责P1,P2,...Pn。由于需求变更需要更改职责P1的代码 逻辑来满足新的业务需求,任务完成后才发现更改职责P1导致了原本能够正常运行的职责P2发送故障。而修复职责P2又不得不更改职责P1的逻辑,导致这个现象的原因是功能类C的职责不够单一,职责P1与职责P2耦合在一起导致的。
场景一:
类 Factory 可以用来生产 X,Y 两种产品,生产过程需要通过 preprocess 方法对原材料预处理,示例代码如下:
class Factory:
def preprocess(self, material):
return "+*+" + material + "+*+"
def processX(self, material):
return self.preprocess(material) + "加工成:产品X"
def processY(self, material):
return self.preprocess(material) + "加工成:产品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
现由于市场供求关系发生了变化,需要优化产品X的生成方案,需将原材料的预处理方式从 "++" 变成 "##",示例代码如下:
class Factory:
def preprocess(self, material):
return "#*#" + material + "#*#"
def processX(self, material):
return self.preprocess(material) + "加工成:产品X"
def processY(self, material):
return self.preprocess(material) + "加工成:产品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
从上面结果发现,在使产品X可以达到预期生产要求的同时,也导致了产品Y发生了变化,但是产品Y的变化并不在预期当中,这就导致了程序运行错误甚至是崩溃。为了避免这种情况的发生,我们在编码过程中,应当注重类中各个职责间的解耦和增强功能类的内聚性从而实现类职责的单一性。切断职责 Process 与职责 preprocess 之间的关联(解耦),然后将职责 preprocess 封装到功能类 C1 中,将职责 process封装到功能类 C2 中,使职责preprocess与职责process分别由各自的独立的类来完成(内聚)。按照单一职责原则可以将上面的代码按照以下方式进行分解,示例代码如下:
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) + "加工成: 产品X"
class Factory(AFactory):
def preprocess(self, material):
return "+*+" + material + "+#+"
def process(self, material):
return self.preprocess(material) + "加工成: 产品Y"
if __name__ == "__main__":
x = FactoryX()
print(x.process("原材料"))
y = FactoryY()
print(y.process("原材料"))
由代码可见,优化产品X的生产方案,不会影响产品Y。
类的“职责扩散”
在编码过程中经常会出现“职责扩散”的现象,表现为比如类C原本只有单个职责 func1,但由于需求变更或其他原因,职责func1 被细分为职责func11,职责func12 等等,进而导致类C无法正常工作;或由于软件的迭代升级,类的某一职责会被分化为颗粒度更细的多个职责,这种分化又分为横向细分和纵向细分两种形式。
场景2
类Factory 负责将原材料多次处理后生产出产品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) + "产品X"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
横向细分
由于产品线拓展,类 Factory 增加生产产品Y,产品Y与产品X除了包装不同之外,其他都一样,换汤不换药。秉持代码复用的原则,我们对工厂类 Factory 进行扩展,示例代码如下:
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) + "产品X"
def processY(sefl, materal):
return self.package(materal) + "产品Y"
if __name__ == "__main__":
f = Factory()
print(f.processX("原材料"))
print(f.processY("原材料"))
纵向细分
由于产品线扩展,类Factory 除了生产产品X,还生产X的半成品,不需要贴上产品X的LOGO。这种场景下我们直接调用 Factory 类中的 package 方法即可实现。示例代码如下:
f = Factory()
w = f.package("原材料")
print(w)
这种之前的问题又出现了,无论是横向细分还是纵向细分,假设需将产品X 的原材料预处理方案做改变,那么同样也会牵连到其他的生产过程。所以牵一发而动全身的问题依然存在。单一职责要求我们在编写类,抽象类,接口时。要使其功能职责单一纯粹,这样导致其变更的因素才能缩减到最少。基于这一原则对类 Factory 我们重新调整一下实现方案。首先对类 Factory 中四个职责进行抽象。示例代码如下:
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
继续创建四个职责类,分别实现这四个抽象类。
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) + "产品A"
创建产品A:
if __name__ == "__main__":
x = PruductA(Package(Process(PreProcess())))
print(x.product("原材料"))
若横向扩展一个新的产品线B,可以增加一个新的产品类 ProductB
class ProductB(ab_product):
def __init__(self, package):
self.package = package
def product(sefl, material):
return self.package.package(metarial) + "产品B"
if __name__ == "__main__":
x = PruductA(Package(Process(PreProcess())))
print(x.product("原材料"))
y = PruductB(Package(Process(PreProcess())))
print(y.product("原材料"))
若需适应市场需求,改变产品X 的原材料预处理方案从 "-*-"变为"-#-",在代码中可以新加入一个原材料预处理类 PreProcessA。示例代码如下:
# 加入一个新的预处理接口
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("原材料"))
结合上述代码输出可知,改变产品A的原材料处理方式并没有影响到产品Y的正常生产。同时若想生产产品X的半成品的话,不需要更改生产代码,只需直接调用类 Package 即可,依然不会对其他逻辑造成影响。
if __name__ == "__main__":
x = ProductA(Package(Process(PreProcess())))
print(x.product("原材料"))
x = Package(Process(PreProcess()))
print(x.package("原材料"))
写在最后
单一职责原则并不是单单的将类的功能进行颗粒化拆分,并且拆分的越细越好。虽然这样可以保证类功能职责的单一性,但是也会导致类的数量暴增,也会造成类的调用繁琐,类间关系交织混乱,后期维护困难等问题。所以单一职责原则并不是要求类的功能拆分的越细越好。但是具体需要把职责细化到哪一步,取决于项目需求和业务的复杂度,单一职责不是刻板的教条,编码的主旨思想还是高内聚,低耦合,所以单一职责原则需要灵活的运用。