Lazarus IDE для аналитика. Приемы работы в современном Free Pascal — 1
Немного о проектировании приложений
Понятно, что проектирование программного обеспечения это достаточно важный этап разработки, на котором необходимо продумать архитектуру будущего приложения, выбрать, например шаблон проектирования, продумать модель данных, типы и схемы базы данных, методы работы с данными, возможные варианты реализации API, варианты внешнего вида приложения и т.п. В материалах ниже будут приведена информация которая призвана помочь сделать такую работу более правильной.
Одним из шаблонов, о котором хочется упомянуть является MVC — это шаблон программирования, который позволяет разделить логику приложения на три части:
Model (модель) — получает данные от контроллера, выполняет необходимые операции и передаёт их в вид.
View (вид или представление) — получает данные от модели и выводит их для пользователя.
Controller (контроллер) — обрабатывает действия пользователя, проверяет полученные данные и передаёт их модели.
Такой подход позволяет разделить код на логические блоки и упростить его поддержку и развитие в будущем. Кроме того, это позволит быстро создавать различные реализации интерфейса пользователя, например обеспечивать параллельную работу как десктопного, так и WEB интерфейса, поскольку логика работы с данными не завязана на интерфейс программы.
По реализации MVC подхода в среде Delphi существует отличный цикл статей на habr.com, с которым рекомендуется ознакомиться:
MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 1. Галочка
MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 2. Списки
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» будут приведены приемы работы со структурами и классами.