Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Oшибка circular unit reference


Автор: Дмитрий01 29.8.2015, 17:20
Oшибка "circular unit reference" вот в таком коде:
Код

unit BaseUnit;
type TBase = class
  owner : TOwner;
  ...
end;

unit OwnerUnit;
type TOwner = class
  a : TA;
  b : TB;
  ...
end;

unit AUnit;
type TA = class(TBase)
  ...
end;

unit BUnit;
type TB = class(TBase)
  ...
end;


Что делать?

Автор: Poseidon 29.8.2015, 17:37
Пропиши во всех модулях остальные модули в uses после implementation

Добавлено через 2 минуты и 26 секунд
Хотя нет, у тебя тут все сложнее. Проще все в один модуль запихнуть. А вообще тут изначально ошиюбка в иерархии классов.

Автор: Дмитрий01 29.8.2015, 18:07
В один модуль всё запихивать не хочу: слишком длинно будет.
А что посоветуете на счёт иерархии?

Автор: Poseidon 29.8.2015, 19:21
 Не правильно использовать в базовом классе параметры, у которых тип - дочерний класс. Другими словами, родитель ничего не должен знать о своих потомках. 

Автор: dnek 30.8.2015, 14:30
Согласен с Poseidon.
Я бы даже сказал что это недопустимо и ломает основополагающие принципы наследования.
Но в данном случае проблема не в этом.
Если Вы хотите использовать именно такую структуру unit-ов, то:
1. В BaseUnit TBase.owner объявить как TObject, в uses секции implementation добавить OwnerUnit а в коде использовать приведение к типу - TOwner(owner)
2. В OwnerUnit TOwner.a и TOwner.b объявить как TBase, в uses секции interface добавить BaseUnit а в коде использовать приведение к типу - TA(a) и TB(b) но лучше корректно спроектировать TBase и избежать этого

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e2e93cae20159e491cc064_0

Автор: Дмитрий01 30.8.2015, 21:53
Цитата(Poseidon @ 29.8.2015,  19:21)
Не правильно использовать в базовом классе параметры, у которых тип - дочерний класс. Другими словами, родитель ничего не должен знать о своих потомках.

Мне этого и не нужно. Мне нужно только, чтобы объект A  мог при необходимости обратиться к B и наоборот.

Автор: Poseidon 30.8.2015, 23:49
Цитата(Дмитрий01 @  30.8.2015,  21:53 Найти цитируемый пост)
Мне нужно только, чтобы объект A  мог при необходимости обратиться к B и наоборот. 
Вот наоборот быть не должно. Если А обращантся к В, то В считается родительским и он ничего не должен знать про А. Посмотри к примеру иерархию VCL. Там родительские классы не знают ничего про дочерние. Или еще пример: базовый класс в Delphi - TObject. Это красс-родитель для всех остальных классов и он ничего не знает не про одного своего потомка. Поэтому если у тебя появилась необходимость обратится из В к А, то надо что-то менять в логике программы.

Автор: Дмитрий01 1.9.2015, 13:08
Я бы хотел, чтобы А и В обращались друг к другу, находясь на одном уровне иерархии. Разве это недопустимо?

Автор: Poseidon 1.9.2015, 13:52
Цитата(Дмитрий01 @  1.9.2015,  13:08 Найти цитируемый пост)
Я бы хотел, чтобы А и В обращались друг к другу, находясь на одном уровне иерархии.
Что бы они общались на одном уровне иерархии, надо их обоих сделать потомками одного родителя, и все общие методы описать у родителя. Тогда не понадобится у A создавать параметры типа B, а у B - параметры типа А.

Автор: dnek 2.9.2015, 09:07
Цитата

   Если А обращантся к В, то В считается родительским и он ничего не должен знать про А
   

Это верно только если А наследуется от В а не обращается.
Цитата

   Я бы хотел, чтобы А и В обращались друг к другу, находясь на одном уровне иерархии. Разве это недопустимо?
   

Не только допустимо но еще и является стандартной часто используемой практикой.
В Вашем случае есть 3 выхода из ситуации:
1. Классы обращающиеся друг к другу запихнуть в один юнит (самый простой, быстрый и удобный выход)
2. Использовать приведение к типу как я писал выше (наиболее часто используемый)
3. Ввести поддержку интерфейсов в этих классах и вынести их объявление в отдельный юнит (наиболее профессиональный подход но не всегда оправдывает затраченные время и силы)

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e69205ae2015af251cc084_0

