Немного о проектировании приложений
Понятно, что проектирование программного обеспечения это достаточно важный этап разработки, на котором необходимо продумать архитектуру будущего приложения, выбрать, например шаблон проектирования, продумать модель данных, типы и схемы базы данных, методы работы с данными, возможные варианты реализации 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" будут приведены приемы работы со структурами и классами.
Автор: Avlakan