【问题标题】:How can I refactor my service use single responsibility principle?如何使用单一责任原则重构我的服务?
【发布时间】:2019-08-19 00:51:54
【问题描述】:

我阅读了“Clean Code”一书((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 方法中调用每个此服务。 如果我想为这个服务创建一个单元测试,我需要 mock 4 个服务。我觉得太多了。

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

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

【问题讨论】:

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


【解决方案1】:

SRP 是一个棘手的问题。

让我们问两个问题:

  • 什么是责任?

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

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

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

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

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

每个由不同的对象函数组成 等。每个 Object 和/或 Function 都有责任并且可以遵守 SRP。

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

Object 中的每个 FunctionMethod 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 进行操作

UserService_v2 做这些事情: 1. 从存储库请求User 按ID 2.是否对User进行操作

UserService_v1 包含:

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

UserService_v1 包含:

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

UserRepository 包含:

  • 如何 具体的查询是如何构建的
  • 如何 具体的DbResult 映射到User

我们在这里所做的是将 How 的职责从 Service 转移到 Repository。这样每个班级都有一个改变的理由。如果如何改变,我们改变Repository。如果改变,我们改变Service

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

如果我们有UserServiceOrderService,我们不会在这里划分whenhow。我们划分what,这样我们的系统中每个Entity 就可以拥有一项服务。

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

在这方面,SRP 帮助我们实现更简洁的代码,方法是让更多更小的部分协作使用彼此。

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

如果您可以将责任如何通过将ClientRequest 移动到ThirdPartyClient 来创建和签名,那么您的SendRequestToThirdPartySystemService 只会告诉何时应发送此请求。这将从您的 SendRequestToThirdPartySystemService 中删除 MarshallerCryptoService 作为依赖项。

您还拥有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 负责如何它序列化并存储其中的内容,使其更具凝聚力。与它协作的其他对象只会告诉它发送并反对队列,而不关心如何这些对象到达那里。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多