Getreide und das Observer Pattern



  • zu Lernzwecken wollte ich das Observer Pattern in C++ implementieren - sieht etwa so aus:

    // Vorwärtsdeklaration: Jede Klasse, die beobachtet werden kann muss von dieser Klasse erben
    class Observable;
    
    // Jeder Beobachter muss dieses Inteface implementieren
    class Observer
    {
    public:
    	// diese Methode wird aufgerufen, wenn das 
    	// Beobachtete Objekt etwas zu Melden hat
    	virtual void update(Observable * obs) = 0;
    };
    
    // Jede Klasse die man beobachten können soll erbt 
    // von dieser Basisklasse
    class Observable
    {
    public:
    	// Liste aller Beobachter
    	vector<Observer*> m_ObserverList;
    public:
    	// fügt neuen Beobachter zu diesem Objekt hinzu
    	void addObserver(Observer *pObserver)
    	{
    		m_ObserverList.push_back(pObserver);
    	}
    	// Informiert alle Beobachter wenn sich etwas getann hat
    	void notifyObservers()
    	{
    		for(vector<Observer*>::size_type i = 0;
                        i < m_ObserverList.size();
                        i++)
                          m_ObserverList[i]->update(this);
    	}
    };
    

    Nun habe ich mir gedacht es soll ein Getreidelager geben – dieses Getreide Lager wird von einem Bäcker beobachtet. Immer wenn im Getreidelager mehr oder genau 200 Getreide Einheiten sind, fängt der Bäcker an einen Kuchen zu backen. Der Bäcker wiederum wird von einem hungrigen Studenten beobachtet – immer wenn der Bäcker ein Kuchen gebacken hat kommt der Student vorbei, der sich dann auf den frischen Kuchen stürzt

    Diesen Sachverhalt habe ich wie folgt programmiert:

    /// Getreide Lager... woher kommt nur das Getreide?
    class GetreideLager : public Observable
    {
    private:
    	int m_getreide;
    public:
    	GetreideLager()
    	{
    		m_getreide = 0;
    	}
    	void changeGetreidestatus(int getreide)
    	{
    		cout<<"getreide änderung"<<endl;
    		m_getreide = getreide;
    		notifyObservers();	
    	}
    	int getGetreideStatus()
    	{
    		return m_getreide;
    	}
    	void setGetreideStatus(int getreide)
    	{
    		m_getreide = getreide;
    	}
    };
    
    /// immer wenn genug Getreide vorhanden ist macht Backt der Bäcker Kuchen
    class Bäcker : public Observer, public Observable
    {
    private:
    	int Kuchen;
    public:
    	Bäcker()
    	{
    		Kuchen = 0;
    	}
    	void update(Observable * obs)
    	{
    		GetreideLager* lager = reinterpret_cast<GetreideLager*>(obs);
    		if((lager->getGetreideStatus() > 200))
    		{
    			lager->setGetreideStatus(lager->getGetreideStatus()-200);
    		}
    		backeKuchen();
    	}		
    	void backeKuchen()
    	{
    		cout<<"Kuchen backen"<<endl;
    		Kuchen++;
    		notifyObservers();
    	}
    	void setKuchenAnzahl(int kuchen)
    	{
    		Kuchen = kuchen;
    	}
    	int getKuchenAnzahl()
    	{
    		return Kuchen;
    	}
    };
    
    class HungrigerStudent : public Observer
    {
    public:
    	void update(Observable * obs)
    	{
    		cout<<"update esser"<<endl;
    		Bäcker* bäcker = reinterpret_cast<Bäcker*>(obs);
    		if(bäcker->getKuchenAnzahl() > 0)
    		{
    			bäcker->setKuchenAnzahl(bäcker->getKuchenAnzahl() - 1);
    			cout<<"mmmh lecker kuchen"<<endl;
    		}
    	}
    };
    

    Noch ein kleines Testprogramm:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    // Vorwärtsdeklaration: Jede Klasse, die beobachtet werden kann muss von dieser Klasse erben
    class Observable;
    
    // Jeder Beobachter muss dieses Inteface implementieren
    class Observer
    {
    public:
    	// diese Methode wird aufgerufen, wenn das 
    	// Beobachtete Objekt etwas zu Melden hat
    	virtual void update(Observable * obs) = 0;
    };
    
    // Jede Klasse die man beobachten können soll erbt 
    // von dieser Basisklasse
    class Observable
    {
    public:
    	// Liste aller Beobachter
    	vector<Observer*> m_ObserverList;
    public:
    	// fügt neuen Beobachter zu diesem Objekt hinzu
    	void addObserver(Observer *pObserver)
    	{
    		m_ObserverList.push_back(pObserver);
    	}
    	// Informiert alle Beobachter wenn sich etwas getann hat
    	void notifyObservers()
    	{
    		for(vector<Observer*>::size_type i = 0;
                        i < m_ObserverList.size();
                        i++)
                          m_ObserverList[i]->update(this);
    	}
    };
    
    /// Beispiel
    
    /// Getreide Lager... woher kommt nur das Getreide?
    class GetreideLager : public Observable
    {
    private:
    	int m_getreide;
    public:
    	GetreideLager()
    	{
    		m_getreide = 0;
    	}
    	void changeGetreidestatus(int getreide)
    	{
    		cout<<"getreide änderung"<<endl;
    		m_getreide = getreide;
    		notifyObservers();	
    	}
    	int getGetreideStatus()
    	{
    		return m_getreide;
    	}
    	void setGetreideStatus(int getreide)
    	{
    		m_getreide = getreide;
    	}
    };
    
    /// immer wenn genug Getreide vorhanden ist macht Backt der Bäcker Kuchen
    class Bäcker : public Observer, public Observable
    {
    private:
    	int Kuchen;
    public:
    	Bäcker()
    	{
    		Kuchen = 0;
    	}
    	void update(Observable * obs)
    	{
    		GetreideLager* lager = reinterpret_cast<GetreideLager*>(obs);
    		if((lager->getGetreideStatus() > 200))
    		{
    			lager->setGetreideStatus(lager->getGetreideStatus()-200);
    		}
    		backeKuchen();
    	}		
    	void backeKuchen()
    	{
    		cout<<"Kuchen backen"<<endl;
    		Kuchen++;
    		notifyObservers();
    	}
    	void setKuchenAnzahl(int kuchen)
    	{
    		Kuchen = kuchen;
    	}
    	int getKuchenAnzahl()
    	{
    		return Kuchen;
    	}
    };
    
    class HungrigerStudent : public Observer
    {
    public:
    	void update(Observable * obs)
    	{
    		cout<<"update esser"<<endl;
    		Bäcker* bäcker = reinterpret_cast<Bäcker*>(obs);
    		if(bäcker->getKuchenAnzahl() > 0)
    		{
    			bäcker->setKuchenAnzahl(bäcker->getKuchenAnzahl() - 1);
    			cout<<"mmmh lecker kuchen"<<endl;
    		}
    	}
    };
    
    int main()
    {
    	GetreideLager 		Lager;
    	Bäcker				Bachmeier;
    	HungrigerStudent     Esser;
    	Lager.addObserver(&Bachmeier);
    	Bachmeier.addObserver(&Esser);
    
    	Lager.changeGetreidestatus(300);
    
    	cin.get();
    }
    

    Die Update Methode des hungrigen Studenten wird zwar aufgerufen, jedoch wird die Bäckerrei falsch übergeben: (

    Bäcker* bäcker = reinterpret_cast<Bäcker*>(obs);
    

    in bäcker steht der verweiß auf irgendwas - nur nicht auf Bachmeier...
    irgendwie finde ich den Fehler nicht



  • Du verwendest definitiv den falschen cast: reinterpret_cast interpretiert das Objekt auf Bit-Ebene neu, für deine Zwecke benötigst du static_cast<> oder dynamic_cast<> (letzteres bietet zusätzliche Fehlerkontrolle).



    1. müsste es nicht ein dynamic_cast sein? Du willst ja den das Objekt in eine andere Klasse klasse casten und nicht den Speicherbereich als andere Klasse ansehen, oder? ich würde jedenfalls dynamic nehmen

    2. Bei update()... Wenn ein Observer jetzt mehrere Observables observt (*gg*), woher weiß der observer dann, welches observable gerade geupdatet wurde? krasse grammatik 🙂



    1. Bei update()... Wenn ein Observer jetzt mehrere Observables observt (*gg*), woher weiß der observer dann, welches observable gerade geupdatet wurde? krasse grammatik 🙂

    mmh... stimmt - daran habe ich gar nicht gedacht - dachte Ein Observer beobachtet genau ein (und nicht mehrere) Observables

    die cast operatoren verstehe ich nicht (hab bisher immer C like gecastet und gehofft das der Compiler das richtige draus macht 😉

    also:
    reinterpret_cast<> mache ich, wenn ich einen Speicherplatz z. B. integer als float betrachten möchte? oder?

    Dachte jeder Zeiger wird intern als 32 Bit Wert dargestellt und hab gehofft das deshalb auch der reinterpret_cast<> funktioniert

    was ist der unterschied zwischen static_cast<> oder dynamic_cast<>?



  • zu den casts findest du bestimmt bei google oder in der FAQ was, ich habs glaub ich hier im forum gelernt 🙂

    zu update:
    vielleicht sollte die Klasse Obsrevable noch eine methode haben GetID() oder so, aus der sich schliessen lässt, welches Objekt das nun wirklich ist



  • static_cast<> sorgt dafür dass der Ausdruck in Klammern hinterher dem geforderten Typ entspricht, führt also zu undefiniertem Verhalten, wenn dem nicht so ist. dynamic_cast<> schaut in den Laufzeittypinformationen nach ob dem wirklich so ist und gibt ggfs Null zurück (bei Zeigercasts) bzw. wirft eine Ausnahme (bei Referenzcasts)



  • irgendwie komisch sagen wird der Observable hat die Adresse 0x00000004
    der Observable soll ein Bäcker sein -
    was macht der reinterpret_cast mit der Adresse (0x00000004)

    er muss sie ja irgendwie ändern, sonst hätte es doch keinen Fehler gegeben...



  • reinterpret_cast<> geht davon aus, daß ab der Adresse 0x00000004 ein Bäcker steht - und interpretiert die Daten dann entsprechend. Dein Problem ist allerdings die Mehrfachvererbung, die du verwendet hast - der Bäcker besteht hintereinander aus den Elementen von "Observer", "Observable" und seinen eigenen Daten, dadurch zeigt ein Observable*, den du darauf verweisen lässt, in die Mitte des Bäckers.

    (static_cast und dynamic_cast kennen im Gegensatz dazu die Vererbungsbeziehung und passen bei der Umwandlung die Adresse passend an)



  • 🕶 👍

    Super Beispiel, jetzt schnall ich das auch mit dem Observer.
    Danke Vertexwahn