Подтвердить что ты не робот

Обойти круговые ссылки в Delphi

Есть ли способ обойти циркулярные ссылки в Delphi?

Может быть, новая версия delphi или какой-то волшебный хак или что-то еще?

Мой проект delphi содержит 100 000 строк кода, в основном основанных на одноэлементных классах. Мне нужно реорганизовать это, но это означало бы несколько месяцев "круговой ссылки" ада:)

4b9b3361

Ответ 1

В течение последних 10 лет я поддерживал около миллиона строк устаревшего кода, поэтому я понимаю вашу боль!

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

В этой ситуации (когда мне повезло!) я могу тщательно извлечь эти части кода в новый блок C, который содержит константы, определения типов и общий код. Затем единицы A и B используют блок C.

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

Ответ 2

  • Кажется, у вас довольно серьезные проблемы с дизайном кода. Помимо многих признаков таких проблем, одна из них - единичные циркулярные ссылки. Но, как вы сказали, вы не можете реорганизовать весь код.
  • Переместите все, что возможно в раздел РЕАЛИЗАЦИЯ. Им разрешено иметь круглые ссылки.
  • Чтобы упростить задачу (2), вы можете использовать инструменты сторонних разработчиков. Я бы порекомендовал - анализатор Peganza Pascal (http://www.peganza.com). Он предложит вам перейти к разделу реализации. Это даст вам больше советов по улучшению качества кода.

Ответ 3

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

Нет "волшебного взлома". Циклические ссылки вызовут бесконечный цикл для компилятора (единица A требует компиляции блока B, который требует компиляции блока A, который требует компиляции блока B и т.д.).

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

Ответ 4

Существует много способов избежать круговых ссылок.

  • Делегаты. Слишком часто объект выполняет какой-то код, который должен выполняться в событии, а не сам объект. Является ли это тем, что программист, работающий над проектом, был слишком коротким во времени (разве мы не всегда?), Не хватало опыта/знаний или просто ленился, некоторый код вроде этого в конечном итоге оказался в приложениях. Реальный мир: компонент TCPSocket, который непосредственно обновляет некоторый визуальный компонент в приложении MainForm вместо того, чтобы иметь основную форму, регистрирует процедуру "OnTCPActivity" на компоненте.

  • Абстрактные классы/интерфейсы. Использование любого из них позволяет удалить прямую зависимость между многими единицами. Абстрактный класс или интерфейс можно объявить самостоятельно в своем собственном блоке, максимально ограничивая зависимость. Пример: наше приложение имеет форму отладки. Он использует почти в целом приложение, поскольку оно отображает информацию из разных областей приложения. Хуже того, каждая форма, которая позволяет отображать форму отладки, также в конечном итоге потребует от всех отладочных форм всех единиц. Лучшим подходом было бы иметь отладочную форму, которая по существу пуста, но имеет возможность регистрировать "DebugFrames".

    TDebugFrm.RegisterDebugFrame(Frame: TDebugFrame);

    Таким образом, TDebugFrm не имеет собственных зависимостей (кроме класса TDebugFrame). Любой блок, который должен отображать форму отладки, может сделать это, не рискуя добавить слишком много зависимостей.

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

Ответ 6

Modelmaker Code Explorer имеет действительно хороший мастер для перечисления всех видов использования, включая циклы.

Это требует, чтобы ваш проект компилировался.

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

В DelphiLive'09 я сделал сеанс под названием "Умный код" с базами данных и контрольными данными, который содержит довольно много советов по хорошему дизайну ( не ограничиваясь приложениями БД).

- Йерун

Ответ 7

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

У меня есть два класса в двух единицах: TMap и TTile.

TMap содержит карту и отображает ее с использованием изометрической плитки (TTile).

Я хотел, чтобы указатель в TTile указывал на карту. Карта - это свойство класса TTile.

Класс Var FoMap: TObject;

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

Вот, как я обойдусь.

В TTile я объявляю, что карта является TObject и перемещает модуль Map в предложении Uses раздела "Реализация".

Таким образом, я могу использовать карту, но каждый раз нужно бросать ее в TMap для доступа к ее свойствам.

Могу ли я сделать лучше? Если бы я мог использовать функцию геттера, чтобы напечатать его. Но мне нужно будет переместить карту Uses в разделе Interface. Итак, вернемся к квадрату.

В разделе "Реализация" я объявил функцию геттера, которая не является частью моего класса. Простая функция.

Реализация

Использует карту;

Карта функций: TMap; Начать   Результат: = TMap (TTile.Map); End;

Круто, подумал я. Теперь, каждый раз, когда мне нужно вызвать свойство моей Карты, я просто использую Map.MyProperty.

Ой! Скомпилировался!:) Не работает ожидаемый путь. Компилятор использует свойство Map для TTile, а не мою функцию.

