我阅读了“ 清洁代码 ”一书((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
,cryptoSevice
和marshaller
。然后在 sendRequest 方法中调用此服务。
如果要为此服务创建单元测试,则需要模拟4服务。我觉得太多了。
有人可以指出如何更改此服务吗?
更改班级名称并保留原样? 分成几类? 还有吗?
答案 0 :(得分:0)
SRP是一个棘手的问题。
我们问两个问题:
什么是责任?
有哪些不同类型的责任?
关于职责的重要一件事是它们具有 作用域 ,并且您可以在不同级别的 粒度 中定义它们强>。并且本质上是分层的。
应用程序中的所有内容都有责任。
让我们从 模块 开始。每个模块都有责任,可以遵守SRP。
然后,该 模块 可以由 Layers 组成。每个 层 都有责任,并且可以遵守 SRP 。
每个 层 由不同的 Objects , Functions 等。每个 Object 和/或 Function 都有责任并可以遵守SRP。< / p>
每个 对象 具有 方法 。每个 Method 都可以遵守SRP。对象可以包含其他对象,依此类推。
对象中的每个 功能 或 方法 strong>由语句组成,可以细分为更多的 函数 / 方法 。每个声明也可以承担责任。
举个例子。假设我们有一个 Billing 模块。如果此模块在一个庞大的类中实现,那么该模块是否符合SRP?
让我们看一下不同类型的责任。
什么时候应该做
应该怎么做圆顶
让我们举个例子。
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
执行以下操作:
db
执行查询DbResult
并从中创建一个User
。User
UserService_v2
执行以下操作:
1.通过ID从存储库请求User
2.对User
UserService_v1
包含:
DbResult
映射到用户 UserService_v1
包含:
User
UserRepository
包含:
DbResult
被映射到User
我们在这里所做的是将 操作方式 的职责从Service
转移到Repository
。这样,每个班级都有一个改变的理由。如果方式发生变化,我们将更改Repository
。如果时间更改,我们将更改Service
。
通过这种方式,我们通过划分职责来创建彼此相互协作的对象以完成特定的工作。棘手的部分是:我们应分担什么责任?
如果我们有UserService
和OrderService
,则在这里不划分何时和如何。我们将什么进行划分,以便我们系统中的每个Entity可以提供一项服务。
那里的服务需要其他对象来完成其工作是很自然的。当然,我们可以将什么,何时和如何的所有职责添加到单个对象中,但这只会造成混乱,难以理解和很难改变。
在这方面,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
负责如何进行序列化并将内容存储在其中,从而使其更具凝聚力。与之协作的其他对象只会告诉它将对象发送并发送到队列中,而不在乎如何该对象到达那里。