2.5 项目管理
开发一个项目管理工具,可能设计如下用户类:
类设计得看着很合理,有用户信息管理、项目管理等。
现在新需求规定每个用户能够设置电话号码,于是你新增方法:
又来新需求:查看一个用户加入了多少项目:
就这样,几乎每个和用户沾边的需求,你都改了user类,导致:
User类不断膨胀
内部实现越来越复杂
这个类变动的频繁程度显然不理想,在于它诱导变动的需求太多:
为什么要增加电话号码?
用户管理的需求。用户管理的需求还会有很多,比如,用户实名认证、用户组织归属等
为什么要查看用户加入多少项目?
项目管理的需求。项目管理的需求还会有很多,比如,团队管理、项目权限等。
这是两种完全不同的需求,但你都改同一个类,所以,User类无法稳定。
最好的方案是拆分不同需求引起的变动。
对于用户管理、项目管理两种不同需求,完全可以把User拆成两个类:
用户管理类需求放到User
项目管理类的需求放到Member
这样,用户管理的需求只需调整User类,项目管理的需求只需调整Member类,二者各自变动的理由就少了。
变化的来源
上面的做法类似分离关注点。
要更好地理解单一职责原则,关键就是分离不同关注点。该案例分离的是不同的业务关注点。所以,理解单一职责原则奥义在于理解分离关注点。
分离关注点,发现的关注点越多越好,粒度越小越好。你能看到的关注点越多,就能构建出更多的类,但每个类的规模相应越小,与之相关需求变动也越少,能稳定的几率就越大。
代码库里稳定的类越多越好,这是我们努力的方向。
如果将这种思路推演到极致,那一个类就应该只有一个方法,这样,它受到影响最小。
的确如此,但实际项目,一个类通常都不只一个方法,要求所有人都做到极致,不现实。
那应该把哪些内容组织到一起?
这就需要考虑单一职责原则定义的升级版,即第二个定义:一个模块应该对一类且仅对一类行为者负责。
若第一个定义将变化纳入考量,则升级版定义则将变化的来源纳入考量。
需求为什么会改变?
因为有各种人提需求,不同人提的需求关注点不同。
关心用户管理和关心项目管理的可能是两种不同角色的人。两件不同的事,到了代码,却混在一起,这显然不合理。
所以,分开才是一个好选择:
用户管理的人,我和他们聊User
项目管理的人,我们来讨论Member
康威定律:一个组织设计出的系统,其结构受限于其组织的沟通结构。
Robert Martin说,单一职责原则是基于康威定律的一个推论:一个软件系统的最佳结构高度依赖于使用这个软件的组织的内部结构。
若我们的软件结构不能够与组织结构对应,就会带来一系列麻烦。
实际上,当我们更新了对于单一职责原则的理解,你会发现,它的应用范围不仅可放在类这个级别,也可放到更大级别。
某交易平台有个关键模型:手续费率,交易一次按xx比例收佣金。平台可以利用手续费率做不同的活动,比如,给一些人比较低的手续费率,鼓励他们来交易,不同的手续费率意味着对不同交易行为的鼓励。
对运营人员
手续费率是一个可以玩出花的东西
对交易系统而言
稳定高效是重点。显然,经常修改的手续费率和稳定的系统之间存在矛盾。
分析发现,这是两类不同行为者。所以,设计时,把手续费率设置放到运营子系统,而交易子系统只负责读取手续费率:
当运营子系统修改了手续费率,会把最新结果更新到交易子系统
至于各种手续费率设置的花样,交易子系统根本无需关心
单一职责原则还能指导我们在不同的子系统之间进行职责分配。所以,单一职责原则这个看起来最简单的原则,实际上也蕴含着很多值得挖掘的内容。
要想理解好单一职责原则:
需要理解封装,知道要把什么样的内容放到一起
理解分离关注点,知道要把不同的内容拆分开来
理解变化的来源,知道把不同行为者负责的代码放到不同的地方。
你就可以更好地理解函数要小的含义了,每个函数承担的职责要单一,这样,它才能稳定。
4 单一且快乐
对于:
- 接口,设计时一定要单一
- 但对于实现类就需要多方面考虑
- 生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的。
单一职责原则很难体现在项目
国内的技术人员地位和话语权都是最低的,在项目中需要考虑环境、工作量、人员的技术水平、硬件的资源情况等,最终妥协经常违背单一职责原则。
单一职责适用于接口、类,同时也适用于方法。一个方法尽可能做一件事情,比如一个方法修改用户密码,不要把这个方法放到“修改用户信息”方法中,这个方法的颗粒度很粗.
- 一个方法承担多个职责
- 在IUserManager中定义了一个方法changeUser,根据传递的类型不同,把可变长度参数changeOptions修改到userBO这个对象上,并调用持久层的方法保存到数据库中。
这种代码看到,直接要求其重写即可:方法职责不清晰,不单一,不要让别人猜测这个方法可能是用来处理什么逻辑的。
比较好的设计如下:
一个方法承担一个职责
若要修改用户名称,就调用changeUserName方法
要修改家庭地址,就调用changeHomeAddress方法
要修改单位电话,就调用changeOfficeTel方法
每个方法的职责非常清晰明确,不仅开发简单,而且日后的维护也非常容易。
5 最佳实践
类的单一职责确实受非常多因素的制约,纯理论地来讲,这个原则很好,但现实有很多难处,你必须考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。
对于单一职责原则,推荐:
- 接口一定要做到单一职责
- 类的设计尽量做到只有一个原因引起变化
参考
- 《设计模式之蝉》