Rozdział 21
Reguły logiki aplikacji w aplikacjach
Delphi
W rozdziale tym omawiamy aplikacyjną stronę udanej implementacji reguł logiki
aplikacji. Jak to już powiedziałem w rozdziale o regułach logiki aplikacji
bazujących na serwerze, w moim głębokim przekonaniu reguły te powinny być -
jeśli to tylko możliwe - implementowane po serwerowej stronie aplikacji. Jednak
w realnych aplikacjach nieuniknione będzie uzupełnianie reguł logiki aplikacji
zrealizowanych po stronie serwera regułami osadzonymi w oprogramowaniu
pośredniczącym lub klienta. Chciałbym wierzyć, że pewnego dnia między
serwerami baz danych, produktami middleware i narzędziami do tworzenia
oprogramowania zaistnieje taki synergizm, iż problem ten zniknie całkowicie
i programista przestanie zajmować się tym, gdzie jego reguły są
zaimplementowane, a skoncentruje się na tym, jak są one zaimplementowane.
Wśród programistów z długim stażem występuje skłonność do umieszczania
znacznej części reguł logiki aplikacji w
samej aplikacji. Świat narzędzi
programistycznych jest dla nich terytorium już „oswojonym”, natomiast
platformom DBMS tradycyjnie brakowało owych wyszukanych narzędzi do
wdrażania reguł logiki aplikacji, bez których aplikacje na wysokim poziomie nie
mogą się obejść.
Abstrahując jednak od wszelkich skłonności, przeniesienie reguł logiki aplikacji
hurtem do niej samej nie jest - jak już powiedziałem - żadnym rozwiązaniem.
Środowisko programistów powinno zmusić producentów DBMS i oprogramo-
wania pośredniczącego do stworzenia wszechstronnych interfejsów dla reguł
logiki aplikacji - czyli takich interfejsów, jakich potrzebują profesjonalne
aplikacje. Odpowiedzią nie może być zwykle „machnięcie ręką” i wyproduko-
wanie banalnego rozwiązania, bazującego na pośledniej technologii. Najlepszym
podejściem byłoby skłonienie producentów do tego, by wyszli na przeciw
potrzebom swych klientów - by dostarczyli takiego wsparcia reguł logiki aplikacji,
jakiego użyć można w prawdziwych aplikacjach. Zanim to nie nastąpi, systemy
klient/serwer będą nadal równie nieodporne na naruszenia reguł logiki aplikacji, co
ich niegdysiejsi plikowi odpowiednicy.
Ku chwale producentów DBMS i oprogramowania pośredniczącego przyznać
trzeba jednak, że w korygowaniu swych strategii reguł logiki aplikacji poczynili
ostatnio spore postępy. W przeszłości większość implementacji reguł logiki
628
Część IV
aplikacji bazujących na serwerze charakteryzowała się całkowitą „hermetycz-
nością” - były bezpieczne - lecz dla programisty „nieprzyjazne”. Programiści mieli
na przykład problemy ze skłonieniem obiektów bazy danych i obiektów aplikacji
do bezkonfliktowej współpracy. Jeśli na przykład jako źródło wartości dla
wstawianego wiersza użyty został domyślny obiekt Sybase, to skąd aplikacja
mogłaby o tym wiedzieć i w jaki sposób miałaby wyświetlić na ekranie domyślne
wartości kolumn, gdyby użytkownik dodał nowy wiersz do tabeli? Programiście
pozostawał tylko wybór mniejszego zła - albo całkowite ignorowanie bazujących
na serwerze reguł logiki aplikacji i ponowne implementowanie ich w aplikacjach,
albo rezygnacja z
synchronizowania widoku aplikacji na ekranie z
jej
bazodanowymi partnerami. Żadna z tych możliwości nie była szczególnie
pociągająca i, jak zauważył kiedyś Jerry Garcia, „wybieranie mniejszego zła jest
także wybieraniem zła”.
Na szczęście w
ostatnich czasach producenci DBMS i
oprogramowania
pośredniczącego stają się coraz bardziej przyjaźni wobec użytkowników.
Rozsądne projektowanie reguł logiki aplikacji stało się obecnie łatwiejsze niż
kiedykolwiek. Jednocześnie narzędzia do projektowania aplikacji baz danych,
takie jak Delphi, stają się coraz bardziej świadome istnienia swych
„współrozmówców” w bazach danych i oprogramowaniu pośredniczącym i coraz
więcej o
nich wiedzą. W
rezultacie narzędzia programistyczne i
produkty
serwerowe zaczynają jednoczyć się w dążeniu do zaspokojenia potrzeb ludzi
W miarę, jak platformy DBMS, produkty middleware i narzędzia programistyczne
będą się uczyć ze sobą współpracować, implementowanie rozsądnej strategii reguł
logiki aplikacji będzie coraz łatwiejsze.
Jednak niezależnie od tego, w którą stronę zmierza postęp, na razie musimy
borykać się z tym, co dostępne jest tu i teraz. Aktualna sytuacja jest zaś taka, że
najprawdopodobniej będziemy musieli zaimplementować część reguł logiki
aplikacji w samej aplikacji. Jak już powiedziałem w rozdziale o regułach po
stronie serwera, podejście, jakie ja zwykle przyjmuję, jest następujące: na
platformie DBMS implementuję całą tę strategię reguł logiki aplikacji, którą mogę
tam zaimplementować,
a potem
w miarę potrzeby uzupełniam ją
w oprogramowaniu pośredniczącym (middleware) lub na kliencie. I nie ma tu
znaczenia, czy mamy do czynienia z
tabelami bazy Paradox, czy też
najprawdziwszą implementacją klient/serwer. To, czego nie mogę zaimplemento-
wać na serwerze, implementuję w middleware i w kliencie. Innymi słowy, tymi
konstrukcjami wypełniam luki w implementacji bazującej na serwerze.
Rozdział ten zajmuje się aplikacyjną stroną prawidłowej implementacji reguł
logiki aplikacji. Dokonuje przeglądu różnych sposobów planowania efektywnej
strategii reguł logiki aplikacji w naszym programie. Dowiemy się w nim na
przykład, że w Delphi są cztery główne poziomy projektowania bazujących na
oprogramowaniu reguł logiki aplikacji: poziom typu danych, poziom
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
629
komponentów, poziom
TField
i poziom
TDataSet
. Każdy z nich omówię
szczegółowo, dzięki czemu Czytelnik bez trudu zrozumie, jak je stosować we
własnych aplikacjach.
Typy reguł logiki aplikacji
Reguły logiki aplikacji dzielą się na dwa wyraźnie odmienne typy: proste reguły
logiki aplikacji i złożone reguły logiki aplikacji. Proste zapewniają prostą
integralność encji. Gwarantują one na przykład, że dane liczbowe trafią do pola
liczbowego, kolumna dat zawierać będzie tylko daty i tak dalej. Proste reguły mają
zastosowanie niezależnie od tego, czym dana aplikacja jest lub do czego baza
danych jest używana.
Reguły złożone dotyczą bardziej zaawansowanych aspektów dostępu do bazy
danych - pozwalają one zapewnić integralność referencyjną. Zwykle są przy tym
specyficzne dla danej bazy danych lub nawet konkretnej tabeli.
W książce tej terminu reguły logiki aplikacji używam w jego najszerszym sensie.
Innymi słowy wszystko, o czym mówię, dotyczy zarówno reguł prostych, jak
i złożonych. Wszystko, począwszy od prostego sprawdzenia poprawności danych,
a na skomplikowanej integralności relacyjnej skończywszy, zmieści się pod
„parasolem” reguł logiki aplikacji. Chociaż z generalnego omówienia reguł logiki
aplikacji mógłbym wyłączyć techniki sprawdzania poprawności danych, to nie
widzę
żadnych korzyści z
takiego rozwiązania. Metody stosowane
w implementacji prostych reguł logiki aplikacji są takie same, jak metody używane
do tworzenia reguł złożonych. Dlatego moje podejście polega na omówieniu
efektywnego projektowania reguł logiki aplikacji z
perspektywy ogólnej.
Niezależnie od tego, czy są to reguły proste, czy złożone, wszystkie one zasługują
na jednakową uwagę.
Delphi dostarcza kilku sposobów konstruowania prostych reguł logiki aplikacji bez
żadnego kodowania i oferuje zgrabny komplet narzędzi do konstruowania reguł
złożonych z użyciem minimalnej ilości kodu. W rozdziale tym przeanalizujemy,
z perspektywy aplikacji, oba typy reguł.
Dwie zasady dla reguł logiki aplikacji
Pierwszym poziomem implementacji reguł logiki aplikacji po stronie klienta jest
poziom typu danych. Trzeba mieć zawsze na uwadze dwie podstawowe zasady
ilustrujące dobitnie doniosłość właściwego wyboru typu danych. Jeśli będziemy
ich przestrzegać, to zawczasu unikniemy wielu typowych problemów, jakie
niekiedy gnębią mniej doświadczonych programistów.
630
Część IV
Zasada pierwsza
Pierwsza zasada ustanawiania reguł logiki aplikacji po stronie klienta brzmi:
Przede wszystkim używaj odpowiednich typów danych spośród dostępnych
w bazie.
VCL w Delphi narzuca kilka prostych, wbudowanych ograniczeń zależnych od
typu danych, jakie kolumna reprezentuje. Na przykład komponenty skojarzone
z danymi (data-aware) nie będą tolerować niepoprawnych dat w kolumnach o typie
„data”, znaków alfabetycznych w polach liczbowych, lub niewłaściwych wartości
w polach logicznych. By narzucić te ograniczenia, nie musimy tworzyć żadnego
kodu - Delphi zrobi to automatycznie. Po prostu trzeba od samego początku
używać odpowiednich typów danych.
Wśród programistów-weteranów zauważa się skłonność do nadużywania typów
łańcuchowych przy tworzeniu baz danych. Widziałem na przykład tabele
zawierające kolumny z datami, które były zdefiniowane z pomocą łańcuchowych
typów danych. Widziałem także dane liczbowe przechowywane w
polach
znakowych. Jeśli kolumna ma zawierać daty i tylko daty, to zdefiniujmy ją
korzystając z jakiegoś typu daty. Jeśli pole może zawierać tylko liczby, to
zdefiniujmy je przy pomocy numerycznego typu danych. Nie ma powodu, by
wartości numeryczne trzymać w kolumnach alfabetycznych.
Inna sytuacja, w której używanie właściwych typów danych ma dla poprawnego
projektowania reguł logiki aplikacji zasadnicze znaczenie, związana jest
z kolumnami zawierającymi wartości sekwencyjne. Choć niewątpliwie
moglibyśmy stworzyć system numerowania sekwencyjnego w kodzie Object
Pascal, to najlepiej będzie od razu użyć typów danych, które z samej swej natury
są sekwencyjne. Na przykład Sybase i Microsoft wspierają stosowanie kolumn
indentyfikacyjnych (identity columns) - specjalnych kolumn, automatycznie
inkrementowanych przez serwer. Korzystając z autoinkrementowanych typów
danych unikamy konieczności samodzielnego generowania liczb sekwencyjnych
i zapewniamy sobie, że podstawowa reguła logiki aplikacji, o która nam chodzi -
liczby w kolumnie Y tablicy X muszą być sekwencyjne - zostanie zrealizowana
praktycznie bez żadnego wysiłku z naszej strony.
Zasada druga
Druga podstawowa zasada obowiązująca przy implementowaniu bazujących na
aplikacji reguł logiki aplikacji brzmi:
Używaj takich komponentów Delphi, które najlepiej odpowiadają bazowym typom
danych.
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
631
Błędem, który w tej dziedzinie programiści robią najczęściej, jest zbyt szerokie
stosowanie komponentów do wprowadzania normalnego tekstu. Tego rodzaju
komponenty (takie jak
DBEdit
,
TEdit
i
TMaskEdit
) mogą umożliwić
wpisanie w pole wartości nieodpowiednich dla typu danej, z jaką komponent jest
skojarzony. Unikamy tego używając w
pierwszym rzędzie odpowiednich
komponentów.
Na przykład nie powinniśmy używać
DBEdit
dla pól logicznych. Pole to może
mieć tylko dwie wartości:
True
i
False
. Dlatego zamiast pola edycyjnego
użyjmy przycisku przełącznika (checkbox), przycisku opcji (radio button) lub listy
rozwijanej (drop-down list). Nie używajmy także
DBEdit
dla pól, których
wartości są tylko do odczytu; zamiast niego użyjmy komponentu DBText. Chroni
to zasoby systemowe i oszczędza kłopotu z ustawianiem właściwości
ReadOnly
komponentu. Podobnie
DBEdit
nie powinniśmy używać dla kolumn liczbowych,
które mogą mieć tylko niewielką ilość dopuszczalnych wartości - tutaj lepszy
będzie
DBRadioGroup
. Tabela 21.1 podaje, których komponentów najlepiej
używać z rozmaitymi podstawowymi typami danych.
Tabela 21.1. Typy danych w kolumnach i odpowiadające im komponenty VCL
Typ danych
Komponenty
Boolowski
DBCheckBox
,
DBRadioGroup
,
DBComboBox
,
DBListBox
Data
DBEdit
,
Calendar
,
SpinEdit
,
DataTimePicker
Liczbowy (pozwalający na
wprowadzanie dużych ilości wartości)
DBEdit
,
SpinEdit
Liczbowy (pozwalający na
wprowadzenie jedynie niewielkiej ilość
wartości)
DBRadioGroup
Łańcuch (pozwalający na wprowadzanie
dużych ilości wartości)
DBEdit
Łańcuch (pozwalający na wprowadzanie
jedynie niewielkiej ilości wartości)
DBComboBox, DBListBox
632
Część IV
WSKAZÓWKA:
Typ kontrolki, używanej dla danego pola, można skonfigurować redagując
właściwość
TControlClass
w Eksploratorze Baz Danych (Database Explorer).
Po ustawieniu tej właściwości przeciągnięcie
TField
z Edytora Pól (Fields
Editor) na formularz spowoduje utworzenie wybranego komponentu.
Podstawowa zasada polega tu na użyciu takiego komponentu, który
w maksymalnym stopniu zapewnia poprawność wprowadzanych do niego danych,
zapobiegając przy tym wprowadzaniu wartości poprawnych.
Komponenty pochodne
Drugim poziomem implementacji reguł logiki aplikacji po stronie programu jest
warstwa komponentów pochodnych (custom component). Dzięki specyficznej,
opartej na komponentach architekturze Delphi, na rynku pojawił się cały szereg
wysokiej jakości narzędzi i bibliotek umożliwiających włączanie reguł logiki
aplikacji bezpośrednio do komponentów. Robi się to zwykle przez dziedziczenie
nowych komponentów ze znajdującej się w Delphi klasy komponentów
DataSet
i wbudowywanie do komponentów potomnych wymaganej reguły logiki aplikacji.
Narzędzia te umożliwiają na ogół specyfikowanie wszystkiego, co tylko może być
potrzebne - od indywidualnych masek wprowadzania dla pól, do złożonych
zależności między tabelami. Zwłaszcza w przypadku Delphi 1.0 stanowi to bardzo
praktyczne podejście do konstruowania racjonalnych implementacji reguł logiki
aplikacji.
Ujemną stroną takiego podejścia jest fakt, że ponieważ programiści nie muszą
koniecznie używać tych właśnie komponentów, to możliwe jest budowanie
aplikacji Delphi nie respektujących wbudowanych w nie reguł logiki aplikacji. Co
więcej, ponieważ podejście „komponentowe” jest rozwiązaniem specyficznym
tylko dla Delphi, to aplikacje stworzone z użyciem innych narzędzi (z wyjątkiem
Borland C++ Builder) nie mogą wykorzystywać reguł logiki aplikacji, osadzonych
w tego rodzaju potomkach
TDataSet
.
Zatem, jeśli chodzi o ten typ narzędzi, moja rada jest taka sama, jak w ogólnym
przypadku implementacji reguł logiki aplikacji po stronie programu: nawet jeśli
zdecydujemy się na użycie jednej z nich, to nadal jak największą część naszej
strategii reguł logiki aplikacji powinniśmy implementować na samym DBMS, zaś
komponentów pochodnych używać jako sposobu na wzbogacenie implementacji.
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
633
Właściwości TField a reguły logiki aplikacji
Trzecim poziomem strategii reguł logiki aplikacji po stronie klienta jest poziom
właściwości
TField
. Reguły logiki aplikacji odnoszą się zwykle do bardziej
złożonych jednostek niż same tylko maski pól lub etykiety do wyświetlania. Mimo
to liczne właściwości klasy komponentu
TField
związane są w pewien sposób
z poprawnym implementowaniem reguł logiki aplikacji. Właściwości takie jak
atrybut maski edycyjnej w
TField
związane są przez to, że wymuszają zgodność
danych, które kolumna otrzymuje, z określonym formatem. Jest to wspomniana
wcześniej prosta odmiana reguł logiki aplikacji. Na bazie takich prostych reguł
logiki aplikacji budujemy strukturę reguł bardziej zaawansowanych, jak na
przykład te, które zapewniają integralność referencyjną pomiędzy dwiema
tabelami lub dostarczają danej kolumnie wartości domyślnych. Udana
implementacja reguł logiki aplikacji wymaga poświęcenia wielkiej uwagi
szczegółom.
Istnieją dwa sposoby implementowania prostych reguł logiki aplikacji poprzez
odpowiednie ustawianie właściwości komponentu
TField
. Pierwsza polega na
użyciu Słownika danych (Data Dictionary) Delphi i ustawieniu odpowiednich
właściwości
TField
z użyciem Zestawów atrybutów (Attribute Sets). Druga to
samodzielne ustawianie tychże właściwości
TField
w istniejących w naszej
aplikacji komponentach
DataSet
. Choć obie sprowadzają się w istocie do tego
samego - specyfikowane przez nasz Zestawy atrybutów
TField
staja się
ostatecznie częścią komponentów
TField
w naszej aplikacji - to preferowaną
opcją winno być użycie Słownika danych do definiowania naszych ustawień
TField
. Określanie właściwości
TField
z pomocą Słownika danych jest
bardziej elastyczne i
ma ogólniejsze zastosowanie, niż robienie tego za
pośrednictwem Edytora pól (Field Editor) komponentów
DataSet
. To, czego
w słowniku tym nie można zdefiniować lub czego definiować w nim nie ma sensu,
można ustawić w samych komponentach Da
t
aSet. Obie te metody omówię
szczegółowo po to, by można było w pełni ocenić korzyści związane z każdym
z tych podejść.
Importowanie informacji do Słownika
Informacje słownikową możemy do Słownika danych Delphi importować
bezpośrednio z naszej wyjściowej bazy danych. Ograniczenia, wartości domyślne,
typy danych i inne wymagane informacje z naszego serwera zostaną wtedy
przeniesione do słownika en masse. Oszczędza to kłopotu z odtwarzaniem
w słowniku więzów bazujących na serwerze i pomaga utrzymać serwerowe
i aplikacyjne reguły logiki aplikacji w stanie zsynchronizowanym. By importować
informacje z bazy danych do Słownika danych należy wykonać następującą
procedurę:
634
Część IV
1. W Eksploratorze baz danych (Database Explorer) kliknij fiszkę
Dictionary
, po
czym rozwiń gałąź
Databases
.
2. Wybierz bazę danych, która mieścić będzie informacje dla Słownika danych.
Informacje te przechowywać można albo w nowej słownikowej bazie danych,
którą rejestrujemy poprzez opcję menu
Dictionary
\
New
, lub w bazie istniejącej,
takiej jak baza danych DBDEMOS dostarczana wraz z Delphi.
3. Kliknij opcjê
Import from Database
w menu
Dictionary
Eksploratora.
4. Po wyświetleniu okna dialogowego
Import
Database
wybierz alias BDE,
odpowiadający twej bazie danych, i kliknij
OK
.
Importowanie naszej bazy danych do Słownika danych Delphi wprowadza do
niego wszystkie występujące w niej więzy i inne informacje z poziomu pól.
Wprowadza również domeny ze związanymi z nimi więzami jako Zestawy
atrybutów (Attribute Set). Dzięki opisanemu procesowi, importowanej informacji
ze słownika (oraz wszelkich dokonanych w niej zmian) można będzie użyć
w naszej aplikacji Delphi. Jeśli na przykład importowaliśmy kolumnę, dla której
w naszej bazie danych jest zdefiniowane CHECK, to więzy te pojawią się również
w aplikacjach Delphi, odwołujących się do tejże kolumny. Zatem nasza aplikacja,
zamiast dopiero reagować na odrzucenie przez ograniczenie w
serwerze
wprowadzonej już, niepoprawnej wartości, sama zapobiegać będzie
przedostawaniu się nieodpowiednich danych do serwera.
Tworzenie własnych zestawów atrybutów
Prócz importowania gotowych zestawów atrybutów z baz danych, możemy
tworzyć zestawy własne i wiązać je z kolumnami w naszych tabelach. Aby
skonstruować i wykorzystać swój własny zestaw atrybutów, należy wykonać
następujące czynności:
1. Prawym klawiszem myszy kliknij gałąź
Attribute
Sets
w karcie
Dictionary
i z menu wybierz
New
.
2. Wpisz nazwę nowego zestawu atrybutów.
3. Skonfiguruj te właściwości
TField
, które powinny być wyspecyfikowane.
4. Kliknij przycisk
Apply
Eksploratora baz danych, aby je zapamiętać.
5. Rozwiń gałąź
Database
na karcie
Dictionary
i kliknij tę kolumnę tabeli, z którą
chciałbyś powiązać swój zestaw atrybutów.
6. Na karcie
Definition
Eksploratora kliknij listę rozwijaną
Attribute
Set
i wybierz
swój nowy zestaw atrybutów.
7. Kliknij przycisk
Apply
Eksploratora, aby zapamiętać dokonane zmiany.
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
635
Przy pomocy zestawów atrybutów można ustawić wiele atrybutów
TField
.
Słownik danych Delphi stanowi bardzo wielkie ułatwienie w definiowaniu reguł
logiki aplikacji za pośrednictwem właściwości
TField
. Zaleta definiowania reguł
logiki aplikacji nie w samych komponentach DataSet, a w Słowniku danych polega
na tym, że reguły ustanowione w słowniku mogą być wykorzystywane w każdej
aplikacji Delphi, odwołującej się do odpowiednich tabel. Zapewnia to tę samą
korzyść, co podejście przyjęte przez niektórych producentów narzędzi baz danych
i polegające na wbudowywaniu reguł logiki aplikacji w komponenty pochodne -
jednak bez ujemnych stron wynikających ze stosowania obcego kodu.
Definiowanie reguł logiki aplikacji z użyciem TField
Poza wykorzystaniem Słownika danych Delphi do definiowania atrybutów
TField
, reguły logiki aplikacji można definiować również używając samych
komponentów
TField
. Delphi dodaje komponenty
TField
do klasy formularza
wówczas, gdy z menu
Edytora
pól
(Field Editor) zbioru danych (DataSet)
wybierzemy opcję
Add
fields
. Gdy w ten sposób tworzymy komponenty
TField
,
w Inspektorze obiektów (Object Inspector) odzwierciedlone zostają wszystkie
atrybuty, jakie zdefiniowaliśmy w Słowniku danych.
Aby ustawić reguły logiki aplikacji bazujące na
TField
, można skorzystać
z następującej procedury:
1. Wywołaj
Edytor pól
, dwukrotnie klikając komponent
DataSet
(
TTable
,
TQuery
lub
TStoredProc
).
2. Naciśnij CTRL+A, aby utworzyć definicję
TField
dla pól komponentu
DataSet
.
3. Kliknij po kolei w każdy z nich i sprawdź/ustaw ich właściwości w Inspektorze
obiektów.
Required
Jedną z najważniejszych spośród tych właściwości
TField
, które można ustawiać
w Inspektorze obiektów, jest właściwość
Required
. Określa ona, czy dane pole
musi mieć wartość. Przykład sytuacji, w której trzeba będzie zmienić domyślną
wartość tej właściwości mamy wtedy, gdy serwer dostarcza wartości dla klucza lub
innej kolumny
NOT NULL
. Ponieważ występowanie wartości w tej kolumnie jest
obowiązkowe na serwerze, to Delphi zakłada, że jest ono obowiązkowe i w samej
aplikacji, zatem wartością domyślną właściwości
Required
w
TField
staje się
True
. Jest to jednak zbędne, skoro wartości dla tego pola dostarcza serwer, a co
więcej - uniemożliwi poprawne działanie aplikacji. Przełączenie
Required
na
False
przerzuci obowiązek zapewnienia, że pole otrzyma swą wartość,
z powrotem na serwer, czyli tam gdzie i tak jest on wykonywany.
636
Część IV
Pola obliczane
Pola obliczane są użyteczne w wielu sytuacjach, począwszy od zwykłego
dostarczania wartości do pól, a na wykonywaniu skomplikowanych obliczeń
z użyciem pól tabeli skończywszy. Używając mechanizmu pól obliczanych Delphi
możemy wykonywać operacje matematyczne, wyszukiwanie w
tabelach,
sprawdzanie poprawności danych i tak dalej. Pola obliczane są szczególnie
przydatne w przypadku tych danych, które musimy wyświetlać, lecz których nie
musimy przechowywać.
Aby utworzyć pole obliczane, trzeba wykonać dwie podstawowe czynności:
1. Zdefiniować pole w Edytorze pól (Fields Editor) dla danego komponentu
DataSet
.
2. Przypisać polu wartości w
procedurze
OnCalcFields
komponentu
DataSet
.
A oto kroki, które trzeba wykonać przy tworzeniu pola obliczanego:
1. Wyświetl Edytor pól dla danego komponentu
DataSet
, klikając komponent
dwukrotnie.
2. Kliknij prawym klawiszem myszy listę pól i z menu kontekstowego wybierz
New
field
.
3. W oknie dialogowym
New Field
wpisz nazwę, jaką chcesz nadać nowemu polu
obliczanemu, wybierz jego typ (Type) danych i w grupie pól opcji
Field type
kliknij pole
Calculated
.
4. Zakończ definiowanie klikając
OK
. Po powrocie do Edytora pól powinniśmy
w Inspektorze obiektów Delphi zobaczyć wylistowane właściwości nowego
pola. Zauważmy, że właściwość
Calculated
ustawiona jest na
True
.
5. Po zdefiniowaniu pola można już zaprogramować zdarzenie
OnCalcFields
dla naszego
DataSet
, które nowemu polu przypisuje wartość. Oto przykład
takiego przypisania:
taORDERSOrderDateTime.Value := Now;
Powyzsza linia kodu przypisuje polu obliczanemu o nazwie
taORDERSOrder
DateTime
wartość dostępnej w Delphi funkcji
Now
, czyli bieżącą datę i czas.
Pola obliczane są szczególnie użyteczne przy wykonywaniu ustalonych obliczeń
obejmujących kolumny tabeli. Pola obliczanego moglibyśmy na przykład użyć do
zakodowania kwoty pieniędzy, jaką instytucja wypłaca za przebyta mile wtedy,
gdy pracownik używa prywatnego samochodu w sprawach służbowych. Podobnie
do zdarzenie
OnCalcFields
można by wbudować prowizję, którą firma płaci
zwykle swym handlowcom. Możliwości są tu nieograniczone. Ważną rzeczą,
o której należy przy tym pamiętać, jest konieczność maksymalnego ograniczania
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
637
ilości obliczeń, aby aplikacji nie spowolnić więcej, niż jest to bezwzględnie
konieczne.
OnValidate
Inna droga do ustanowienia reguł logiki aplikacji w
TField
wiedzie poprzez
zdarzenie
OnValidate
. Każdy
TField
ma związane ze sobą zdarzenie
OnValidate
, którego można użyć do weryfikacji jego wartości. Kod, który się
w nim znajdzie, może zrobić praktycznie wszystko - może porównać wartości, jaka
pole otrzymuje, ze stałymi, innymi polami lub rezultatami zapytania. Jeśli
stwierdzimy, że wartość, którą pole otrzymuje, jest nieprawidłowa, po prostu
generujemy wyjątek. Powoduje to wyświetlenie komunikatu o
błędzie
i uniemożliwia zatwierdzenie wiersza. Możemy tworzyć nasze własne, pochodne
typy wyjątków lub korzystać tylko z tych, których dostarcza Delphi.
OnExit
Innym powszechnie wykorzystywanym miejscem implementowania bazujących na
polach reguł logiki aplikacji jest zdarzenie
OnExit
w skojarzonych z danymi
(data-aware) komponentach, takich jak
DBEdit
. Do zdarzenie
OnExit
możemy
dołączyć kod, który będzie się wykonywać w momencie, gdy użytkownik zacznie
wychodzić z danej kontrolki. Kod ten może przypisywać wartości innym polom
(na przykład, przypisywać datę zakończenia w oparciu o dostarczoną datę
rozpoczęcia), dokonywać sprawdzenia wartości lub też uwidaczniać albo ukrywać
inne komponenty na formularzu w zależności od wprowadzonych wcześniej
wartości
Zbiory danych a reguły logiki aplikacji
Następnym poziomem, na którym konstruować można reguły logiki aplikacji
aplikacji, jest poziom zbiorów danych (DataSet). W
klasie komponentów
DataSet
(której pochodnymi są wszystkie komponenty
Table
,
Query
i
StoredProc
) występują trzy zdarzenia, które zasługują na specjalną uwagę:
OnNewRecord
,
BeforeDelete
i
BeforePost
. Odpowiadają one z grubsza
procedurom zdarzeń
INSERT
,
DELETE
I
UPDATE
, które można podłączać do
tabel bazy danych.
OnNewRecord
Zdarzenie
OnNewRecord
zachodzi za każdym razem, gdy do tabeli dodawany
jest nowy rekord. Można go wykorzystać do nadawania wartości kolumnom tabeli.
A oto próbka kodu, dostarczająca domyślnych wartości kolumnom
PetDeposit
i
LawnService
z tabeli LEASE, wykorzystywanej w sekcji „Tutorial” książki:
638
Część IV
procedure TForm1.taLEASENewRecord(DataSet: TDataSet);
begin
taLEASEPetDeposit.Value:=0;
taLEASELawnService.Value:=True;
end;
BeforeDelete
Zdarzenie
BeforeDelete
, jak sama nazwa wskazuje, poprzedza kasowanie
wiersza. Zdarzenia
BeforeDelete
można użyć do sprawdzenia poprawności
usuwania wiersza zanim zostanie ono wykonane. Na przykład można je
wykorzystać aby zapobiec usunięciu wiersza, który jest powiązany z wierszami
innej tabeli. Jeśli w trakcie obsługi tego zdarzenia wygenerujemy wyjątek,
usuwanie zostaje przerwane. Poniżej podajemy próbkę kodu używającego tabel
LEASE i PROPERTY z części drugiej tej książki:
taLEASE.IndexName:=‘Propery_Number’;
If taLEASE.FindKey([taPROPERTYProperty_Number.AsInteger]) then
raise EDatabaseError.Create(‘Property_Number is in use - delete
failed’);
Ten fragment programu zabezpiecza przed usunięciem wierszy tabeli PROPERTY
które posiadają odwołanie do wierszy w tabeli LEASE.
BeforePost
Zdarzenie
BeforePost
, jak sama nazwa wskazuje, zachodzi tuż przed
zapisaniem wiersza w jego rodzimej tabeli. Zdarzenia
BeforePost
można użyć
do sprawdzenia poprawności wartości w nowym wierszu lub zmian dokonanych
w wierszu istniejącym. Jeśli w trakcie obsługi tego zdarzenia wygenerujemy
wyjątek, zatwierdzanie zostaje przerwane. Poniżej podajemy próbkę kodu
zapewniającą, że chronologicznie data
LeaseEndDate
wypadnie zawsze po
dacie
LeaseBeginDate
:
procedure TForm1.taLEASEBeforePost(DataSet: TDataSet);
begin
If (taLEASELeaseBeginDate.AsDateTime >
➥
taLEASELeaseEndDate.AsDateTime) then
raise EDatabaseError.Create(‘Lease ending date m.
➥
ust come after its beginning date’);
end;
Istnieje kilka innych specjalnych zdarzeń DataSet, które można wykorzystać do
implementowania bazujących na programie reguł logiki aplikacji. Tabela 21.1
Rozdział 21 Reguły logiki aplikacji w aplikacjach Delphi
639
zawiera listę zdarzeń dostępnych w klasie komponentów
DataSet
, które mają
związek z projektowaniem reguł logiki aplikacji.
Tabela 21.2. Metody DataSet, które mogą się przydać przy implementowaniu
bazujących na aplikacji reguł biznesowych.
Zdarzenie Czynnik
wywołujący
AfterCancel
Zachodzi po Cancel
AfterClose
Zachodzi po zamknięciu DataSet
AfterDelete
Zachodzi po Delete
AfterEdit
Zachodzi po Edit
AfterInsert
Zachodzi po Insert lub Append
AfterOpen
Zachodzi po otwarciu DataSet
AfterPost
Zachodzi po Post
BeforeCancel
Zachodzi przed Cancel
BeforeClose
Zachodzi przed zamknięciem DataSet
BeforeEdit
Zachodzi przed Edit
BeforeInsert
Zachodzi przed Insert lub Append
BeforeOpen
Zachodzi przed otwarciem DataSet
OnCalcField
Zachodzi gdy pole obliczane potrzebuje wartości
OnDeleteError
Zachodzi gdy przy kasowaniu rekordu pojawia się
problem
OnEditError
Zachodzi gdy przy redagowaniu rekordu pojawia się
problem
OnFilterRecord
Zachodzi gdy filtrowanie jest uaktywnione i DataSet
potrzebuje wiersza