单一职责原则

2022-09-21 10:07:52 浏览数 (1)

单一职责原则

定义

单一职责原则的英文是 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]

0 人点赞