Różnice między wybraną wersją a wersją aktualną.
— |
nvi [2008/04/15 13:07] (aktualna) 3p utworzono |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
+ | <code cpp> | ||
+ | // 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 | ||
+ | } | ||
+ | }; | ||
+ | </code> | ||