Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Schlagwort: class

Observer Pattern (Beobachter-Muster) in Java und C#

Wie benachrichtige ich mehrere Clients möglichst ohne Zeitverlust und ohne das Rad neu zu erfinden?
Eine weitverbreitete Möglichkeit ist das Observer Pattern (und erweitert, das Subscriber Pattern).
Es ist push-basiert, der Beobachtete gibt also allen Beobachtern Bescheid, was eine Benachrichtigung an die Beobachter in kürzester Zeit ermöglicht.

ObserverPattern

Was wir in Java dafür brauchen sind folgende Klassen und Interfaces, welche wir im Code referenzieren. In C# gibt es keine äquivalenten Klassen, wir müssen etwas selber schreiben.

Java
import java.util.Observable;
import java.util.Observer;

Erstellen wir nun die Klasse, welche später beobachtet werden soll.
In C# nutzen wir EventHandler für eine asynchrone Benachrichtigung:

JavaC#
public class Beobachteter extends Observable
public class Observable
	{
		private bool changed;
		event EventHandler notifier;
		
		public void addObserver(Observer obs)
		{
			notifier += obs.update;
		}
	
		private void notifyObservers(dynamic obj)
		{
			if (this.changed) {
				if(notifier != null)
					 notifier(obj, EventArgs.Empty);
			}
			this.changed = false;
		}
		
		public void setChanged()
		{
			this.changed = true;
		}
		
		public void doIt(){
			setChanged();
			notifyObservers(32123);
		}
		
	}

Wir lassen unseren Beobachteten die Klasse Observable im Namespace java.util.* erben. (Siehe oben)
Dadurch bekommt die Klasse u.A. folgende Methode vererbt, welche wir im Folgenden nutzen:

JavaC#
public void AddObserver(){
       Beobachteter.addObserver(Observer o);
}
 Durch die eigene Implementierung, stehen uns die identischen Methoden zur Verfügung. 

Wie man auf dem Bild sieht, muss man nun die Beobachter (Observer) einem Beobachtbaren (Observable) hinzufügen, im Folgenden nenne ich diesen dann Beobachteten.
Wir erstellen verschiedene Beobachter und fügen diese nun einem Objekt der Klasse Beobachteter zu, welche – der Einfachheit halber – direkt in den Beobachtern erstellt wird.
Die Beobachter implementieren das Interface von java.util.Observer.

JavaC#
public class Beobachter implements Observer{
    public Beobachter(){
         Beobachteter observable = new Beobachteter();
         observable.addObserver(this); //fügt dem Beobachteten mich als Beobachter hinzu.
    }
}
 	public class Observer
	{
		string name;
		
		public Observer()
		{
		}
		
		public Observer(string name){
			this.name = name;
		}
	
		public void update(object message, EventArgs args){
			//Console.WriteLine(this + "received: "+message+obs);
		}
		
		public override string ToString()
		{
			return "{Observer,"+this.name+"}";
		} 
	}

Nun haben wir einen oder mehrere Beobachter und einen Beobachteten.
Wie lassen wir die Beobachter nun wissen, wenn sich etwas ändert?
Die Klasse Observable bietet hier einige passende Methoden. Ich fokussiere mich hier allerdings auf folgende zwei:

JavaC#
 setChanged(); 
 notifyObservers(message);
setChanged();  
notifyObservers(message);

Durch das dynamic keyword können wir dem EventHandler „notifier“ jegliche Daten als Parameter durchreichen, diese Implementierung vereinfacht das Handling der Daten extrem.

setChanged() ist von Nöten, da ein Beobachter auch nur benachrichtigt wird, wenn sich auch etwas geändert hat.
In Java ist es zudem sehr praktisch, dass man jede Art von Daten in der Methode

notifyObservers();

versenden kann. Mit dieser werden die Beobachter auch benachrichtigt.

Im Java Observer wird nun die Methode

update(Observable _ob, Object message)

, welche wir durch das Interface implementierten, ausgeführt.
In C# rufen der EventHandler die Observer-Methode

 update(dynamic message, EventArgs empty) 

aus.

Im Anhang befindet sich das C# Test Projekt, als .sln Solution:
ObserverPattern

