理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
如何理解单一职责原则(SRP)
- 单一职责原则:Single responsibility Principle, SRP. A class or module should have a single responsibility. 一个类或模块只负责完成一个职责(或者功能)。关于模块和类有两种理解:
- 模块可以看做更抽象的类,类也可看成一个模块。
- 模块可以看做更粗粒度的代码块,包含多个类。
- 解读就是不要设计大而全的类,要设计粒度小,功能单一的类。比如一个类既包含订单的操作,又包含用户的操作,他们(用户和订单)是不同的领域模型,我们应该将其拆分。
如何判断类的职责足够单一
举个例子,比如我们要设计一个社交产品,其中UserInfo用来记录用户的信息,以下这个类是否满足单一职责原则:
public class UserInfo { private long userId; private String username; private String email; private String telephone; private long createTime; private long lastLoginTime; private String avatarUrl; private String provinceOfAddress; // 省 private String cityOfAddress; // 市 private String regionOfAddress; // 区 private String detailedAddress; // 详细地址 // ...省略其他属性和方法... }
反正我看着属性有点多,下面的省市区详细地址可以拆成另一个类。
作者提供的两个观点:1是和我的一样,地址信息占比较高,可以继续拆分成独立的UserAddress类,UserInfo只保留地址以外的信息。2. 是所有的信息都是用户相关的,所以满足SRP。
- 结合实际的应用业务场景。如何地址信息只是单纯的用来展示的,和其他的属性一样,那么就可以保留在UserInfo中。但是如果业务在拓展,有了电商模块,用户的地址信息还会用在电商物流中,那么我们最好将其分离出来,独立成物流信息。
- 如果业务继续发展,这个公司开发出来其他的一些应用,公司希望支持统一账号系统(一个账号可以登陆旗下所有app),那么我们还需要对UserInfo继续拆分,将身份认证的信息例如email, phone抽取成独立的的类。
- 不同的应用场景、不同阶段的需求背景下,对一个类的单一职责判定是不同的。
- 在实际开发中,我们可以先写一个粗粒度的类,满足业务需求。随着业务发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候我们那就可以将这个类拆分成几个更细粒度的类。这就是所谓的持续重构。
- 为什么做拆分
- 类中的代码行数、函数或属性过多。影响代码的可读性可维护性。
- 类依赖的其他类过多,或者依赖这个类的其他类过多,不符合高内聚、低耦合的设计思想。
- 所有方法过多的,我们可以考虑是否可以将私有方法独立到新类中设置为公有方法,以供更多类使用提高代码复用。
- 比较难起名字。这时候说明类的职责不是十分清晰。
- 大量的方法都集中操作某几个属性,比如在UserInfo中一半的方法在操作address信息,那么就可以考虑将address相关的信息和方法拆分出来。
- 没有很具体的方法论和很定量说多少属性算多。如果要找一个标准,可以通过一个类是否已经让你感觉十分复杂和过多功能了来判断。
类的职责是否越单一越好?
看个例子:
/** * Protocol format: identifier-string;{gson string} * For example: UEUEUE;{"a":"A","b":"B"} */ public class Serialization { private static final String IDENTIFIER_STRING = "UEUEUE;"; private Gson gson; public Serialization() { this.gson = new Gson(); } public String serialize(Map<String, String> object) { StringBuilder textBuilder = new StringBuilder(); textBuilder.append(IDENTIFIER_STRING); textBuilder.append(gson.toJson(object)); return textBuilder.toString(); } public Map<String, String> deserialize(String text) { if (!text.startsWith(IDENTIFIER_STRING)) { return Collections.emptyMap(); } String gsonStr = text.substring(IDENTIFIER_STRING.length()); return gson.fromJson(gsonStr, Map.class); } }
如果我们想拆分这个类,让职责更单一,拆分出Serializer和Deserializer
public class Serializer { private static final String IDENTIFIER_STRING = "UEUEUE;"; private Gson gson; public Serializer() { this.gson = new Gson(); } public String serialize(Map<String, String> object) { StringBuilder textBuilder = new StringBuilder(); textBuilder.append(IDENTIFIER_STRING); textBuilder.append(gson.toJson(object)); return textBuilder.toString(); } } public class Deserializer { private static final String IDENTIFIER_STRING = "UEUEUE;"; private Gson gson; public Deserializer() { this.gson = new Gson(); } public Map<String, String> deserialize(String text) { if (!text.startsWith(IDENTIFIER_STRING)) { return Collections.emptyMap(); } String gsonStr = text.substring(IDENTIFIER_STRING.length()); return gson.fromJson(gsonStr, Map.class); } }
拆分后功能更单一了,但是我们如果修改了协议的格式,数据标识从“UEUEUE”改为“DFDFDF”,或者序列化方式从 JSON 改为了 XML,那 Serializer 类和 Deserializer 类都需要做相应的修改,代码的内聚性显然没有原来 Serialization 高了
或者我们只改了desirializer。忘记了serializer,那么就会导致序列化反序列化不匹配。这其实是可维护性变差了。
总体还是要从代码的可读性,可维护性和可扩展性去考虑。