实践GoF的23种设计模式:SOLID原则( 三 )


除了重复的代码编译,违反SRP还会带来以下2个常见的问题:
1、代码冲突 。程序员A修改了模块的A功能,而程序员B在不知情的情况下也在修改该模块的B功能(因为A功能和B功能面向不同的用户,完全可能由2位不同的程序员来维护),当他们同时提交修改时,代码冲突就会发生(修改了同一个源文件) 。
2、A功能的修改影响了B功能 。如果A功能和B功能都使用了模块里的一个公共函数C,现在A功能有新的需求需要修改函数C,那么如果修改人没有考虑到B功能,那么B功能的原有逻辑就会受到影响 。
由此可见,违反SRP会导致软件的可维护性变得极差 。但是,我们也不能盲目地进行模块拆分,这样会导致代码过于碎片化,同样也会提升软件的复杂性 。比如,在前面的例子中,我们就没有必要再对服务管理模块进行拆分为服务注册模块、服务更新模块和服务去注册模块,一是因为它们面向的用户是一致的;二是在可预见的未来它们要么同时变化,要么都不变 。
因此,我们可以得出这样的结论:
如果一个模块面向的都是同一类用户(变化原因一致),那么就没必要进行拆分 。如果缺乏用户归类的判断,那么最好的拆分时机是变化发生时 。
SRP是聚合和拆分的一个平衡,太过聚合会导致牵一发动全身,拆分过细又会提升复杂性 。要从用户的视角来把握拆分的度,把面向不同用户的功能拆分开 。如果实在无法判断/预测,那就等变化发生时再拆分,避免过度的设计 。
OCP:开闭原则
开闭原则(The Open-Close ,OCP)中,“开”指的是对扩展开放,“闭”指的是对修改封闭,它的完整解释为:
Abe open forbutfor .
通俗地讲就是,一个软件系统应该具备良好的可扩展性,新增功能应当通过扩展的方式实现,而不是在已有的代码基础上修改 。
然而,从字面意思上看,OCP貌似又是自相矛盾的:想要给一个模块新增功能,但是又不能修改它 。
*如何才能打破这个困境呢?*关键是抽象!优秀的软件系统总是建立在良好的抽象的基础上,抽象化可以降低软件系统的复杂性 。
*那么什么是抽象呢?*抽象不仅存在于软件领域,在我们的生活中也随处可见 。下面以《语言学的邀请》中的一个例子来解释抽象的含义:
假设某农庄有一头叫“阿花”的母牛,那么:
1、当把它称为“阿花”时,我们看到的是它独一无二的一些特征:身上有很多斑点花纹、额头上还有一个闪电形状的伤疤 。
2、当把它称为母牛时,我们忽略了它的独有特征,看到的是它与母牛“阿黑”,母牛“阿黄”的共同点:是一头牛、雌性的 。
3、当把它称为家畜时,我们又忽略了它作为母牛的特征,而是看到了它和猪、鸡、羊一样的特点:是一个动物,在农庄里圈养 。
4、当把它称为农庄财产时,我们只关注了它和农庄上其他可售对象的共同点:可以卖钱、转让 。
从“阿花”,到母牛,到家畜,再到农庄财产,这就是一个不断抽象化的过程 。
从上述例子中,我们可以得出这样的结论:
抽象就是不断忽略细节,找到事物间共同点的过程 。抽象是分层的,抽象层次越高,细节也就越少 。
再回到软件领域,我们也可以把上述的例子类比到数据库上,数据库的抽象层次从低至高可以是这样的:MySQL 8.0版本 -> MySQL -> 关系型数据库 -> 数据库 。现在假设有一个需求,需要业务模块将业务数据保存到数据库上,那么就有以下几种设计方案:
我们可以发现,上述方案的演进过程,就是我们不断对业务依赖的数据库模块进行抽象的过程,最终设计出稳定的、服务OCP的软件 。