Автор: Poseidon 2.9.2015, 11:50
Цитата(dnek @  2.9.2015,  09:07 Найти цитируемый пост)
В Вашем случае есть 3 выхода из ситуации

dnek, в данном случае на лицо не верно спроектированная иерархия классов. TBase, на сколько я понимаю, задумывался как базовый класс, коим он и является для TA и TB. В то же время TBase содержит в себе параметр типа TOwner. И все бы хорошо, но TOwner содержит парамеры типа TA и TB. Другими словами, из TBase мы вполне себе можем достучаться до TA (TBase.owner.a). Т.е. из родителя имеем доступ к потомку, а это в корне не правильно. Пихание в один юнит избавит от ругательств компилятора, но не избавит от дальнейших ошибок на уровне проектирования. В лучшем случае нарвемся на вечный цикл и, как следствие, мертвое зависание программы.


Автор: dnek 2.9.2015, 14:23
Да кто Вам сказал что это неправильно. Как пример - TControl.Parent и TComponent.Owner
Другое дело, что TOwner.a и TOwner.b корректнее было бы объявить как TBase и правильно спроектировать его, но TOwner может не знать о TBase но знать TA и TB. Да и TBase лучше объявить как abstract.

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e6dc4dae20155c1e1cbe87_0

Автор: dnek 2.9.2015, 14:33
Нельзя объявлять только что-то типа "TBase.A: TA;". Это действительно недопустимо. Только "TBase.A: TBase;"

А с промежуточным классом, который не наследуется от TBase - сколько угодно и как угодно.

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e6de91ae201508231cbdb5_0

Автор: Poseidon 2.9.2015, 14:47
Цитата(dnek @  2.9.2015,  14:23 Найти цитируемый пост)
Да кто Вам сказал что это неправильно. Как пример - TControl.Parent и TComponent.Owner
Ну хотя бы Стив Макконнелл в своей книге "Совершенный код" посвещает этому вопросу очень большое внимание. 

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

Цитата(dnek @  2.9.2015,  14:23 Найти цитируемый пост)
TOwner может не знать о TBase но знать TA и TB.
К TOwner вообще нет притензий, тут все норально. Вопросы есть к TBase.



Автор: dnek 2.9.2015, 15:00
Цитата

   Ну хотя бы Стив Макконнелл в своей книге "Совершенный код" посвещает этому вопросу очень большое внимание
   

Теория и практика разные вещи
Посмотрите на объявление TControl и TWinControl в модуле Controls:
Код

  TControl = class(TComponent)
  private
    FParent: TWinControl;
  ...


  TWinControl = class(TControl)
  ...


После этого, если хотите, продолжим дискуссию :)

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e6e4e7ae2015a6281cbdf7_0

Автор: Poseidon 2.9.2015, 15:51
Цитата(dnek @  2.9.2015,  15:00 Найти цитируемый пост)
Посмотрите на объявление TControl и TWinControl в модуле Controls:
Хорошо, дальше смотрели? Мы дальше где-нибудь в TControl используем функционал TWinControl? Его собственные методы? То, что там TWinControl, обослувленно Windows, которая требует, что бы родительским компонентом был только оконный компонент (что, в принципе, логично). Что бы это реализовать, пришлось огланичить все многообразие элементов управления Windows (TControl) только конкретно оконными компонентами (TWinControl). Грубо говоря, что бы в Parent нельзя было передать тот же TComponent или его наследников. Можем говорить о том, что это скорее исключение из правил и обусловлено это требованиями ОС.

Приведу пример. Допустим в TBase есть какой-то метод, который создает owner.a (owner.a := TA.Create). Он через наследование доступен и в TA. В TA этот метод вызывается в конструкторе. Что произойдет? Зависон на ровном месте. 

Автор: dnek 2.9.2015, 16:22
Цитата

   Допустим в TBase есть какой-то метод, который создает owner.a (owner.a := TA.Create). Он через наследование доступен и в TA. В TA этот метод вызывается в конструкторе. Что произойдет? Зависон на ровном месте
   

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

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Oshibka-circular-unit-reference-id55e1bfcfae20153e608b4567#findElement_E7045_55e6f812ae201509371cc03a_0

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)