Dobrym przykładem jest procedura sortująca łańcuchy znaków; Służyć ma do posortowania podanej macierzy w sposób dokładnie określony przez nas:
type
TStringsArray = array of String;
type
TCompareResult = (crLess, crEqual, crGreater);
TCompareFunc = function(const AFirst, ASecond: String): TCompareResult;
function CompareAscending(const AFirst, ASecond: String): TCompareResult;
begin
// zwrócenie wartości dla sortowania komórek rosnąco
end;
function CompareDescending(const AFirst, ASecond: String): TCompareResult;
begin
// zwrócenie wartości dla sortowania komórek malejąco
end;
function CompareRandom(const AFirst, ASecond: String): TCompareResult;
begin
// zwrócenie wartości dla mieszania komórek
end;
procedure SortStringsArray(var AStrings: TStringsArray; ACompareFunc: TCompareFunc);
begin
// główna procedura sortująca macierz
end;
Przykład prosty, ale ma swoje istniejące odpowiedniki;
I teraz tak, w zależności od tego jak chcemy posortować macierz, podajemy odpowiednią funkcję porównującą elementy i zwracającą odpowiednią wartość, określającą rezultat porównania; Dzięki temu mamy jeden mechanizm sortowania, implementujący np. sortowanie szybkie, a to jaka będzie końcowa postać macierzy zależy tylko i wyłącznie od podanej funkcji porównującej elementy;
____Następnym przykładem jest znana biblioteka do tworzenia gier pod FPC, czyli ZenGL; Biblioteka ta posiada odpowiedni mechanizm, implementujący główną pętlę gry; W tej pętli używane są procedury do malowania po ekranie, obsługę inputu czy logiki, jednak nie są one nigdzie zdefiniowane; Nie jest z góry określone, jaki kod będzie wykonywany w każdej klatce gry np. do malowania ekranu;
Aby główna pętla gry używała naszego kodu, istnieje specjalna procedura, służąca do rejestrowania właśnie tych naszych procedur do obsługi gry - podajemy jej wskaźnik na procedurę oraz typ procedury (do malowania po ekranie, obsługi inputu itd.); Dzięki temu biblioteka wywołuje nasze procedury, które implementują naszą grę; Główny mechanizm gry jest jeden i niezmienny, ale wykonywany przez niego kod jest nasz;
Gdyby nie przekazywanie wskaźników na procedury, trzeba by zmodyfikować mechanizmy biblioteki ZenGL, aby główna pętla gry używała naszego kodu; A tak to podajemy bibliotece nasze fragmenty, a główna pętla wywołuje je w odpowiednich sytuacjach;
____Kolejny przykład to przechowywanie wskaźnika na aktualnie używaną procedurę/funkcję w celu uniknięcia skomplikowanych instrukcji warunkowych; Pakujemy wskaźnik na odpowiednią procedurę/funkcję do zmiennej i wywołujemy odpowiedni algorytm, bez konieczności rozróżniania sytuacji; Sam korzystam z takiego mechanizmu w swojej bibliotece do obsługi plików TreeStructInfo, w klasie ładującej drzewo do pamięci oraz zapisującej drzewo z pamięci np. do pliku;
To czy dodać element referencjonowany do listy pomocniczej czy wstawić w określone jej miejsce, zależy od metody trzymanej jako wskaźnik w polu FStoreRefElement (typu TStoreRefElementProc); Podczas przetwarzania głównego ciała drzewa, pole to przechowuje wskaźnik na metodę AddRefElement, która każdy nowy element dodaje na koniec listy; Natomiast podczas przetwarzania części referencjonowanej, pole trzyma wskazanie na metodę InsertRefElement, wstawiającą do listy nowo napotkany element referencjonowany pod zadany indeks; Ten mechanizm jest nieco bardziej skomplikowany, dlatego też opisuję go ogólnie, bez wielu szczegółów;
____Innym przykładem są standardowe zdarzenia - pole trzyma wskazanie na odpowiednią "metodę proceduralną", która zostanie wywołana przez obiekt komponentu w specyficznych warunkach; Jak powszechnie wiadomo, jedno zdarzenie można podłączyć pod wiele komponentów - nie trzeba dla każdego komponentu pisać tego samego kodu; Dzięki temu, kod może być jeden, krótki oraz zgodny z regułami KISS i DRY;
____Potrzeb wykorzystywania takiej techniki jest mnóstwo - wystarczy nieco wyobraźni :]