单一职责原则

前言:
单一职责原则提供了一个代码开发准则,即要求在编写类,抽象,接口时,要使其功能职责单一纯粹,将导致其变更的因素缩降至最低。如果一个类有多个职责,就相当于把多个职责耦合在一起了。然而代码设计的其中一个原则就是希望低耦合,所以任何高耦合的设计都是应该尽力避免的,这样当修改一个功能时,才可以显著降低对其他功能的影响。

代码示例:一个类负责多项职责的场景

在编码过程中开发了功能类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("原材料"))

写在最后
单一职责原则并不是单单的将类的功能进行颗粒化拆分,并且拆分的越细越好。虽然这样可以保证类功能职责的单一性,但是也会导致类的数量暴增,也会造成类的调用繁琐,类间关系交织混乱,后期维护困难等问题。所以单一职责原则并不是要求类的功能拆分的越细越好。但是具体需要把职责细化到哪一步,取决于项目需求和业务的复杂度,单一职责不是刻板的教条,编码的主旨思想还是高内聚,低耦合,所以单一职责原则需要灵活的运用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容