单一职责原则
SRP(The Single Responsibility Principle):一个类应该只有一个发生变化的原因。这里的变化指职责的变化。
SRP 很好理解,它的要求是 让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。听起来很简单,即一个类指做一种事情。这里是一种并不是一件事情。
若果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。
打个比方:生产线上的员工在作业时,他们每个人的工作职责都非常明确,只负责某一个环节,只被安排做某一件事情。这个和 SRP 感觉很像。
如何定义职责
SPR 中,我们把职责定义为变化的原因。如果我们能想到对于一个的动机去改变一个类,那么这个类就具有对于一个的职责。不过,通常情况下,我们会更习惯于用组的形式去考虑职责。
若下面的所示的关系型 Database 处理接口:
public interface IRdbmsStorage { IDbConnection Connection(string connectionString); IEnumrable<T> Query<T>(string sqlQuery, object param); T FirstOrDefault<T>(string sqlQuery, object param); int Count(string sqlQuery, object param); void Execute(string sqlCommand, object param); void ExecuteScalar(string sqlCommand, object param); }
这个接口中我们看到有数据库的连接、数据的查询 和 数据的处理 三个职责。
这三个职责我们需要分开吗?这依赖于应用程序的变化方式。若果说应用程序的变化会影响到连接函数的签名(可能是连接字符串,也可能是 config 中的连接字符串名称),那么这个设计可能存在不合理。也可能代码中某些地方只需要开放查询功能。
另一方面,若果应用程序的变化方式总是会导致这三个职责同时变化,那么就不需要分离他们。实际上,分离他们会导致不必要的复杂性。
分离耦合职责
如何分离耦合的职责呢?我们可以分离它们的接口来进行解耦。将多功能的大接口分解成多个单功能的小接口。
public interface IRdbmsStorageQuery { IEnumrable<T> Query<T>(string sqlQuery, object param); T FirstOrDefault<T>(string sqlQuery, object param); int Count(string sqlQuery, object param); } public interface IRdbmsStorageCommand { void Execute(string sqlCommand, object param); void ExecuteScalar(string sqlCommand, object param); } public interface IRdbmsStorage : IRdbmsStorageQuery, IRdbmsStorageCommand { IDbConnection Connection(string connectionString); }
上述代码中我们换分成了 IRdbmsStorageQuery 查询接口 和 IRdbmsStorageCommand 命令 接口(有点 CQS 的味道),然后通过 IRdbmsStorage 接口组合起来。在有些地方我们可能只需要查询功能,有些地方可能用到执行功能,而有些地方可能两者都需要用到。
总结
SPR 原则可以说是面向对象原则中最简单的原则之一,但是也是最难把握的原则之一。软件设计真正要做的许多工作,就是要发现职责并把那些职责相互分离开来。这些职责什么时候该划分职责,划分的颗粒度又有多大,都需要我们去体会。