// 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
	}
};

