首页 > 解决方案 > 如何使用单一责任原则重构我的服务?

问题描述

我阅读了“清洁代码”一书((c) Robert C. Martin)并尝试使用SRP(单一责任原则)。我对此有一些疑问。我的应用程序中有一些服务,但我不知道如何重构它以匹配正确的方法。例如,我有服务:

public interface SendRequestToThirdPartySystemService  {
    void sendRequest();
}

如果你看类名,它会做什么?- 向第三方系统发送请求。但我有这个实现:

@Slf4j
@Service
public class SendRequestToThirdPartySystemServiceImpl implements SendRequestToThirdPartySystemService {

    @Value("${topic.name}")
    private String topicName;

    private final EventBus eventBus;
    private final ThirdPartyClient thirdPartyClient;
    private final CryptoService cryptoService;
    private final Marshaller marshaller;

    public SendRequestToThirdPartySystemServiceImpl(EventBus eventBus, ThirdPartyClient thirdPartyClient, CryptoService cryptoService, Marshaller marshaller) {
        this.eventBus = eventBus;
        this.thirdPartyClient = thirdPartyClient;
        this.cryptoService = cryptoService;
        this.marshaller = marshaller;
    }

    @Override
    public void sendRequest() {
        try {
            ThirdPartyRequest thirdPartyRequest = createThirdPartyRequest();

            Signature signature = signRequest(thirdPartyRequest);
            thirdPartyRequest.setSignature(signature);

            ThirdPartyResponse response = thirdPartyClient.getResponse(thirdPartyRequest);

            byte[] serialize = SerializationUtils.serialize(response);

            eventBus.sendToQueue(topicName, serialize);

        } catch (Exception e) {
            log.error("Send request was filed with exception: {}", e.getMessage());
        }
    }

private ThirdPartyRequest createThirdPartyRequest() {
    ...
    return thirdPartyRequest;
}

private Signature signRequest(ThirdPartyRequest thirdPartyRequest) {
    byte[] elementForSignBytes = marshaller.marshal(thirdPartyRequest);
    Element element = cryptoService.signElement(elementForSignBytes);
    Signature signature  = new Signature(element);  
    return signature;
}

它实际上是做什么的?-创建一个请求 -> 签署这个请求 -> 发送这个请求 -> 将响应发送到队列

此服务注入 4 个其他服务:eventBusthirdPartyClient和。并在sendRequest方法中调用每个此服务。如果我想为这个服务创建一个单元测试,我需要 mock 4 个服务。我觉得太多了。cryptoSevicemarshaller

有人可以指出如何更改此服务吗?

更改班级名称并保持原样?分成几个班?还有什么?

标签: javaspringooparchitecturesingle-responsibility-principle

解决方案


SRP 是一个棘手的问题。

让我们问两个问题:

  • 什么是责任?

  • 有哪些不同类型的责任?

关于职责的一件重要的事情是它们有一个范围,您可以在不同的粒度级别中定义它们。并且本质上是分层的。

您的应用程序中的所有内容都可能有责任。

让我们从模块开始。每个模块都有责任可以遵守 SRP。

那么这个Module可以由Layers组成。每一层都有责任并且可以遵守SRP

一层由不同的对象功能等组成。每个对象和/或功能都有责任并可以遵守 SRP。

每个对象都有方法。每个方法都可以遵守 SRP。对象可以包含其他对象等等。

对象中的每个函数方法都由语句组成,并且可以分解为更多的函数/方法。每个语句也可以有责任。

让我们举个例子。假设我们有一个计费模块。如果这个模块是在一个单独的大类中实现的,这个模块是否遵守 SRP?

  • 从系统的角度来看,该模块确实遵守了 SRP。它是一团糟的事实并不影响这个事实。
  • 从模块的角度来看,代表这个模块的类不遵守 SRP,因为它会做很多其他的事情,比如与 DB 通信、发送电子邮件、执行业务逻辑等。

让我们来看看不同类型的职责。

  • 什么时候应该做

  • 圆顶应该如何

让我们举个例子。

public class UserService_v1 {

    public class SomeOperation(Guid userID) {
        var user = getUserByID(userID);
        // do something with the user
    }

    public User GetUserByID(Guid userID) {
        var query = "SELECT * FROM USERS WHERE ID = {userID}";
        var dbResult = db.ExecuteQuery(query);
        return CreateUserFromDBResult(dbResult);
    }

    public User CreateUserFromDBResult(DbResult result) {
        // parse and return User
    }
}

public class UserService_v2 {

    public void SomeOperation(Guid userID) {
        var user = UserRepository.getByID(userID);
        // do something with the user
    }
}

让我们看一下这两个实现。

UserService_v1UserService_v2做完全相同的事情,但方式不同。从系统的角度来看,这些服务遵循 SRP,因为它们包含相关的操作Users

现在让我们来看看他们为完成工作实际做了什么。

UserService_v1做这些事情:

  1. 构建 SQL 查询字符串。
  2. 调用db执行查询
  3. 获取特定的并从中DbResult创建一个。User
  4. 是否对User

UserService_v2做这些事情: 1.User通过 ID 从存储库请求 2. 对User

UserService_v1包含:

  • 如何 构建特定查询
  • 具体如何映射到用户DbResult
  • 需要调用这个查询时(在这种情况下的请求操作中)

UserService_v1包含:

  • 何时 应从数据库中检索aUser

UserRepository包含:

  • 如何 构建特定查询
  • 具体如何映射到DbResultUser

我们在这里所做的是将HowService的责任从Repository. 这样每个班级都有一个改变的理由。如果如何改变,我们改变Repository. 如果when改变,我们改变Service.

通过这种方式,我们通过划分职责来创建相互协作以完成特定工作的对象。棘手的部分是:我们划分了哪些责任

如果我们有一个UserService并且OrderService我们不划分何时以及如何在这里。我们划分什么,以便我们可以在我们的系统中为每个实体提供一项服务。

那里的服务需要其他对象来完成它们的工作是很自然的。我们当然可以将什么何时如何的所有职责添加到单个对象中,但这只会导致混乱、不可读且难以更改。

在这方面,SRP 通过让更多更小的部分相互协作使用来帮助我们实现更简洁的代码。

我们来看看你的具体情况。

如果您可以通过将创建和签名的责任转移ClientRequestThirdPartyClientSendRequestToThirdPartySystemService只会告诉何时应该发送此请求。这将从您Marshaller的.CryptoServiceSendRequestToThirdPartySystemService

此外SerializationUtils,您可能会重命名为Serializer以更好地捕捉意图Utils,因为我们坚持使用我们只是不知道如何命名并包含大量逻辑(可能还有多种职责)的对象。

这将减少依赖项的数量,并且您的测试将有更少的东西来模拟。

这是该方法的一个版本,其sendRequest职责较少。

@Override
public void sendRequest() {
    try {
        // params are not clear as you don't show them to your code
        ThirdPartyResponse response = thirdPartyClient.sendRequest(param1, param2);

        byte[] serializedMessage = SerializationUtils.serialize(response);

        eventBus.sendToQueue(topicName, serialize);

    } catch (Exception e) {
        log.error("Send request was filed with exception: {}", e.getMessage());
    }
}

从您的代码中,我不确定您是否也可以将序列化和反序列化的责任转移到 . EventBus,但如果您可以这样做,它也会Seriazaliation从您的服务中删除。这将使EventBus负责它如何序列化和存储其中的东西,使其更具凝聚力。与它协作的其他对象只会告诉它向队列发送和反对,而不关心这些对象是如何到达那里的。


推荐阅读