Иногда циклическая зависимость требует некоторого анализа участвующих компонентов.Выясните, почему круг существует, а затем выясните, почему он не должен существовать.Анализ может происходить на нескольких уровнях.Вот два уровня, с которых я бы начал.
(Поскольку код явно упрощен по сравнению с реальным кодом, я не буду предполагать, что он показывает степень истинной проблемы. Кстати, спасибоза то, что не залили нас огромными подробностями! Код был достаточен для иллюстрации проблемы в целом. Любые конкретные предложения в моих ответах аналогично предназначены для иллюстрации пунктов, а не для того, чтобы быть окончательным решением.)
На одном уровне вы можете посмотреть на намерения классов.Забудьте код и сосредоточьтесь на цели.Имеет ли смысл для класса A быть неспособным определить себя, не зная, что такое класс B?Имейте в виду, что это сильнее, чем знать, что класс B существует (что будет соответствовать предварительному определению, заголовок не требуется).Если это не имеет смысла, не глядя на код, то, возможно, вы нашли что-то, над чем можно поработать.Следует признать, что использование шаблонов усложняет ситуацию, поскольку вся реализация должна быть в заголовке.
Например, Serializable
действительно должен иметь возможность определять себя, не зная, что будет сделано с сериализацией (т. Е.Logger
).Однако это шаблон, и его реализация должна иметь возможность регистрировать ошибки.Итак ... хитро.
Тем не менее, это место, где можно искать решения.Одной из возможностей может быть разделение регистрации ошибок на базовую часть, которая обрабатывает только строки (уже сериализованные данные), и слой перевода, который может преобразовать LogMessage
в строку для базовой части.Ошибка во время десериализации настоятельно указывает на отсутствие чего-либо для сериализации, поэтому регистрация может идти непосредственно к базовому элементу.Круг зависимостей разорвется на цепи:
Serializable -> LoggerBase
Logger -> LoggerBase
Logger -> LogMessage -> Serializable -> LoggerBase
На другом уровне вы можете детально взглянуть на код, не слишком заботясь о цели.У вас есть заголовок A, включая заголовок B - почему?Какие части A на самом деле используют что-то из B?Какие части B на самом деле используются?Составьте диаграмму, если вам нужно лучше представить, где находится эта зависимость.Затем внесите некоторое внимание в цель.Определены ли эти части в соответствующих местах?Существуют ли другие возможности?
Например, в примере кода причина, по которой Serializable
нужно LogMessage
, заключается в получении доступа к перечислению LogMessage::ERROR
.Это не является серьезной причиной необходимости полного определения LogMessage
.Возможно, оболочка типа PostLogErrorSimple
могла бы устранить необходимость знать константу серьезности?Может быть, реальность сложнее, но дело в том, что некоторые зависимости можно обойти, поместив зависимость в исходный файл.Иногда исходный файл предназначен для другого класса.
Другой пример взят из класса Logger
.Для этого класса требуется LogMessage
, чтобы получить доступ к перечислению LogMessage::Severity
(т. Е. Перечислению, в котором ERROR
имеет одно из своих значений).Это также не является серьезной причиной необходимости полного определения класса.Возможно перечисление должно быть определено в другом месте?Как часть Logger
возможно?Или, может быть, не в определении класса вообще?Если этот подход работает, круг зависимостей разбивается на цепочки:
Serializable -> Severity
Serializable -> Logger -> Severity // To get the PostLogMessageSimple function
Logger -> Severity
В идеале, после того, как перечисление позаботится, заголовок Logger
сможет обойтись только с помощью предварительного объявления LogMessage
вместо включения полного заголовка.(Предварительного объявления достаточно, чтобы получить объект по ссылке. И, вероятно, полное определение Logger
будет иметь некоторые функции, принимающие LogMessage
параметры.)