一、概念
单一职责:Single Responsibility Principle,简称SRP。字面义很简单,但是做起来有的时候真的蛮难的。就是小到一个接口只干一件事,大到一个类、一个模块只负责完成一个功能。
比如:UserService里面既包含用户账号密码登录注册等操作又包含用户积分等操作。这种情况就很明显的违背了单一职责原则,需要将其拆分为UserService和ScoreService。
二、如何确定是否单一职责
1、描述
上面那么明显的demo就不多说了,但是一般都会出现摸棱两可的情况,比如下面的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; // 详细地址
// ...省略其他属性和方法...
}
这段代码符合单一职责吗?有的人说符合,有的人说不符合,应该把省市区详细地址单独抽出来放到一个类里。
哪种人说的对?我觉得这需要区分业务,如果你的产品业务的用户信息字段只是负责显示,没其他逻辑。那这一个UserInfo.java就足够了,如果你不光有用户系统,还有积分兑换系统,兑换的实物需要发到你的省市区详细地址,这种情况我认为UserInfo.java
结合UserAddress.java
两个类比较合适。因为地址信息不光显示用,物流发货也需要用。
所以我觉得,这就是取决于产品业务的,当然特别明显的违背了单一职责原则的代码一眼就看得出,只有这种摸棱两可的代码需要取决于业务。但我觉得没必要过度设计,如果你系统有这个需求或者近期可能会出现这个需求,那就拆分。如果近期不会这么搞,我觉得先弄一个类,到时候重构就完了,不要过度设计。
2、总结
总结评判是否符合单一职责原则的几个技巧
- 类的代码行数、函数、属性过多的时候。多少算多?个人建议类行数不过300行、函数不过50行、函数和属性的个人不多于10个。
- 类依赖的其他类过多,不符合高内聚低耦合的思想,需要拆分。比如一个service层又依赖了其他七八个service层才能完成某个业务。
- 私有方法过多,可以考虑放到其他类里弄成public公用。提高复用性。
- 根据业务场景来适当拆分,这句话好像是废话…这里想说的是那个UserInfo和UserAddress的取舍。
三、切勿过度设计
千万不要为了单一而过度单一。比如Serialization实现了序列化和反序列化的功能。
public class Serialization {
private static final String IDENTIFIER_STRING = "CTW";
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);
}
}
上面的设计很合理,也很完美,完全符合上面【如何确定是否单一职责】的描述。但是如下代码就过度设计了,他将序列化和反序列化拆分成两个Java类。
/**
* 序列化
*/
public class Serializer {
private static final String IDENTIFIER_STRING = "CTW";
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 = "CTW";
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);
}
}
乍一看好像是更加符合单一职责了,其实是典型的过度设计。首先完全没必要,照这个思路的话,那所有的类里面都只会包含一个方法了,其次他会引入新的问题,若我们修改了协议,数据标识从CTW变成了HELLO,或者序列化的方式从JSON换成了XML等,那Serializer和Deserializer两个类都需要做相应的修改。显然没有第一种方式的内聚性高,主要是我们如果只修改了Serializer的CTW->HELLO,忘记了反序列化的类的修改,这时候会导致序列化、反序列化不一致,报错。(我知道你能将CTW放到公用常量类里去解决,我只想突出问题,说明这是过度设计)。