java - 如何使用单一责任原则重构我的服务?
问题描述
我阅读了“清洁代码”一书((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 个其他服务:eventBus
、thirdPartyClient
和。并在sendRequest方法中调用每个此服务。如果我想为这个服务创建一个单元测试,我需要 mock 4 个服务。我觉得太多了。cryptoSevice
marshaller
有人可以指出如何更改此服务吗?
更改班级名称并保持原样?分成几个班?还有什么?
解决方案
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_v1
并UserService_v2
做完全相同的事情,但方式不同。从系统的角度来看,这些服务遵循 SRP,因为它们包含相关的操作Users
。
现在让我们来看看他们为完成工作实际做了什么。
UserService_v1
做这些事情:
- 构建 SQL 查询字符串。
- 调用
db
执行查询 - 获取特定的并从中
DbResult
创建一个。User
- 是否对
User
UserService_v2
做这些事情: 1.User
通过 ID 从存储库请求 2. 对User
UserService_v1
包含:
- 如何 构建特定查询
- 具体如何映射到用户
DbResult
- 当 需要调用这个查询时(在这种情况下的请求操作中)
UserService_v1
包含:
- 何时 应从数据库中检索a
User
UserRepository
包含:
- 如何 构建特定查询
- 具体如何映射到
DbResult
User
我们在这里所做的是将HowService
的责任从Repository
. 这样每个班级都有一个改变的理由。如果如何改变,我们改变Repository
. 如果when改变,我们改变Service
.
通过这种方式,我们通过划分职责来创建相互协作以完成特定工作的对象。棘手的部分是:我们划分了哪些责任?
如果我们有一个UserService
并且OrderService
我们不划分何时以及如何在这里。我们划分什么,以便我们可以在我们的系统中为每个实体提供一项服务。
那里的服务需要其他对象来完成它们的工作是很自然的。我们当然可以将什么、何时和如何的所有职责添加到单个对象中,但这只会导致混乱、不可读且难以更改。
在这方面,SRP 通过让更多更小的部分相互协作和使用来帮助我们实现更简洁的代码。
我们来看看你的具体情况。
如果您可以通过将创建和签名的责任转移到ClientRequest
,ThirdPartyClient
您SendRequestToThirdPartySystemService
只会告诉何时应该发送此请求。这将从您Marshaller
的.CryptoService
SendRequestToThirdPartySystemService
此外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
负责它如何序列化和存储其中的东西,使其更具凝聚力。与它协作的其他对象只会告诉它向队列发送和反对,而不关心这些对象是如何到达那里的。
推荐阅读
- python-3.x - 根据索引插入无
- vue.js - VueJS:材料表:如何从行中分配一个值作为列标题?
- python - 如何将列表的 numpy 列表直接作为列附加到现有 CSV 文件?
- visual-studio-code - 如何在 vscode 中激活 neovim 的键绑定?
- javascript - 在 JavaScript 中导入带有波兰语字符的 JSON 时编码错误
- node.js - 为没有开发依赖项的 nodejs 分发创建一个 zip 文件(捆绑)
- java - JPA Criteria API - 查询代码在单独执行时有效,但在用作子查询时无效
- python - DPI-1047:找不到 64 位 Oracle 客户端库:“找不到指定的模块”- 不重复
- macos - 无法在 Bot Framework 模拟器上运行本地机器人
- typescript - Nest 无法解决 AuthService 的依赖关系 - 导入模块的问题,该模块使用 Mongoose 和提供者