单一职责原则
定义
单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。一个类或者模块只负责完成一个职责(或者功能)。
如何理解单一职责原则(SRP)?
一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的 类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护 性。
如何判断类的职责是否足够单一?
不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。例如人员信息里的地址相关属性,如果和其他属性一样都是为了简单展示,那就没必要拆分。如果有类似网购收货地址维护的场景,就需要拆分了。
实际上,一些侧面的判断指标更具有指导意义和可执行性,比如, 出现下面这些情况就有可能说明这类的设计不满足单一职责原则:
- 类中的代码行数、函数或者属性过多;
- 类依赖的其他类过多,或者依赖类的其他类过多;
- 私有方法过多;
- 比较难给类起一个合适的名字;
- 类中大量的方法都是集中操作类中的某几个属性,说明有拆分的可能性;
类的职责是否设计得越单一越好?
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚 性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此 来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。 对热门模块,设计充分点。对冷门模块,设计保守点。
AtomicInteger#getAndIncrement()是否符合单一职责
此方法的功能是将获取和增加原子化,职责是明确的,符合单一职责。 原则是死的,业务是活的。凡事要结合业务场景来考虑,切勿死扣定义。
示例
重试场景
在某些特定情况下,我们需要进行重试操作。一般就写个循环,但如果这样的场景比较多,就可以考虑抽取重试的逻辑,业务逻辑职责更单一。(可能存在过度设计的嫌疑,但这里还有个目的:抛砖引玉)
方案一、模板模式
AbstractSpinExecutor
代码语言:javascript复制public abstract class AbstractSpinExecutor {
public void spin(int count) {
IntStream.range(0, count).boxed().forEach(this::handle);
}
protected abstract void handle(int index);
}
handle(int index)
:模板模式,交给子类实现需要重试的方法
测试
代码语言:javascript复制@Test
public void test() {
new AbstractSpinExecutor() {
@Override
protected void handle(int index) {
System.out.println(StringUtil.format("repeat {} do something", index));
}
}.spin(5);
}
- 定义了一个匿名内部类
输出
代码语言:javascript复制repeat 0 do something
repeat 1 do something
repeat 2 do something
repeat 3 do something
repeat 4 do something
方案二、lambda表达式
SpinExecutor
代码语言:javascript复制public class SpinExecutor {
private Consumer<Integer> consumer;
public SpinExecutor execute(Consumer<Integer> consumer) {
this.consumer = consumer;
return this;
}
public void spin(int count) {
Assert.notNull(consumer, "请先设置需要重复执行的方法");
IntStream.range(0, count).boxed().forEach(consumer::accept);
}
}
测试
代码语言:javascript复制@Test
public void test1() {
new SpinExecutor().execute(index -> {
System.out.println(StringUtil.format("repeat {} do something", index));
}).spin(5);
}
数据拆分
使用mybatis进行批量插入的时候,数据量太大就需要分批次执行(mybatis plus就是这么做的)
数组拆分逻辑出现多次,而且与业务无关,可以抽取出来,使职责更明确。
ListUtil
代码语言:javascript复制@Slf4j
public class ListUtil {
public static final int DEFAULT_PARTITION_SIZE = 100;
public static <T, R> List<R> partitionToDo(List allData, Function<List<T>, List<R>> function) {
return partitionToDo(DEFAULT_PARTITION_SIZE, allData, function);
}
public static <T, R> List<R> partitionToDo(int partitionSize, List allData, Function<List<T>, List<R>> function) {
if (CollUtil.isEmpty(allData)) {
log.warn("no data to partition");
return Collections.emptyList();
}
List<List<T>> partitionList = Lists.partition(allData, partitionSize);
return partitionList.stream().map(function).flatMap(Collection::stream).collect(Collectors.toList());
}
}
flatMap(Collection::stream)
:将集合转换成流,并合并起来,类似list.addAll()
测试
代码语言:javascript复制@Test
public void test() {
List<Boolean> result = ListUtil.partitionToDo(4,
Stream.generate(Math::random).limit(13).collect(Collectors.toList()),
(data) -> {
return data.stream().map(x -> (Double) x > 0.5).collect(Collectors.toList());
});
System.out.println(result);
}
输出
代码语言:javascript复制[true, false, false, false, false, false, true, false, true, false, true, false, false]