Как убрать циклическую зависимость между двумя классами в одном пакете? - PullRequest
0 голосов
/ 21 июня 2019

Мы проводим рефакторинг, и мы попали в стену здесь. Есть два класса обслуживания, AService и BService, выполняющие разные работы, имеют циклическую зависимость. Раньше они были в разных библиотеках, поэтому круговой зависимости не было. Но теперь после рефакторинга мы переместили их в одну библиотеку в один пакет. Задача AService - сохранить некоторые данные в базе данных NoSQL для отдельного варианта использования. Работа BService полностью отличается от работы AService. AService нужен BService для получения некоторых данных. BService требуется AService для записи некоторых данных в базу данных NoSQL и чтения обратно.

Как исправить проблему циклической зависимости, создав каждую из них, зависит друг от друга. Любой шаблон дизайна для таких вопросов?

class AService {
@Autowired
private BService bService;

}

class BService {
@Autowired
private AService aService;

}

Ответы [ 4 ]

3 голосов
/ 21 июня 2019

Реальный вопрос заключается в том, почему ваша концепция делает А и В зависимыми друг от друга?

  • Возможно, А и В представляют одну и ту же концепцию и могут быть объединены.
  • Также может быть, что часть вашего сервиса A или B должна быть извлечена в новом сервисе C.

В этом последнем случае вам нужно знать, какой метод зависит от того, какой сервис и какойможет быть извлечен в новом сервисе.Без контекста и списка методов трудно предложить решение.В случае косвенных рекурсивных методов вам, возможно, придется сократить также некоторые методы.

Примечание: не пытайтесь найти обходной путь, чтобы заставить его работать с циклическими зависимостями, это показывает проблему концепции, у вас естьсделать рефакторинг.

1 голос
/ 22 июня 2019

Решение 1 (рекомендуется): Пересмотр классовых обязанностей.Придерживаясь принципа единой ответственности и в соответствии с указанными вами сведениями о классе, мы можем исправить дизайн вашего класса, выделив хотя бы один новый класс: DatabaseConnector.Этот класс инкапсулирует все связанные с базой данных операции (CRUD) и, следовательно, снимает циклическую зависимость классов обслуживания (без изменения первоначальных представлений классов).

// This is just a raw template to express the intention
class DatabaseConnector {
  void createInDatabase(Object data) {
  }

  Object readFromDatabase(Object args) {
  }

  void updateInDatabase(Object data) {
  }

  void deleteFromDatabase(Object data) {
  }
}

class AService {
  @Autowired
  private DatabaseConnector dbConnector;    
}

class BService {
  @Autowired
  private DatabaseConnector dbConnector;    
}

Вы можете добавить более специализированные методы в DatabaseConnector для удовлетворения особых требований (например, readName(), readId() и т. д.).

Поскольку существует вероятность того, что в будущем потребуется больше классов для доступа к базе данных, вы уже решили или предотвратили новые циклические зависимости сегодня.Инкапсуляция решает потенциально предстоящие проблемы.

Решение 2: инверсия зависимостей

interface IAService {    
}

interface BService {
}

class AService implements IAService {
  @Autowired
  private IBService bService;    
}

class BService implements IBService {
  @Autowired
  private IAService aService;    
}

Круговая зависимость всегда является показателем плохого проектирования классов.В большинстве случаев причиной является нарушение принципа единой ответственности ( S в SOLID).Неправильная концепция композиции также может привести к этой ошибке проектирования.Что всегда поможет, но не исправит концептуальные недостатки классовой ответственности, это введение интерфейсов для инвертирования всех зависимостей ( D в SOLID).Принятие серьезных принципов SOLID может сэкономить много времени и работы и всегда приведет к улучшению кода (хотя вы представили более высокую сложность кода).

Шаблон посредника также может помочь поднять циклические зависимости путем инкапсуляции двунаправленного взаимодействия.из двух или более объектов.

Недостатком вашего текущего кода является (помимо циклической зависимости), что всякий раз, когда изменяется класс A, а также изменяется постоянство данных, вы должны коснуться и изменить класс B. Эти изменения могут нарушитькласс B, который использует те же самые операции сохранения.Это верно для всех случаев, когда один класс имеет общие обязанности с другим классом.Если бы не было общего кода, оба класса не знали бы друг друга вообще.В вашем особом случае, когда зависимость циклическая, вы добавляете этот недостаток и к другому направлению зависимости: когда B нужно настроить или расширить способ чтения данных, вам придется изменить класс A, что может нарушить A. Если выиспользуя модульные тесты, вам придется провести рефакторинг тестов обоих классов тоже.Эта тесная (и циклическая) связь A и B приведет к ошибкам или ошибкам.Расширение кода стало опасным.Но хорошая новость заключается в том, что циклические зависимости никогда не компилируются (поскольку разрешение зависимости приводит к бесконечной рекурсии).

0 голосов
/ 21 июня 2019

Самый простой способ - переместить метод, который использует Сервис B, из Сервиса A в Сервис B ИЛИ в совершенно другой класс.

Конструкция показывает, что у вас, вероятно, слишком много методов в Сервисе A, которые Сервис A фактически не использует, но которые являются открытыми методами, которые действительно должны быть статическими и полностью в другом Классе.

0 голосов
/ 21 июня 2019

Попробуйте вместо этого установить сеттер на сеттер:

@Autowired
public void setServiceB(ServiceB service){
 this.serviceB = service;
 }
...