Lazarus IDE для аналитика. Приемы работы в современном Free Pascal — 1

от автора

Немного о проектировании приложений

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

Одним из шаблонов, о котором хочется упомянуть является MVC — это шаблон программирования, который позволяет разделить логику приложения на три части:

  1. Model (модель) — получает данные от контроллера, выполняет необходимые операции и передаёт их в вид.

  2. View (вид или представление) — получает данные от модели и выводит их для пользователя.

  3. Controller (контроллер) — обрабатывает действия пользователя, проверяет полученные данные и передаёт их модели.

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

По реализации MVC подхода в среде Delphi существует отличный цикл статей на habr.com, с которым рекомендуется ознакомиться:

  1. MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 1. Галочка

  2. MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 2. Списки

  3. MVC-подход к реализации пользовательского интерфейса в Delphi. Часть 3. Объекты

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

Ниже приведена информация, дополняющая и расширяющая материалы, разработанные Michalis Kamburelis — Приемы работы в современном Object Pascal, и призванная помочь более качественно организовать работу с моделью данных, разрабатываемого приложения.

Синтаксис и типы данных

Полезные директивы компилятора

{$modeswitch advancedrecords} — разрешает использование записей с методами.

{$modeswitch typehelpers} — разрешает использования хелперов для типов.

{$VarPropSetter+}

{$codePage UTF-8} — позволяет выводить в консоль кириллические символы.

Подробную информацию о директивах можно получить на странице Директивы компилятора. Руководство программиста Free Pascal (freepascal.ru)

Литералы (Строковые ресурсы)

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

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

В библиотеках Free Pascal есть готовое решение для данной темы — Строки ресурсов. Подробную информацию о работе со строками ресурсов можно получить на странице Строки ресурсов. Руководство программиста Free Pascal.

Перечисляемые типы и наборы

Перечисляемый тип в большинстве случаев используется для того, чтобы перечислить какие-то признаки или определенные варианты значения свойства поля.

 TAnchorKind = (akTop, akLeft, akRight, akBottom);

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

 TAnchors = set of TAnchorKind; 

Наличие выбранного варианта для перечисляемого типа удобно проверять в условии через оператор in

... myAncor:=akRight; Options:=[akTop, akLef]; if myAncor in Options then     Writeln('myAncor in Options - true') else     Writeln('myAncor in Options - false');  if myAncor in [akLeft,akRight] then      Writeln('myAncor in')   else      Writeln('myAncor not in'); ...

Получить числовое значение для перечисления можно с помощью ORD

i:=ORD(myAncor);

Получить текстовое значение для перечисляемого типа можно с помощью специального оператора из модуля Typinfo

uses Typinfo; ... str:=GetEnumName(TypeInfo(TAnchorKind),ORD(myAncor))); ...

Для перечисляемого типа можно использовать Хелперы, что значительно повышает удобство работы с таким типом данных, Примеры Хелперов для перечисляемого типа будут приведены далее в разделе Хелперы (Helpers)

Местные типы, переменные, константы, процедуры и функции

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

Местные типы

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

Ниже приведен пример локальной функции формирующей таблицу для сохранения в файл.

Пример процедуры вывода таблицы на чертеж
// Процедура вывода таблицы на чертеж на основе начальных данных procedure CreateTable(AInitialData:TData);  const  // Местная константа ColCount = 4;  type   // Местная структура для хранения числовых данных   TLocalStructure = record     InPortContacts: integer;     OutPortContacts: integer;     TotalContactsOnType:integer;   end;  var   // Двумерный строковый массив   Grid: array of array of string;   // Массив для хранения расчетных данных   LocalData: array of TLocalStructure;   Block: TBlock;   i, j: integer;  begin  // Инициация массива табличных данных +1 - на заголовок  SetLength(Grid,ColCount,AInitialData.Count+1);  // Инициализация массива местной структуры  SetLength(Grid,LocalData,AInitialData.Count);   // Формирование заголовка таблицы  Grid[0,0]:='№';  Grid[1,0]:='Тип';  Grid[2,0]:='Число входных портов в типе';  Grid[3,0]:='Число контактов на входной порт';     Grid[4,0]:='Число выходных портов в типе';  Grid[5,0]:='Число контактов на выходной порт';     Grid[6,0]:='Всего число контактов на тип';      // Анализ данных и заполнение строкового массива  i:=0;  for Block in AInitialData do   with LocalData[i] do      begin      // Установка кол-ва контактов в зависимости от типа порта      if Block.Type_='A' then       begin       InPortContacts:=2;       OutPortContacts:=3;       end      else       begin       InPortContacts:=4;       OutPortContacts:=5;       end;      // Выполнение необходимых расчетов      TotalContactsOnType:=Block.InPorts.Count*InPortCotact+Block.OutPorts.Count*OutPortContacts;      inc(i);      // Заполнение строки таблицы      Grid[0,i]:=i.ToString();      Grid[1,i]:=Block.Type_;      Grid[2,i]:=Block.InPorts.Count.ToString();      Grid[3,i]:=InPortCotact.ToString();         Grid[4,i]:=Block.OutPorts.Count.ToString();      Grid[5,i]:=OutPortContacts.ToString();          Grid[6,i]:=TotalContactsOnType.ToString();       end;    for i:=0 to Length(LocalData)-1 do   begin   // Здесь может производиться дополнительная обработка или вычисления для строки итогов   end;     // Здесь может располагаться код для вывода StringArray в файл end;

