// Paweł Pawliński E1ISI // // // //////////////////////////////////////////////////////////////// // // Wzorzec Non-virtual Interface (NVI) // //////////////////////////////////////////////////////////////// // Wzorzec NVI pozwala na oddzielenie interfejsu od szczegółów implementacji, // które można modyfikować dzięki dziedziczeniu i polimorfizmowi. // Główny pomysł opiera się na unikaniu deklarowania funkcji wirtualnych // jako publicznych. #include <string> #include <vector> #include <exception> // Przykładowa klasa bazowa CommInterface, implementująca NVI: class CommInterface { public: // Wszystkie metody publiczne nie są wirtualne, dzięki czemu zawsze // będą wywoływane metody klasy CommInterface. void shutdown () throw (std::exception) { // doShutdown to prywatna metoda czysto wirtualna. doShutdown (); // Dzięki temu, że nasz interfejs jest niewirtualny, możemy // zagwarantować pewną funkcjonalność i jednocześnie nie // wymuszać na autorze klasy pochodnej jej implementacji, np. // możemy zagwarantować spełnienie pewnych warunków: if (not queue.empty()) throw(std::exception()); } // Wzorzec NVI jest silnie związany z wzorcem metody szablonu // (template method pattern). W skrócie sprowadza się on do // umieszczenia w metodzie klasy bazowej schematu algorytmu, którego // kolejne kroki są wywołaniami prywatnych funkcji wirtualnych. // Prostym przykładem może być poniższe zapytanie: std::string& query (std::string &q) { validateInput(q); // krok 1 send(q); // krok 2 std::string& results = recieve(); // krok 3 validateOutput(results); // krok 4 return results; } // Destruktory są wyjątkiem - muszą być zadeklarowane jako wirtualne // (konieczne do poprawnego niszczenia obiektów klas pochodnych) oraz // posiadać dostęp publiczny. virtual ~CommInterface () { // W przypadku klasy CommInterface brak operacji. } protected: // Przykładowa kolejka wychodząca. std::vector<std::string> queue; private: // W tym przykładzie wszystkie funkcje wirtualne z wyjątkiem // destruktorów zadeklarowane są jako nie-publiczne. // Poniżej znajdują się przykładowe "prymitywne" funkcje, // reprezentujące szczegóły implementacji, o które klasa bazowa // pozostawia do implementacji klasom pochodnym. // Wysłanie danych z kolejki i zamknięcie połączenia virtual void doShutdown () throw () = 0; // Wysłanie danych z kolejki virtual void send (std::string &) = 0; // Odebranie danych virtual std::string& recieve () = 0; protected: // Zadeklarowanie metod jako protected pozwala klasom pochodnym // skorzystanie z gotowych implementacji w klasie bazowej, co może // być bardzo przydatne np. przy serializacji. // Walidacja wyjścia od zdalnego serwera virtual void validateOutput (std::string s) throw (std::exception) { if (s.size() < 1024) throw (std::exception()); } // Walidacja wejścia od użytkownika virtual void validateInput (std::string s) throw (std::exception) { if (s.size() == 0) throw (std::exception()); } }; // Przykładowa konkretne klasa pochodna, implementująca funkcje czysto // wirtualne. class CommViaTLS : public CommInterface { public: // W sekcji publicznej nie ma potrzeby implementowania metod klasy // bazowej. private: // Przykładowe implementacje. virtual void doShutdown () throw () { while (!queue.empty()) { send(queue.back()); queue.pop_back(); } // Tu powinno nastąpić np. zamknięcie sesji TLS. // close() ... } virtual void send (std::string &) { // Tu powinno nastąpić wysłanie danych. // write(...) queue.pop_back(); } virtual std::string& recieve () { // Tu powinno znajdować się czytanie z deskryptora // read(...) } }; // W innych klasach możemy implementować pozostałe funkcje wirtualne - fakt, że // niektóre nie są czysto wirtualne, sygnalizuje domyślną implementację, którą // można zmodyfikować. class CommViaDBUS : public CommInterface { private: virtual void doShutdown () throw () { // ... // miejsce na konkretną implementację } virtual void send (std::string &) { // ... // miejsce na konkretną implementację } virtual std::string& recieve () { // ... // miejsce na konkretną implementację } virtual void validateInput (std::string s) throw (std::exception) { CommInterface::validateInput(s); // ... // Tu powinna nastąpić dodatkowa walidacja } virtual void validateOutput (std::string s) throw (std::exception) { CommInterface::validateOutput(s); // ... // Tu powinna nastąpić dodatkowa walidacja } };