#java #spring #refactoring
#java #spring #рефакторинг
Вопрос:
У меня есть проект, который использует интерфейсы Spring boot и Spring Data JPA. Существует метод, который появляется в 5 сервисах, и код помечается моей IDE как дубликат. Я опубликую 2 метода, чтобы вы могли видеть, как выглядит код, это в основном то же самое, единственное, что отличается, это используемый интерфейс репозитория.
В AccountRequestService.java У меня есть этот метод:
public Page<AccountRequest> getAccountRequestPageable(final Pageable pageable, final String status) {
Page<AccountRequest> accountRequestPage;
if (UNCOMPLETED_STATUS.equals(status)) {
accountRequestPage = accountRequestRepository.findByBaseRequestInitiatorIgnoreCaseAndBaseRequestOutdatedDaysGreaterThanAndBaseRequestStatus(pageable,
LoggedUserUtil.getLoggedUserName(), 0, BaseRequestStatusType.APPROVED);
} else {
if (!ALL_STATUS.equals(status)) {
accountRequestPage = accountRequestRepository.findByBaseRequestStatusAndBaseRequestInitiatorIgnoreCase(pageable,
BaseRequestStatusType.valueOf(status), LoggedUserUtil.getLoggedUserName());
} else {
accountRequestPage = accountRequestRepository.findByBaseRequestInitiatorIgnoreCase(pageable, LoggedUserUtil.getLoggedUserName());
}
}
return accountRequestPage;
}
В ApplicationRequestService.java:
public Page<ApplicationsRequest> getApplicationsRequestPageable(final Pageable pageable, final String status) {
Page<ApplicationsRequest> applicationsRequestPage;
if (UNCOMPLETED_STATUS.equals(status)) {
applicationsRequestPage = applicationRequestRepository
.findByBaseRequestInitiatorIgnoreCaseAndBaseRequestOutdatedDaysGreaterThanAndBaseRequestStatus(pageable, LoggedUserUtil.getLoggedUserName(), 0,
BaseRequestStatusType.APPROVED);
} else {
if (!ALL_STATUS.equals(status)) {
applicationsRequestPage = applicationRequestRepository.findByBaseRequestStatusAndBaseRequestInitiatorIgnoreCase(
pageable, BaseRequestStatusType.valueOf(status), LoggedUserUtil.getLoggedUserName());
} else {
applicationsRequestPage = applicationRequestRepository.findByBaseRequestInitiatorIgnoreCase(pageable, LoggedUserUtil.getLoggedUserName());
}
}
return applicationsRequestPage;
}
Я опубликую код для AccountRequestRepository, поскольку ApplicationRequestRepository аналогичен, но ссылается на тип объекта ApplicationRequest вместо AccountRequest:
public interface AccountRequestRepository extends JpaRepository<AccountRequest, Long> {
AccountRequest findByBaseRequestId(Long id);
Page<AccountRequest> findByBaseRequestInitiatorIgnoreCaseAndBaseRequestOutdatedDaysGreaterThanAndBaseRequestStatus(Pageable pageable, String initiator,
Integer days, BaseRequestStatusType status);
Page<AccountRequest> findByBaseRequestStatusAndBaseRequestInitiatorIgnoreCase(Pageable pageable, BaseRequestStatusType baseRequestStatusType, String initiator);
Page<AccountRequest> findByBaseRequestInitiatorIgnoreCase(Pageable pageable, String loggedUserName);
}
Теперь мне было интересно, есть ли какой-либо способ реорганизовать этот код, чтобы я мог избавиться от дубликата. Я пытался найти решение, используя Java 8 и передавая функции, но я не могу понять, как это параметризовать.
Комментарии:
1. Похоже, вы опубликовали тот же код из того же класса.
2. @Hulk Вот как работают интерфейсы JPA, вам в основном нужно написать код, подобный этому, чтобы метод мог быть преобразован в SQL-запрос. Я думаю, что другой альтернативой было бы написать собственный запрос и присвоить методу другое имя.
Ответ №1:
Если вы не можете или по какой-то причине не хотите изменять существующую структуру классов, все еще существуют способы рефакторинга такого кода. Вы могли бы, например, передать свои функции запроса в качестве параметров универсальной функции, которая решает, какой из запросов вызывать, например
public Page<AccountRequest> getAccountRequestPageable(final Pageable pageable, final String status) {
return getPageable(pageable, status,
p -> accountRequestRepository.findByBaseRequestInitiatorIgnoreCaseAndBaseRequestOutdatedDaysGreaterThanAndBaseRequestStatus(p, LoggedUserUtil.getLoggedUserName(), 0, BaseRequestStatusType.APPROVED),
p -> accountRequestRepository.findByBaseRequestInitiatorIgnoreCase(p, LoggedUserUtil.getLoggedUserName()),
p -> accountRequestRepository.findByBaseRequestStatusAndBaseRequestInitiatorIgnoreCase(p, BaseRequestStatusType.valueOf(status), LoggedUserUtil.getLoggedUserName()));
}
public Page<ApplicationsRequest> getApplicationsRequestPageable(final Pageable pageable, final String status) {
return getPageable(pageable, status,
p -> applicationRequestRepository.findByBaseRequestInitiatorIgnoreCaseAndBaseRequestOutdatedDaysGreaterThanAndBaseRequestStatus(pageable, LoggedUserUtil.getLoggedUserName(), 0,BaseRequestStatusType.APPROVED),
p -> applicationRequestRepository.findByBaseRequestInitiatorIgnoreCase(pageable, LoggedUserUtil.getLoggedUserName()),
p -> applicationRequestRepository.findByBaseRequestStatusAndBaseRequestInitiatorIgnoreCase(pageable, BaseRequestStatusType.valueOf(status), LoggedUserUtil.getLoggedUserName()));
}
public static <T,Q> Page<Q> getPageable(final Pageable pageable, final String status,
Function<Pageable, Page<Q>> findUncompleted,
Function<Pageable, Page<Q>> findAll,
Function<Pageable, Page<Q>> findOther
) {
Page<Q> accountRequestPage;
if (UNCOMPLETED_STATUS.equals(status)) {
accountRequestPage = findUncompleted.apply(pageable);
} else {
if (!ALL_STATUS.equals(status)) {
accountRequestPage = findOther.apply(pageable);
} else {
accountRequestPage = findAll.apply(pageable);
}
}
return accountRequestPage;
}
Имеет ли смысл такой рефакторинг, зависит от проекта, и на него нельзя ответить в целом. Когда вы думаете о дублировании кода, важно учитывать возможность того, что сходство может быть просто совпадением. Если код двух сегментов сейчас выглядит похожим, но в будущем его необходимо будет изменить независимо, возможно, будет лучше сохранить их отдельно. Если они действительно разделяют основополагающий принцип в вашей бизнес-области, который можно абстрагировать, сделайте это. Другими словами, следуйте принципу единой ответственности в смысле «единственной причины для изменения».
Ответ №2:
Я думаю, что вам нужно, это создать интерфейс «запрос» и использовать сущность наследования гибернации (которые будут понятны Весна данных), чтобы получить страницу<? расширяет запрос>. See https://www.baeldung.com/hibernate-inheritance.
Затем вы можете поместить общую логику, используя интерфейс запроса, или даже определить запрос как суперкласс, где вы будете хранить эту общую логику, ваш вызов !
Комментарии:
1. Спасибо, это тоже хорошее решение! Я протестировал его, и он работает просто отлично, просто я хотел сделать это с помощью Java 8.