Местные функции

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

Ниже приведен простой пример кода реализации вложенной функции:

... function One:integer; function SubOne(a:integer):integer;     begin     result:=a*10;     end; var i,j:integer begin i:=SubOne(1); j:=SubOne(2); result:=i+j; end; ...

Условный оператор (If)

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

Оператор ORD

Можно использовать функцию ORD(Boolean). Эта функция преобразует логическое значение (True или False) в целое число, где ORD(True) равно 1, а ORD(False) равно 0. Это может быть полезно, например, при умножении числа на логическое значение. Например, если мы хотим получить значение переменной x:-1 только в случае, если условие flag истинно, мы можем использовать следующий код:

x:=1-2*ORD(flag);

В примере если flag:=true, то x=-1, а если flag:=false, то x=1.

Словарь

Для определения вариантов значений которые будут получены при том или ином значении переменной типа boolean можно использовать словарь, как в коде ниже:

const   direction:array[false..true] of integer =(-10,10); var   flag:boolean;   i:integer; begin   flag:=false;   i:=direction[flag];   i+=100;   i+=100; end; 

Функция из модуля math

Для числовых значений можно использовать функцию из модуля math,

function IfThen(val:boolean;const iftrue:integer; const iffalse:integer= 0) :integer;

Несколько условий в операторе If

В случае, когда нужно получить доступ к полю объекта, который на момент запроса может быть еще не инициирован, при простом обращении может возникнуть ошибка. Чтобы избежать остановки выполнения программы, можно использовать конструкцию Try Except, а можно просто выполнить множественную проверку, когда в операторе if объединяются несколько условий.

При объединении условий если первое условие не выполняется, то последующие условия не проверяются. В примере ниже функция two не будет вызвана. Дополнительно стоит обратить внимание на то что компилятор в первую очередь будет пытаться выполнить побитовый оператор and и в случае записи кода условия следующим образом: if one and two then — будет выходить ошибка, поэтому при объединении условий их необходимо брать в скобки.

program Project1;  function one:boolean; begin   result:=false; end;  function two:boolean; begin   result:=true; end;  begin   if (one) and (two) then   begin     writeln('and');   end; end. 

По аналогии с кодом, приведенным выше, в первом условии выполняется проверка, инициирован ли определенный объект if (Assigned(SomeObject), во втором условии, допустим проверяется, данные в каком либо поле, тогда общая запись будет иметь вид if (Assigned(SomeObject)) and (SomeObject.Value <> 0) then. Такая конструкция с несколькими проверками делает код более читаемым и простым.

Оператор (in) для условий

Для перечислений удобно проверять наличие значения в наборе через оператор in

if (TmyEnum in [meApple, meBanana]) then begin end;

Оператор in можно определить для своей структуры и использовать уже его для проверок

uses   SysUtils;  type   TDisk = class;   TScheme = specialize TObjectList;  operator in (const A: TDisk; const B: TScheme):boolean; begin   if not (B is TScheme) then     raise Exception.Create('TScheme expected');    Result := B.IndexOf(A) >= 0; end;

Оператор цикла (for)

Помимо классической организации цикла for при которой задается начальное значение, условие продолжения и шаг итерации возможно использовать цикл for item in items, который позволяет перебирать элементы коллекции (например, массива или списка) без явного указания индексов.

... var   myArray: array[1..5] of Integer;   item: Integer;  // Заполнение массива for item in myArray do begin   //операции с элементами массива end;

В случаях когда нужно обойти коллекцию с использованием индекса, начиная с последнего элемента до первого, то вместо того чтобы вычитать 1 из items.count, мы можем использовать функцию Pred, которая возвращает предыдущее значение. Пример:

var   i: Integer;  for i := Pred(items.count) downto 0 do begin   // Ваш код здесь end;

О чем еще не сказано

В части 2. статьи «Приемы работы в современном Free Pascal» будут приведены приемы работы со структурами и классами.


ссылка на оригинал статьи https://habr.com/ru/articles/867968/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *