Interfejsy programowe w C

Manna5

Załóżmy, że piszemy procedurę sumującą ciąg liczb. Jednak ten ciąg może być podawany na bieżąco z klawiatury, może być zgromadzony w tablicy lub innej strukturze danych, albo też być generowany przez jakiś algorytm. Jak poradzić sobie z takim problemem i napisać jedną, wszechstronną procedurę sumującą? Gdybyśmy programowali w Javie, oczywistą odpowiedzią byłby interfejsy - każde źródło danych byłoby klasą implementującą jeden interfejs z którego korzystalibyśmy podczas sumowania. Język C nie posiada klas ani interfejsów, jednak okazuje się, że zbudowanie takiego mechanizmu wcale nie jest trudne.

Tak naprawdę podczas przetwarzania ciągu liczb wykonujemy trzy operacje: rozpoczęcie przetwarzania, np. poprzez wyzerowanie licznika; odczyt kolejnej danej i sprawdzenie, czy jest jeszcze więcej danych. Zatem możemy stworzyć strukturę zawierającą wskaźniki do procedur odpowiadających tym operacjom.

struct Vector
{
        void (*Start) (void*);    /* Rozpoczęcie przetwarzania ciągu. */
        char (*HasMore) (void*);  /* Sprawdzenie, czy to nie koniec. */
        int (*Next) (void*);      /* Uzyskanie kolejnego elementu. */
};

Warto zauważyć również, że wszystkie te procedury przyjmują jako argument jeden wskaźnik ogólny, typu void. Ma to takie zastosowanie, że czasami jeden "sterownik" dla danego źródła danych może operować na różnych "egzemplerzach" - choćby w przypadku zwykłej tablicy musi być wiadomo, o jaką tablicę chodzi. Z kolei w przypadku generowania ciągu liczb, takiego jak ciąg Fibonacciego, danymi egzemplarza będą wszelkie dane pomocnicze takie jak ostatnio wygenerowane wyrazy ciągu. Te informacje będziemy mogli zapisać w strukturze i przesyłać wskaźnik do niej. Jeżeli nie potrzebujemy tego wskaźnika, możemy po prostu przekazać NULL.

Procedura sumująca oparta o interfejs wektora liczb koncepcyjnie nie różni się od zwykłego sumowania tablicy, ale zamiast działań na liczniku elementów wywoływane są funkcje odpowiedniego interfejsu. Tę architekturę można rozszerzać "w obie strony" - zarówno dodawać nowe strktury danych do sumowania, jak i wykonywać inne operacje na wektorach liczb, np. wyznaczanie największej wartości.

void SumVec (struct Vector *Vec, void *Inst)
{
        int Total;
        Vec->Start (Inst);
        Total = 0;
        while (Vec->HasMore (Inst))
                Total += Vec->Next (Inst);
        return Total;
}

Ostatnim krokiem będzie napisanie funkcji "opakowujących" nasze źródłó danych i zainicjalizowanie struktur-interfejsów wskaźnikami do nich. Poniżej znajduje się pełny kod programu sumującego zarówno tablicę (wypełnioną liczbami losowymi) jak i serię liczb podaną przez użytkownika.

#include <stdio.h>
#include <stdlib.h>

/* INTERFEJS WEKTORA ------------------------------------------------------- */

struct Vector
{
        void (*Start) (void*);
        char (*HasMore) (void*);
        int (*Next) (void*);
};

void SumVec (struct Vector *Vec, void *Inst)
{
        int Total;
        Vec->Start (Inst);
        Total = 0;
        while (Vec->HasMore (Inst))
                Total += Vec->Next (Inst);
        return Total;
}

/* TABLICA JAKO WEKTOR ----------------------------------------------------- */

struct ArvInstance
{
        int *Array, *Begin;
        unsigned int RemCount, Count;
};

#define InstA ((struct ArvInstance*) Inst)

void ArrStart (void *Inst)
{
        InstA->Array = InstA->Begin;
        InstA->RemCount = InstA->Count;
}

char ArrHasMore (void *Inst)
{
        return InstA->RemCount > 0;
}

int ArrNext (void *Inst)
{
        --(InstA->RemCount);
        return *(InstA->Array)++;
}

#undef InstA

const struct Vector ArrayVec = {ArrStart, ArrHasMore, ArrNext};

/* CIĄG WPROWADZANY NA BIEŻĄCO JAKO WEKTOR --------------------------------- */

struct InseqInst
{
        int GotValue;
        char WasRead;
};

#define InstS ((struct InseqInst*) Inst)

void IsqStart (void *Inst)
{
        InstS->WasRead = 0;
        puts ("Enter a sequence of numbers, terminated with 0.");
}

void IsqEnsure (void *Inst)
{
        int Tmp;
        if (! (InstS->WasRead)) {
                printf ("? ");
                scanf ("%d", &Tmp);
                InstS->WasRead = 1;
                InstS->GotValue = Tmp;
        }
}

char IsqHasMore (void *Inst)
{
        IsqEnsure (Inst);
        return InstS->GotValue != 0;
}

int IsqNext (void *Inst)
{
        IsqEnsure (Inst);
        InstS->WasRead = 0;
        return InstS->GotValue;
}

#undef InstS

const struct Vector InseqVec = {IsqStart, IsqHasMore, IsqNext};

/* TEST -------------------------------------------------------------------- */

void FillArr (int *Arr, unsigned int Len)
{
        while (Len--)
                *Arr++ = rand () % 2001 - 1000;
}

#define ARRSIZE 50

void TestArr ()
{
        struct ArvInstance ArrI;
        int Array[ARRSIZE];
        FillArr (Array, ARRSIZE);
        ArrI.Begin = Array;
        ArrI.Count = ARRSIZE;
        printf ("Sum of array: %d.\n", SumVec (&ArrayVec, &ArrI));
}

void TestInseq ()
{
        struct InseqInst IsqI;
        int Sum;
        Sum = SumVec (&InseqVec, &IsqI); 
        printf ("Sum of entered numbers: %d.\n", Sum);
}

int main ()
{
        TestArr ();
        TestInseq ();
        return 0;
}

0 komentarzy