[C#/.NET] – Unterschied zwischen new virtual und override

Wer mit Vererbung arbeitet, hat sicher schon mit dem Begriff override zu tun gehabt.
Was es aber auch gibt ist das Schlüsselwort new bzw. virtual .
Wo ist der Unterschied?

Erstellen wir ein Beispiel. Wir haben eine Klasse mit der Bezeichnung Engineer:

     class Engineer
    {
        protected string name;
        protected double billingRate;

        public Engineer(string name, double billingRate)
        {
            this.name = name;
            this.billingRate = billingRate;
        }

        public virtual double calcMoney(double hours) //virtual ermöglicht das überschreiben der Methode in abgeleiteten Klassen
        {
            return billingRate * hours * 2.0d; //Standardfunktion, falls nicht überschrieben
        }

    }  

Hier haben wir die virtuelle* Methode calcMoney.

virtual: Mit dem virtual-Schlüsselwort kann eine Methode, eine Eigenschaft, ein Indexer oder eine Ereignisdeklaration geändert und in einer abgeleiteten Klasse überschrieben werden.

(Quelle: MSDN)

Nun erstellen wir eine Klasse CivilEngineer (Bauingenieur) und leiten von Engineer-Klasse ab:

     class CivilEngineer : Engineer
    {
        public CivilEngineer(string name, double billingRate)
            : base(name, billingRate)
        {
            this.name = name;
            this.billingRate = billingRate;
        }

        public override double calcMoney(double hours) //Hier wird die Funktion aus der Klasse Engineer
        { //überschrieben, d.h. hier wird jetzt diese Funktion verwendet, anstatt der Basisfunktion
            return billingRate * hours * 1.5d; //Faktor auf 1.5d geändert
        }
    } 

Der Konstruktor ruft hier zuerst den Konstruktor der Basisklasse (Engineer) auf base(name, billingRate) und übergibt die Parameter der CivilEngineer-Klasse.

Nun überschreiben wir die Methode der Basisklasse und ersetzen Sie durch calcMoney der CivilEngineer Klasse.

Jetzt erstellen wir ein Objekt des Engineers und ein Objekt des CivilEngineers.

             CivilEngineer c1 = new CivilEngineer("John",15d);
            Engineer e1 = new Engineer("Keith", 10d); 

Nun erstellen wir eine Liste von Engineers (List<Engineer>) und fügen beide Objekte dieser Liste hinzu:

             List elist = new List();
            elist.Add(c1);
            elist.Add(e1); 

Nun rufen wir bei beiden Objekten die Funktion calcMoney() auf.

             e1.calcMoney(10); //Es wird die Methode Engineer.calcMoney() aufgerufen (Ist ja klar!)
            c1.calcMoney(10); // Es wird die überschriebene Methode CivilEngineer.calcMoney() aufgerufen (Soweit, so gut. Passt alles) 

Jetzt rufen wir die calcMoney()-Methode innerhalb einer foreach-Schleife in der Liste auf:

             foreach (Engineer eng in blalist) //Alle Objekte, egal ob abgeleitet oder nicht, sind vom Typ Engineer
            {
                eng.calcMoney(10);
            } 

Beim ersten Durchlauf wird jetzt die Funktion des Objektes c1 aufgerufen, da dieses zuerst zur Liste hinzugefügt wurde.
Es wird, wie erwartet die überschriebene Methode der abgeleiteten Klasse (CivilEngineer) aufgerufen (also CivilEngineer.calcMoney()), da wir diese in der Klasse Engineer überschrieben (override) haben.

Im darauffolgenden Durchlauf wird jetzt die Funktion des Objektes e1 aufgerufen:
Es wird die Methode der Klasse Engineer aufgerufen, da das Objekt vom Typ Engineer ist – also Engineer.calcMoney(10).

Dies war jetzt mit den Schlüsselwörtern virtual und override.


Jetzt kommen wir zu new:

Fügen wir eine Klasse TankEngineer hinzu, welche die Methode calcMoney() hat, aber hier mit dem new Schlüsselwort deklariert:

    class TankEngineer : Engineer
    {
        public TankEngineer(string name, double billingRate) //Konstruktor der abgeleiteten Klasse
            : base(name, billingRate) //Ruft den Konstruktor der Basisklasse auf, da es von dieser ableitet (benötigt zur Existenz)
        //gleiche Parameter!
        {
            this.name = name; //Zuweisung der Eigenschaften (vererbt)
            this.billingRate = billingRate;
        }

        public new double calcMoney(double hours) //Hier wird die Funktion aus der Klasse Engineer
        { //verdeckt (!) und NICHT überschrieben
            return billingRate * hours * 3.0d; //Faktor auf 3.0d geändert
        }
    }

Nun erstellen ein Objekt vom Typ TankEngineer und fügen es der vorigen Liste mit den zwei anderen Objekten hinzu:

 TankEngineer t1 = new TankEngineer("Robert", 20d); 
 elist.Add(t1); // Füge den TankEngineer hinzu 

Rufen wir nun die Methode calcMoney direkt über das Objekt auf,

  t1.calcMoney(10); // Ruft TankEngineer.calcMoney auf, da vom Typ TankEngineer und Basismethode verdeckt 

Wird die Methode TankEngineer.calcMoney aufgerufen, da diese in der Klasse TankEngineer existiert.

Gehen wir aber nun per foreach wieder durch die Liste (elist):

             foreach (Engineer eng in elist)
            {
                eng.calcMoney(10);
                // 3. Durchgang (t1), ruft die Engineer.calcMoney-Methode auf, da diese existent ist und durch 
                // das new-Schlüsselwort nur verdeckt wurde. 
            } 

so wird hier jetzt die Methode der Basisklasse aufgerufen, da die Liste nur Objekte vom Typ Engineer hält und die Methode nicht überschrieben, sondern nur verdeckt wurde.
D.h. es waren zur Laufzeit beide Methoden des gleichen Namens in beiden Klassen verfügbar.

Zur Veranschaulichung habe ich euch ein kleines Projekt gebastelt, in welchem ihr durch Debugging selbst sehen könnt, wie override bzw. new arbeiten: DOWNLOAD

© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.