Немного о проектировании приложений
Понятно, что проектирование программного обеспечения это достаточно важный этап разработки, на котором необходимо продумать архитектуру будущего приложения, выбрать, например шаблон проектирования, продумать модель данных, типы и схемы базы данных, методы работы с данными, возможные варианты реализации 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" будут приведены приемы работы со структурами и классами.
Комментарии (11)
vadimr
19.12.2024 10:12Дополнительно стоит обратить внимание на то что компилятор в первую очередь будет пытаться выполнить побитовый оператор
and
и в случае записи кода условия следующим образом:if one and two then
- будет выходить ошибка, поэтому при объединении условий их необходимо брать в скобки.Этот момент разъясните, пожалуйста. У вас две булевские функции, о каком побитовом операторе идёт речь? Зачем скобки?
bodyawm
19.12.2024 10:12Особенность синтаксиса Pascal.
a and b - побитовая операция
(a < 0) and (b < 0) - boolean
vadimr
19.12.2024 10:12С каких пор вдруг, и благодаря чему?
Всю жизнь в паскале and была булевская специальная форма, когда операнды типа boolean (а исходно только так и было), и побитовая операция, когда операнды целочисленные.
А самое главное, как скобки вообще могут на это влиять???
gazkom
19.12.2024 10:12приоритет and больше, чем <, поэтому a<0 and b<0 это то же, что a< (0 and b) <0, такое не компилируется ни в какой версии Паскаля, хоть 40 летней давности
vadimr
19.12.2024 10:12Это ясное дело, но мой вопрос о другом. Автор зачем-то ставит в скобки вызов булевских функций:
if (one) and (two) then
Я думаю, он как раз дезориентирован приоритетными скобочками для сравнений, о которых написали Вы.
Seenkao
19.12.2024 10:12Маловероятно, что дезориентирован.
Вероятнее всего автор хотел какие-то "сложные" сравнения делать, а по ходу переделал код. Сложные сравнения без скобок выдадут просто ошибку (по большей части).
NeoCode
19.12.2024 10:12На Паскале не писал со школьных времен, но в силу интереса к языкам программирования как таковым тема интересна. Какие существуют отличия FreePascal от Delphi на уровне языка? Есть ли какие-то интересные языковые фичи?
И еще - я как-то посмотреть на RTTI, где-то вычитал что оно в паскале реализовано весьма неплохо сделано. Скачал Lazarus, запустил - и оказалось что RTTI официальной последней версией компилятора не поддерживается. Якобы нужна (разрабатываемая в настоящее время) версия 3.3.1, а не официальная 3.2.2 от 2021 года. Что нибудь слышно о том когда будет релиз 3.3.1?
HemulGM
19.12.2024 10:12Какие существуют отличия FreePascal от Delphi на уровне языка? Есть ли какие-то интересные языковые фичи?
Синтаксис Delphi достаточно сильно продвинулся от обычного Pascal. FreePascal (FPC) тоже продвигается, плюс, он пытается быть совместимым и с новшествами в Delphi (FPC позволяет выбрать режим совместимости директивой
{$MODE DELPHI}
).Но далеко не все добавляется в FPC. Например:
Инлайн объявление переменных
var i: integer := 2024;
Выведение типов
var i := 2024; var s := 'Привет';
Анонимные функции (в FPC это на стадии открытого тестирования (в транке))
var d := function: string begin Result := 'hi there' end; var ss := d(); TTask.Run( procedure begin // работа в потоке end);
Инлайн методов классов, процедур и функций (встраивание кода без выполнения вызова метода)
TMyObj = class procedure DoSomthing; inline; end; procedure Test1; inline begin end;
Использование переменной цикла без первичного объявления и for in
for var i := 1 to 10 do ... for var Item in SomeList do ... for var Item in [1, 2, 3, 4, 5, 6] do ...
Строки в Delphi по умолчанию UTF-16, в FPC - UTF-8
Дженерики тоже работают по-разному. В fpc дженерики ближе к шаблонам.
bodyawm
Все еще люблю Pascal и смотрю на язык с теплотой. Выдал инвайт чтобы побольше писали статей о нем :)