Статьи

Преобразование циклической зависимости в направленную зависимость

Итак, это вышло более чем на месяц позже … Woops.

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

Что такое циклическая зависимость? Это взаимозависимость, когда один класс зависит от другого, что также зависит от первого, как показано ниже.

циклическая зависимость

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

циклическая зависимость

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

В любом случае, вы ищете хороший ациклический ориентированный граф зависимостей, как показано ниже, где все стрелки указывают вниз.

циклическая зависимость

Итак, как мы пойдем из этого

циклическая зависимость

Для ациклического ориентированного графа? Как будет выглядеть этот график?

Ну, это будет выглядеть так!

циклическая зависимость

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

С этими очень расплывчатыми графическими изображениями может быть трудно реально понять, как это можно сделать, поэтому я приведу вам очень простой пример кода (написанный на Kotlin и Python!). Это должно помочь вам начать разбивать циклические зависимости.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class A {
   var b: B? = null
   var _observed: Int = 0
   var observed: Int
       get() = _observed
       set(value) {
           _observed = value
           b?.alert()
       }
 
   fun alert(): Unit {
       println("A.alert")
   }
 
   fun doSomething(): Unit {
       alert()
   }
}
 
class B {
   var a: A? = null
   var _observed: Int = 0
   var observed: Int
       get() = _observed
       set(value) {
           _observed = value
           a?.alert()
       }
 
   fun alert(): Unit {
       println("B.alert")
   }
 
   fun doSomething(): Unit {
       alert()
   }
}

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class A:
   def __init__(self):
       self.b: B = None
       self._observed: int = 0
 
   @property
   def observed(self):
       return self._observed
 
   @observed.setter
   def observed(self, value):
       self._observed = value
       self.b.alert()
 
   def alert(self):
       print("A.alert")
 
   def doSomething(self):
       self.alert()
 
class B:
   def __init__(self):
       self.a: A = None
       self._observed: int = 0
 
   @property
   def observed(self):
       return self._observed
 
   @observed.setter
   def observed(self, value):
       self._observed = value
       self.a.alert()
 
   def alert(self):
       print("B.alert")
 
   def doSomething(self):
       self.alert()

Два класса почти одинаковы, но это не имеет значения. Важно то, что зависимые части могут быть извлечены. От каких частей типа B зависит A ? Наоборот? Каждый класс зависит от метода alert() другого. Итак, давайте извлечем их:

01
02
03
04
05
06
07
08
09
10
11
12
class AAlerter {
   fun alert(): Unit {
       println("A.alert")
   }
}
 
class BAlerter {
   fun alert(): Unit {
       println("B.alert")
   }
 
}

1
2
3
4
5
6
7
class AAlerter:
   def alert(self):
       print("A.alert")
 
class BAlerter:
   def alert(self):
       print("B.alert")

Теперь другие классы могут зависеть от этих

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A (var a: AAlerter, var b: BAlerter) {
   var _observed: Int = 0
   var observed: Int
       get() = _observed
       set(value) {
           _observed = value
           b.alert()
       }
 
   fun doSomething(): Unit {
       a.alert()
   }
}
 
class B (var a: AAlerter, var b: BAlerter){
   var _observed: Int = 0
   var observed: Int
       get() = _observed
       set(value) {
           _observed = value
           a.alert()
       }
 
   fun doSomething(): Unit {
       b.alert()
   }
}

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A:
   def __init__(self, a: AAlerter, b: BAlerter):
       self.a = a
       self.b = b
       self._observed: int = 0
 
   @property
   def observed(self):
       return self._observed
 
   @observed.setter
   def observed(self, value):
       self._observed = value
       self.b.alert()
 
   def doSomething(self):
       self.a.alert()
 
class B:
   def __init__(self, a: AAlerter, b: BAlerter):
       self.a = a
       self.b = b
       self._observed: int = 0
 
   @property
   def observed(self):
       return self._observed
 
   @observed.setter
   def observed(self, value):
       self._observed = value
       self.a.alert()
 
   def doSomething(self):
       self.b.alert()

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

Теперь можно создать экземпляр, в котором конструктор принимает все поля без каких-либо временных null значений.

Outro

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

Я признаю, что существует по крайней мере один «вид» циклической зависимости, который это не исправляет: дочерний элемент, указывающий назад на своего родителя. Например, у вас есть FilingCabinet со списком File s, и эти File также имеют указатель на FilingCabinet в FilingCabinet они находятся, если вам когда-нибудь понадобится способ вернуться обратно в дерево, когда вы не знаете оригинал уже.

Совет, который я видел по этому поводу, заключается в том, чтобы потерять ссылку, возвращающуюся к родителю, и вместо этого вставить метод, который выполняет какой-то поиск, чтобы найти родителя. Это глупо; у него все еще есть циклическая зависимость; просто зависимость либо удаляется на один шаг дальше (для поиска в памяти, на языке), либо перемещается в систему другого типа (для чего-то вроде поиска в базе данных).

Я рекомендую либо попытаться сделать так, чтобы коду не нужно было возвращаться назад по дереву, либо чтобы родительский объект передавался вместе с дочерним, возможно, в каком-то виде пользовательского типа пара родительский / дочерний.

Опубликовано на Java Code Geeks с разрешения Джейкоба Циммермана, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Преобразование циклической зависимости в направленную зависимость

Мнения, высказанные участниками Java Code Geeks, являются их собственными.