СРП хитрый.
Давайте зададим два вопроса:
Одна важная особенность обязанностей состоит в том, что у них есть Область действия , и вы можете определять их на разных уровнях Степень детализации . и имеют иерархический характер.
Ответственность за все в вашем приложении может быть.
Давайте начнем с Модули . Каждый модуль имеет обязанности и может придерживаться SRP.
Тогда этот Модуль может быть изготовлен из Слоев . Каждый Слой несет ответственность и может придерживаться SRP .
Каждый Слой состоит из различных объектов , Функции и т. Д. Каждый Объект и / или Функция имеет обязанности и может придерживаться SRP.
Каждый Объект имеет Методы . Каждый Метод может придерживаться SRP. Объекты могут содержать другие объекты и т. Д.
Каждый Функция или Метод в Объект состоит из операторов и может быть разбит на более Функции / Методы . У каждого утверждения тоже могут быть обязанности.
Давайте приведем пример. Допустим, у нас есть модуль Billing . Если этот модуль реализован в одном огромном классе, придерживается ли этот модуль SRP?
- С точки зрения системы, модуль действительно придерживается SRP. Тот факт, что это беспорядок, не влияет на этот факт.
- С точки зрения модуля класс, представляющий этот модуль, не придерживается 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
делает эти вещи:
- Создает строку SQL-запроса.
- Вызывает
db
для выполнения запроса
- Берет определенный
DbResult
и создает из него User
.
- Работает ли на
User
UserService_v2
делает эти вещи:
1. Запросы из репозитория User
по идентификатору
2. Работает ли на User
UserService_v1
содержит:
- Как сборка для конкретного запроса
- Как определенный
DbResult
сопоставляется с пользователем
- Когда , этот запрос нужно назвать (в начале операции в этом случае)
UserService_v1
содержит:
- Когда a
User
следует извлечь из БД
UserRepository
содержит:
- Как определенный запрос является сборкой
- Как определенный
DbResult
сопоставлен с User
То, что мы делаем здесь, - это перевод ответственности How с Service
на Repository
.Таким образом, у каждого класса есть одна причина для изменения .Если как изменяется, мы меняем Repository
.Если при изменении , мы меняем 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
ответственным за , как он сериализовал, и хранит вещи внутри него, делая его более сплоченным.Другие объекты, которые сотрудничают с ним, просто скажут ему отправить и возразить в очередь, не заботясь как эти объекты попадают туда.