Пусть требуется разработать программу, которая должна рассматривать точки на плоскости и находить среди них пару (одну), расстояние между которыми максимально.
Похоже, что это самое крупное структурное разбиение, потому что если какую то пару блоков объединить, то объединенный блок будет выполнять не одну, а две функции.
Примечание: В таком соединении иногда есть смысл. Например, ввод данных объединить с какими-то вычислениями, потому что при вводе с клавиатуры процессор почти все время свободен.
Если выделенные блоки могут применяться не только в разрабатываемой программе, но и в каких-то других программах или они должны исполняться несколько раз в программе (или и то и другое), то такие блоки обычно оформляют в виде подпрограмм. Мы так и сделаем. Описание таких вспомогательных подпрограмм обычно размещают в отдельном модуле, чтобы структура главного модуля была максимально ясной. Применение модулей имеет и другие достоинства.
А где же главный модуль и что он делает ?
Главный модуль обеспечивает интерфейс пользователя, например, на главной форме разместим переключатель для выбора вариантов получения исходных данных, переключатель для выбора варианта просмотра данных и варианта представления результатов. Полезно предусмотреть кнопку "Пуск" и кнопку для выхода (завершения работы). Конечно, что-нибудь забыто, но надеемся дополнить в ходе работы
Через некоторое время мне это не понравилось, решил, что на главной форме для вызова подпрограмм должно быть просто главное меню. И что можно добавить ввод и вывод данных с использованием файла (текстового, каждая строка - координаты точки).
Так примерно выглядит стиль разработки "сверху - вниз", т е вначале определяем крупные структурные элементы и их функции. После этого разработку каждого блока можно поручить отдельному программисту, и это ускорит создание проекта в целом. Однако, реально не хватает одного момента - нужно договориться реализовывать каждый блок в виде подпрограммы и определить заголовки (т е список параметров) для подпрограмм. Как ни странно, можно спроектировать заголовки подпрограмм уже сейчас, хотя, конечно, можно кое-что не угадать и тогда придется корректировать. Итак, приступим.
Думаю, что этап разработки заголовков подпрограмм был не лишним. Теперь
нужно выбрать стиль продолжения разработки. Можно сосредоточиться на детальной разработке всех (по очереди) или некоторых подпрограмм, а затем организовать их вызов из главной формы. А можно действовать наоборот: вначале создать главное меню и обеспечить вызовы подпрограмм (которые пока заменить заглушками), а потом заняться детальной разработкой подпрограмм, начиная с тех, которые можно проверять и отлаживать не дожидаясь готовности остальных. Отметим, что заголовки заглушек совпадают с заголовками "настоящих" подпрограмм, т е являются окончательными, меняется только тело (т е: begin -операторы- end). Для этой программы выберу 2-й стиль.
procedure InputFromKeyboard(NumPoints: integer; // число точек MinX,MaxX,MinY,MaxY: double; // допустимые мин и макс значения координат. var Coord: TCoord { массив координат точек. Тип TCoord (TCoord = array of TPointFloat) не забудьте описать глобально } ); begin showmessage('Ввод с клавиатуры'); end;Так как эта процедура должна быть доступна в модуле unMain, то ее заголовок помещаю в разделе interface модуля unPokaz (копирую заголовок). Выше его ставлю описание типа TCoord. Для того, чтобы эти описания были доступны в unMain, нужно в предложение uses этого модуля добавить unPokaz. Пока что эти описания используются только в части implementation, поэтому достаточно добавить в этой секции. Здесь же описываю переменные NPoints, MinX1, MaxX1, MinY1, MaxY1, Coord1.
unit unMain; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Menus; type TfrmMain = class(TForm) MainMenu1: TMainMenu; Vvod: TMenuItem; KeyBoard: TMenuItem; fromFile: TMenuItem; Random: TMenuItem; RG1: TRadioGroup; OD1: TOpenDialog; SD1: TSaveDialog; SaveAs: TMenuItem; Solve: TMenuItem; Quit: TMenuItem; Pokaz: TMenuItem; procedure KeyBoardClick(Sender: TObject); procedure fromFileClick(Sender: TObject); procedure RandomClick(Sender: TObject); procedure SaveAsClick(Sender: TObject); procedure QuitClick(Sender: TObject); procedure PokazClick(Sender: TObject); procedure SolveClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmMain: TfrmMain; implementation uses unPokaz; {$R *.dfm} var NPoints, NN1, NN2: integer; MinX1,MaxX1,MinY1,MaxY1: double; Coord1: TCoord; FName: string; procedure TfrmMain.KeyBoardClick(Sender: TObject); begin InputFromKeyboard(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1); RG1.Enabled := True; Solve.Enabled:= true; end; procedure TfrmMain.fromFileClick(Sender: TObject); begin if OD1.Execute then begin FName:= OD1.FileName; DataFromFile(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1, FName); end; RG1.Enabled := True; Solve.Enabled:= true; end; procedure TfrmMain.RandomClick(Sender: TObject); begin RandomData(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1); RG1.Enabled := True; Solve.Enabled:= true; end; procedure TfrmMain.SaveAsClick(Sender: TObject); begin if SD1.Execute then begin FName:= SD1.FileName; SaveData(NPoints, Coord1, FName); end; end; procedure TfrmMain.QuitClick(Sender: TObject); begin Application.Terminate; end; procedure TfrmMain.PokazClick(Sender: TObject); begin case RG1.ItemIndex of 0: ShowTabPoints(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1); 1: ShowGraphPoints(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,0,0); end {case}; end; procedure TfrmMain.SolveClick(Sender: TObject); begin CalcMax(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,NN1,NN2); ShowGraphPoints(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1,NN1,NN2); end; end. ====================================================== unit unPokaz; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TfrmPokaz = class(TForm) private { Private declarations } public { Public declarations } end; var frmPokaz: TfrmPokaz; type TPointFloat = record X,Y: single; end; TCoord = array of TPointFloat; procedure InputFromKeyboard(NumPoints: integer; // число точек MinX,MaxX,MinY,MaxY: double; // допустимые мин и макс значения координат. var Coord: TCoord { массив координат точек. Тип TCoord не забудьте описать глобально } ); procedure DataFromFile(var NumPoints: integer; var MinX,MaxX,MinY,MaxY: double; var Coord:TCoord; FName: string {имя файла с данными}); procedure RandomData(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord); procedure CalcMax(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; var N1,N2: integer {номера заданных точек}); procedure SaveData(var NumPoints: integer; var Coord: TCoord; FName: string {имя файла с данными}); procedure ShowTabPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord); procedure ShowGraphPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; N1,N2: integer); implementation {$R *.dfm} procedure InputFromKeyboard(NumPoints: integer; // число точек MinX,MaxX,MinY,MaxY: double; // допустимые мин и макс значения координат. var Coord: TCoord { массив координат точек. Тип TCoord не забудьте описать глобально } ); begin showmessage('Ввод с клавиатуры'); end; procedure DataFromFile(var NumPoints: integer; var MinX,MaxX,MinY,MaxY: double; var Coord:TCoord; FName: string {имя файла с данными}); begin showmessage('Ввод из файла'); end; procedure RandomData(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord); begin showmessage('Случайные данные'); end; procedure CalcMax(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; var N1,N2: integer {номера найденных точек}); begin showmessage('Вычисляю...'); end; procedure SaveData(var NumPoints: integer; var Coord: TCoord; FName: string {имя файла с данными}); begin showmessage('Сохраняю...'); end; procedure ShowTabPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord); begin showmessage('Показ таблицы'); end; procedure ShowGraphPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; N1,N2: integer); begin showmessage('Показ точек - график'); end; end.
procedure RandomData(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord); var ii: integer; begin // showmessage('Случайные данные'); Randomize; // инициализируется генератор случайных чисел. без этого // последовательность случайных чисел будет одинакова // при всех запусках процедуры. for ii:=0 to NumPoints-1 do begin Coord[ii].X:= MinX + (MaxX-MinX)*random; Coord[ii].Y:= MinY + (MaxY-MinY)*random; end; end;Метод обработки клика по пункту меню "Случайные данные" тоже дополнен и имеет вид:
procedure TfrmMain.RandomClick(Sender: TObject); begin // на форму frmMain добавлены TEdit - для ввода данных NPoints:= StrToInt(edCount.Text); MinX1:= StrToFloat(edminX.Text); MaxX1:= StrToFloat(edmaxX.Text); MinY1:= StrToFloat(edminY.Text); MaxY1:= StrToFloat(edmaxY.Text); if MaxX1 <= MinX1 then begin showmessage('MaxX > MinX !'); exit; end; if MaxY1 <= MinY1 then begin showmessage('MaxY > MinY !'); exit; end; SetLength(Coord1,NPoints); // выделяется память массиву Coord1 RandomData(NPoints,MinX1,MaxX1,MinY1,MaxY1,Coord1); // вызов процедуры RG1.Enabled := True; // меняем доступность некоторых компонент. Solve.Enabled:= true; edCount.Enabled:= false; edminX.Enabled:= false; edmaxX.Enabled:= false; edminY.Enabled:= false; edmaxY.Enabled:= false; end;
procedure ShowGraphPoints(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; N1,N2: integer); var ii,XX,YY: integer; ax,bx,ay,by: double; { коэффициенты линейного преобразования реальных координат в экранные: Хэкр := ax*Хреал + bx; такие, что Хэкр(Xmin) = 0; Хэкр(Xmax) = ширина формы. Yэкр := ay*Yреал + by; Yэкр(Ymin) = высота формы; Yэкр(Ymax) = 0. a,b - определяются из решения СЛАУ, записанных выше } begin // showmessage('Показ точек - график'); ax:= frmPokaz.ClientWidth/(MaxX-MinX); bx:= ax*MinX; ay:= frmPokaz.ClientHeight/(MinY - MaxY); by:= -ay*MaxY; with frmPOkaz do begin Canvas.Pen.Width:=2; Canvas.Pen.Color:= clGreen; Color:= clBlack; Canvas.FillRect(ClientRect); Show; for ii:= 0 to NumPoints - 1 do begin XX:= Round(ax*Coord[ii].X + bx); YY:= Round(ay*Coord[ii].Y + by); Canvas.Pixels[XX,YY ]:= RGB(200,200,200); Canvas.Pixels[XX+1,YY ]:= RGB(200,200,0); //чтобы усилить точку Canvas.Pixels[XX+1,YY+1 ]:= RGB(0,200,200); //чтобы усилить точку Canvas.Pixels[XX,YY+1 ]:= RGB(200,0,200); //чтобы усилить точку end; if N1<>N2 then begin XX:= Round(ax*Coord[N1].X + bx); YY:= Round(ay*Coord[N1].Y + by); Canvas.Ellipse(XX-5,YY-5,XX+5,YY+5); XX:= Round(ax*Coord[N2].X + bx); YY:= Round(ay*Coord[N2].Y + by); Canvas.Ellipse(XX-5,YY-5,XX+5,YY+5); end; end; end;
procedure CalcMax(NumPoints: integer; MinX,MaxX,MinY,MaxY: double; var Coord: TCoord; var N1,N2: integer {номера найденных точек}); var ii,jj,imax,jmax: integer; Rasst,RMax: double; begin // showmessage('Вычисляю...'); RMax:= 0; for ii:=1 to NumPoints-1 do //цикл перебирает всевозможные пары точек for jj:= ii+1 to NumPoints do // и находит максимальное расстояние begin Rasst:= sqrt(sqr(Coord[ii-1].X - Coord[jj-1].X) + sqr(Coord[ii-1].Y - Coord[jj-1].Y)); if Rasst > Rmax then begin RMax:= Rasst; imax:= ii-1; jmax:= jj-1; end; end; N1:= imax; // выходные параметры заполняю. N2:= jmax; end;