/*
	Michał Plutecki G1SIS, 27.03.2008

	Wzorzec projektowy dekorator (decorator) - prezentacja

	Mechanizm działania:
		Opakowywuje 

	Zastosowanie:
	>	Rozszerza funkcjonalność klasy, zapobiegając tworzeniu 
		dużej liczby klas z kombinacjami różnych funkcjonalności.
	>	Jako alternatywa dla dziedziczenia, pozwala na 
		rozszerzanie klasy podczas działania aplikacji.
	>	W szczególności może zostać użyty do kontroli dostępu 
		do klasy poprzez opakowanie jej w dekorator kontrolii 
		dostępu.
*/

/*
	Zastosowane biblioteki
*/
#include <iostream>
#include <string>

/*
	Abstrakcyjna klasa bazowa stanowiąca interfejs dla głównej 
	klasy programu oraz jej dekoratora.
	Dostarcza dwie czysto wirtualne metody.
*/
class ICoffee {
public:
	ICoffee() { }
	virtual ~ICoffee() { }

	virtual float getPrice() = 0;
	virtual void getDescription() = 0;
};

/*
	Główna klasa programu, która może zostać użyta np. 
	do oprogramowania automatu do kawy :]. Klasa dziedziczy 
	po abstrakcyjnej klasie bazowej ICoffee i realizuje 
	jej metody wirtualne.
*/
class Coffee : public ICoffee {
public:
	//Wypełnia pola klasy Coffee, definiując ją.
	Coffee() {
		price_ = 8.0;
		description_ = "A coffee";
	}
	virtual ~Coffee() { }
	
	//Metoda zwraca aktualną cenę kawy.
	float getPrice() {
		return price_;
	}

	//Metoda zwraca opis kawy.
	void getDescription() {
		std::cout << description_;
	}

private:
	//Pola klasy Coffee opisujące kawę, dostępne 
	//tylko przez powyższe metody.
	float price_;
	std::string description_;
};

/*
	Klasa dekoratora, dziedziczy po abstrakcyjnej klasie 
	bazowej ICoffee.
*/
class CoffeeDecorator : public ICoffee {
public:
	//Konstruktor tworzy relację między klasą dekoratora 
	//i główną klasą programu (Coffee).
	CoffeeDecorator(ICoffee *c) {
		coffee = c;
	}
	//Konieczne wywołanie destruktora w klasie bazowej.
	virtual ~CoffeeDecorator() { }
	
	//Wywołanie metody getDescription() dekoratora. Umożliwia 
	//wywołanie skorelowanej metody głównej klasy programu
	//(Coffee).
	void getDescription() {
		coffee->getDescription();
	}
	
	//Wywołanie metody getPrice() dekoratora. Umożliwia
	//wywołanie skorelowanej metody głównej klasy aplikacji
	//(Coffee).
	float getPrice() {
		return coffee->getPrice();
	}

private:
	//Wska?nik na instancję abstrakcyjnej klasy ICoffee.
	//Umożliwia odwołanie się do głównej klasy aplikacji
	//(Coffee).
	ICoffee *coffee;
};

/*
	Klasa pierwszego dekoratora. Dodaje mleko do kawy.
*/
class MilkCoffeeDecorator : public CoffeeDecorator {
public:
	//Konstruktor korzysta z konstruktora klasy bazowej.
	MilkCoffeeDecorator(ICoffee *c) : CoffeeDecorator(c) {}
	virtual ~MilkCoffeeDecorator() {}

	void getDescription() {
		//Zapewnia funkcjonalność klasy bazowej.
		CoffeeDecorator::getDescription();
		//Dodaje nową funkcjonalność, rozszerzając
		//opis o mleko do kawy.
		std::cout << " with milk";
	}

	float getPrice() {
		//Zapewnia funkcjonalność klasy bazowej dodając
		//funkcjonalność własną - dodanie ceny mleka do kawy.
		return CoffeeDecorator::getPrice() + 1.0;
	}
};

/*
	Klasa drugiego dekoratora. Dodaje cukier do kawy.
*/
class SugarCoffeeDecorator : public CoffeeDecorator {
public:
	//Konstruktor korzysta z konstruktora klasy bazowej.
	SugarCoffeeDecorator(ICoffee *c) : CoffeeDecorator(c) {}
	virtual ~SugarCoffeeDecorator() { }

	void getDescription() {
		//Zapewnia funkcjonalność klasy bazowej.
		CoffeeDecorator::getDescription();
		//Dodaje nową funkcjonalność, rozszerzając
		//opis o cukier do kawy.
		std::cout << " with sugar";
	}

	float getPrice() {
		//Zapewnia funkcjonalność klasy bazowej dodając
		//funkcjonalność własną - dodanie ceny cukru do kawy.
		return CoffeeDecorator::getPrice() + 0.6;
	}
};

int main(){
	//Użytkownik tworzy instancję klasy Coffee (new Coffee), korzystając z interfejsu ICoffee.
	//Dodatkowo opakowywuje klase Coffee najpierw klasą dekoratora MilkCoffeeDecorator a następnie
	//dwukrotnie klasą dekoratora SugarCoffeeDecorator.
	ICoffee *aCoffee = new SugarCoffeeDecorator(new SugarCoffeeDecorator(new MilkCoffeeDecorator(new Coffee)));

	//Wyświetlenie opisu sporządzonej kawy.
	std::cout << "What we have got is: ";
	aCoffee->getDescription();
	std::cout << "." << std::endl;

	//Wyświetlenie ceny sporządzonej kawy.
	std::cout << "The price of the coffee is: ";
	std::cout << aCoffee->getPrice();
	std::cout << " PLN" << std::endl;

	/*	Wynik:
	What we have got is: A coffee with milk with sugar with sugar.
	The price of the coffee is: 10.2 PLN
	*/

	//Usunięcie obiektu testowego.
	delete aCoffee;

	return 0;
}