Итак, я переименовал свою функцию в aMap. Но моя Муза говорила со мной. Нееет! Переименуйте свойство класса в aMap... Теперь я могу использовать Map так, как я его намеревался.

Map.Size; Этот вызов вызывает мою маленькую функцию, которая определяет тип aMap как TMap;

Патрик Форест

Ответ 8

Я дал предыдущий ответ, но после некоторых размышлений и царапин я нашел лучший способ решить круговую справочную проблему. Здесь мой первый блок, которому нужен указатель на объект TB, определяет в блоке B.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, b, StdCtrls;

type

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }

  public
    { Public declarations }
    FoB: TB;
  end;

var
  Form1: TForm1;



implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  FoB := TB.Create(Self);
  showmessage(FoB.owner.name);
end;

end.

Здесь код блока B, где TB имеет указатель на TForm1.

unit B;

interface

  Uses
    dialogs, Forms;

  type
    TForm1 = class(TForm);

    TB = class
     private
       FaOwner: TForm1;
     public
       constructor Create(aOwner: TForm);
       property owner: TForm1 read FaOwner;
    end;

implementation
  uses unit1;

  Constructor TB.create(aOwner: TForm);
  Begin
    FaOwner := TForm1(aOwner);

    FaOwner.Left := 500;
  End;//Constructor
end.

И вот почему он компилируется. Первый блок B объявляет об использовании Unit1 в разделе реализации. Немедленно разрешить круговой эталонный блок между Unit1 и Unit B.

Но чтобы позволить Delphi скомпилировать, мне нужно дать ему что-то, чтобы разжечь объявление FaOwner: TForm1. Итак, я добавляю имя класса-заглушки TForm1, которые соответствуют объявлению TForm1 в Unit1. Затем, когда пришло время вызвать конструктор, TForm1 может передать сам, имеет параметр. В коде конструктора мне нужно придать параметру aOwner значение Unit1.TForm1. И вуаля, FaOwner, его набор, чтобы указать на мою форму.

Теперь, если класс TB должен использовать FaOwner внутренне, мне не нужно его выводить каждый раз к Unit1.TForm1, потому что оба объявления одинаковы. Обратите внимание, что вы можете установить для объявления конструктором значение

Constructor TB.create(aOwner: TForm1); но когда TForm1 вызовет конструктор, а сам pass имеет параметр, вам нужно будет придать тип b.TForm1. В противном случае Delphi выдаст ошибку, сообщающую, что оба TForm1 несовместимы. Поэтому каждый раз, когда вы вызываете TB.constructor, вам нужно прибегнуть к соответствующему TForm1. Первое решение, использующее общего предка, лучше. Напишите примерный раз и забудьте о нем.

После того, как я опубликовал его, я понял, что сделал ошибку, сказав, что оба TForm1 были идентичны. Они не являются Unit1.TForm1 имеет компоненты и методы, которые неизвестны B.TForm1. Длительный туберкулез не должен использовать их или просто нужно использовать общность, данную TForm, и вы в порядке. Если вам нужно вызывать что-то особенное для UNit1.TForm1 из TB, вам нужно будет придать ему тип Unit1.TForm1.

Я пробовал и тестировал его с помощью Delphi 2010, и он скомпилировался и работал.

Надеюсь, что это поможет и избавит вас от головной боли.