如何使用单一责任原则重构服务?

时间:2019-03-28 09:42:42

标签: java spring oop architecture single-responsibility-principle

我阅读了“ 清洁代码 ”一书((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个服务:eventBusthirdPartyClientcryptoSevicemarshaller。然后在 sendRequest 方法中调用此服务。 如果要为此服务创建单元测试,则需要模拟4服务。我觉得太多了。

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

更改班级名称并保留原样? 分成几类? 还有吗?

1 个答案:

答案 0 :(得分:0)

SRP是一个棘手的问题。

我们问两个问题:

  • 什么是责任?

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

关于职责的重要一件事是它们具有 作用域 ,并且您可以在不同级别的 粒度 中定义它们强>。并且本质上是分层的。

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

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

然后,该 模块 可以由 Layers 组成。每个 都有责任,并且可以遵守 SRP

每个 由不同的 Objects Functions 等。每个 Object 和/或 Function 都有责任并可以遵守SRP。< / p>

每个 对象 具有 方法 。每个 Method 都可以遵守SRP。对象可以包含其他对象,依此类推。

对象中的每个 功能 方法 strong>由语句组成,可以细分为更多的 函数 / 方法 。每个声明也可以承担责任。

举个例子。假设我们有一个 Billing 模块。如果此模块在一个庞大的类中实现,那么该模块是否符合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
  5. 上执行操作

UserService_v2执行以下操作: 1.通过ID从存储库请求User 2.对User

进行操作

UserService_v1包含:

  • 如何 特定查询的构建
  • 如何 特定的DbResult映射到用户
  • 何时 此查询需要被调用(在这种情况下,以操作开头)

UserService_v1包含:

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

UserRepository包含:

  • 如何 特定查询的构建
  • 如何 特定的DbResult被映射到User

我们在这里所做的是将 操作方式 的职责从Service转移到Repository。这样,每个班级都有一个改变的理由。如果方式发生变化,我们将更改Repository。如果时间更改,我们将更改Service

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

如果我们有UserServiceOrderService,则在这里不划分何时如何。我们将什么进行划分,以便我们系统中的每个Entity可以提供一项服务。

那里的服务需要其他对象来完成其工作是很自然的。当然,我们可以将什么何时如何的所有职责添加到单个对象中,但这只会造成混乱,难以理解和很难改变。

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

让我们看一下您的具体情况。

如果您可以将ClientRequest的创建和签名责任移到ThirdPartyClient,那么您的SendRequestToThirdPartySystemService只会告诉何时应发送此请求。这将从您的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负责如何进行序列化并将内容存储在其中,从而使其更具凝聚力。与之协作的其他对象只会告诉它将对象发送并发送到队列中,而不在乎如何该对象到达那里。