Alternative zu Funktionszeigern

Muepe32

Erfahrenes Mitglied
Hallo zusammen,

Wie ich im Netz lesen konnte ist es in Java momentan nicht möglich Funktionszeiger zu verwenden. Da ich bis vor wenigen Tagen noch praktisch nur in C# programmiert habe ist das natürlich für mich ein ziemlich grosser Schock, da mit anonymen Delegaten und anderem das Konzept der Funktionszeiger in C# sozusagen auf die Spitze getrieben wurde und ich dies auch gerne verwende.

Nun habe ich mich also gefragt was es denn für gute Alternativen gäbe. Als Beispiel habe ich folgende Situation:
Java:
class LogonClient {
	protected void handleLogonInit(byte[] data) {
	}
	
	protected void handleLogonProof(byte[] data) {
	}
	
	@Override
	protected void OnDataReceived(byte[] data) {
		if(data == null || data.length == 0)
			return;
		
		int opcode = data[0];
	}
}

Während sich hier das ganze noch relativ in Grenzen hält und über einen switch oder ein if realisierbar ist (opcode 0 -> handleLogonInit, opcode 1 -> handleLogonProof) ist es natürlich verständlich, dass in späteren Stadien meines Protokolls viel mehr hinzukommen wird. Ich würde jetzt eigentlich gerne eine assoziative Verbindung opcode -> Handlerfunktion machen.

Ich habe mal eine Variante mit anonymer Klasse gemacht, diese ist aber vom Schreibaufwand her auch nicht gerade schonend :D
Java:
class LogonClient {
	private static HashMap<Integer, IPacketFunction> mPacketFunctions;
	
	private interface IPacketFunction {
		public void execute(byte[] data, LogonClient client);
	}

	static {
		mPacketFunctions = new HashMap<Integer, IPacketFunction>();
		mPacketFunctions.put(0, new IPacketFunction() { public void execute(byte[] data, LogonClient client) { client.handleLogonInit(data); } });
	}
}

Gibt es da noch kürzere oder elegantere Versionen?

Grüsse
Muepe
 
Hab ich das richtig verstanden (zum 1. Codebeispiel):
Du willst Daten mit der Methode OnDataReceived() empfangen und je nach Inhalt des Parameters (byte[]) entweder handleLogonInit() oder handleLogonProof() aufrufen.

Dafür ist doch eine if-Abfrage bzw. ein switch-Block gedacht, warum nicht?
 
Ich denke eher das TO das so änlich wie mit Reflections machen will das er dierekt per String angeben kann welche Methode zu verweden ist ... ansonsten verstehe ich so gut wie nichts aus dem Post da ich mich weder mit C noch dem geschildertem Problem auskenne ...
 
Hallo genodeftest,

Dazu habe ich in meinem Posting auch was geschrieben:
Während sich hier das ganze noch relativ in Grenzen hält und über einen switch oder ein if realisierbar ist (opcode 0 -> handleLogonInit, opcode 1 -> handleLogonProof) ist es natürlich verständlich, dass in späteren Stadien meines Protokolls viel mehr hinzukommen wird.

Mit "viel mehr" meine ich Sachen wie 1500 verschiedene Opcodes mit entsprechend ungefähr 1000 Handlern. Da alles mit switches bzw. if-Abfragen zu machen ist viel zu aufwändig.

In C# zum Beispiel habe ich ein Enum für alle Opcodes und kann folgenden Ansatz verwenden:
C#:
enum Opcodes
{
   MSG_LOGON_INIT,
   MSG_LOGON_PROOF,
}

public class LogonClient
{
   public void OnDataReceived(byte[] data)
   {
      int opcode = data[0];
      if(mPacketHandlers.ContainsKey(opcode))
      {
         var method = mPacketHandlers[opcode];
         if(method != null)
            method.Invoke(this, data);
      }         
   }

   public void Handle_MSG_LOGON_INIT(byte[] data)
   {
   }

   public void Handle_MSG_LOGON_PROOF(byte[] data)
   {
   }

   private static Dictionary<int, MethodInfo> mPacketHandlers = new Dictionary<int, MethodInfo>();

   static LogonClient()
   {
      var values = Enum.GetValues(typeof(Opcodes));
      var handler = typeof(LogonClient);

      foreach(var value in values)
      {
         var method = "Handle_" + value.ToString();
         var info = handler.GetMethod(method);
         if(info != null)
            mPacketHandlers.Add((Opcodes)value, info);
      }
   }
}

Dadurch kann ich beliebig Opcodes hinzufügen und entfernen und habe dadurch keinen zusätzlichen Schreibaufwand mehr. Ist eine von vielen Möglichkeiten wie man das realisieren kann. Sämtliche Informationen werden erst zu Laufzeit zusammengestellt.

So etwas ähnliches möchte ich auch Java machen.

Gruss
Muepe
 
Ja ... sowas geht auch ganz gut in Java ... allerdings nur mit den sog. Reflections was eigentlich ein nicht so gutes Design ist aber für dynamischen Code das Mittel der Wahl ist.

Du kannst die via Class.getDelclearedMethodes() ein Array geben lassen das alle in dieser Klasse deklarierten Methoden enthält *wenn du alle willst verwende stattdessen Class.getMethodes()*.
Nun kannst du dir mit Methode.getName() den Methoden-Namen geben lassen ... diesen dann mit String.equals(String) vergleichen und dann fast genau so wie in deinem C-Code mittels invoke aufrufen.
Du kannst auch dierekt Methoden-Namen konstruieren ... dies meldet der Compiler aber dann mit einer UNCHECKED-WARNING ... was einfach nur aufzeigen soll das der Code leicht zum Crach gebracht werden kann wenn etwas nicht wirklich so ist wie du es zur Compile-Zeit angenommen hast. Daher ist es sicherer sich die Methoden als Array zu holen und diese abzugleichen. Dadurch hast du bei Fehlern ein etwas besseres Error-Handling weil du nicht umständlich Exceptions abfangen und parsen musst sondern dierekt mit den übergebenen Werten arbeiten kannst.

Ich habe grade leider keinen Link zu Reflections parat .. aber google sollte dir da einiges liefern.
 
Als ich das Beispiel oben schrieb habe ich auch gemerkt, dass ich ja gar keine Delegate/Funktionszeiger mehr benötige sondern alles über Reflection geregelt habe und das ja in Java auch möglich sein müsste. Mit deinen Hinweisen noch dazu habe ich das dann folgendermassen implementiert:
Java:
	private static HashMap<Integer, Method> mPacketMethods;

	static {
		mPacketMethods = new HashMap<Integer, Method>();
		Class<LogonClient> cls = LogonClient.class;
		Opcodes[] values = Opcodes.class.getEnumConstants();

		for(Opcodes opcode : values) {
			String name = "handle_" + opcode.toString();
			try {
				Method m = cls.getDeclaredMethod(name, byte[].class);
				mPacketMethods.put(opcode.getOpcode(), m);
			} catch (NoSuchMethodException e) {
				System.out.println("The opcode '" + opcode.toString() + "' has no handler!");
				continue;
			}
		}
	}
 
Du könntest das foreach noch um drehen in dem du
Java:
for(Methode method : cls.getDeclaredMethods())
schreiben würdest und dann mit
Java:
if(methode.getName().equals("handle_"+opcode.toString()))
oder sowas auf Vorhandensein der Methode prüfst. Wäre im Prinzip nichts anderes als der try-catch-Block ... würde aber etwas eleganter aussehen.
 
Zurück