# Dynamisch Konstruktor aufrufen



## xZise (21. Jan 2011)

Moin,
ich versuche folgendes Problem zu lösen:
Es gibt mehrere Klassen die von einer anderen abgeleitet sind. Zu jeder Klasse gibt es eine entsprechende Gegenstückklasse (wenn man so will). Und ich möchte jetzt einfach eine Instanz davon erstellen, anhand der Gegenstückklasse.

Konkret:
Es gibt mehrere Klassen die alle (mehr oder weniger tief) von Field abgeleitet sind. Die möchte ich aber nicht bearbeiten. Stattdessen habe ich einen FieldDrawer. Davon leiten sich verschiedene andere Klassen ab, die anhand eines Fields jetzt was zeichnen.

So sieht die Hauptklasse aus:

```
package org.ojim.client.gui.GameField.fielddrawer;

import java.awt.Graphics;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.ojim.logic.state.fields.Field;
import org.ojim.logic.state.fields.GoField;
import org.ojim.logic.state.fields.Jail;

/**
 * A abstract drawer to draw a field. For every field class at least one drawer
 * has to exists.
 * 
 * Any new drawer has to be set in the internal drawermap. Either by register
 * himself or by adding this class in the static block.
 * 
 * Because this is an abstract class it is not possible to create an instance of
 * this class. Therefore a {@link #getDrawer(Field)} exists, which returns a
 * drawer for a given field-class.
 * 
 * @author Fabian Neundorf.
 */
public abstract class FieldDrawer {

	/** Internal map to store which drawer belongs to which fieldclass. */
	private static final Map<Class<? extends Field>, Class<? extends FieldDrawer>> drawerMap = new HashMap<Class<? extends Field>, Class<? extends FieldDrawer>>();

	/*
	 * Add here all drawers to the internal map! Or call somewhere in the
	 * subclass the static method register. This has to be done BEFORE the first
	 * need of the class.
	 */
	static {
		drawerMap.put(GoField.class, GoFieldDrawer.class);
		drawerMap.put(Jail.class, JailDrawer.class);
	}

	protected Field field;

	public FieldDrawer(Field field) {
		this.field = field;
	}

	public abstract void drawTo(Graphics g);

	protected static void register(Class<? extends Field> fieldClass,
			Class<? extends FieldDrawer> drawerClass) {
		drawerMap.put(fieldClass, drawerClass);
	}

	/**
	 * Returns the drawer to the given field and sets the field property of this
	 * new drawer to the given field.
	 * 
	 * @param field
	 *            The field.
	 * @return The new drawer.
	 * @throws InstantiationException
	 *             if this Class represents an abstract class, an interface, an
	 *             array class, a primitive type, or void; or if the class has
	 *             no nullary constructor; or if the instantiation fails for
	 *             some other reason.
	 * @throws IllegalAccessException
	 *             if the class or its nullary constructor is not accessible.
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws SecurityException 
	 * @throws IllegalArgumentException 
	 */
	public static FieldDrawer getDrawer(Field field)
			throws InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException {
		Class<? extends FieldDrawer> drawer = drawerMap.get(field.getClass());
		if (drawer == null) {
			throw new IllegalArgumentException("The given field has no drawer.");
		}
		FieldDrawer newDrawer = drawer.getConstructor(Field.class).newInstance(field);
//		FieldDrawer newDrawer = drawer.newInstance();
//		newDrawer.setField(field);
		return newDrawer;
	}
}
```

Davon abgeleitet gibt es (wie man an der Map sieht):

```
package org.ojim.client.gui.GameField.fielddrawer;

import java.awt.Graphics;

import org.ojim.logic.state.fields.Jail;

public class JailDrawer extends FieldDrawer {
	
	public JailDrawer(Jail field) {
		super(field);
	}

	@Override
	public void drawTo(Graphics g) {
		// TODO Auto-generated method stub

	}
}
```

Jetzt kommt es aber zu einem Fehler, wenn ich folgendes mache:

```
FieldDrawer d = FieldDrawer.getDrawer(new Jail());
```


```
java.lang.NoSuchMethodException: org.ojim.client.gui.GameField.fielddrawer.JailDrawer.<init>(org.ojim.logic.state.fields.Field)
	at java.lang.Class.getConstructor0(Class.java:2723)
	at java.lang.Class.getConstructor(Class.java:1674)
	at org.ojim.client.gui.GameField.fielddrawer.FieldDrawer.getDrawer(FieldDrawer.java:107)
	at org.ojim.Test.main(Test.java:49)
```
Anscheinend ignoriert er, dass ich eine Instanz vom Typ Jail übergebe. Ändere ich den Typ im Konstruktor zu „Field“ geht es wunderbar, aber dann möchte ich eigentlich verhindern. Geht das oder muss ich den Typ ändern?

MfG
Fabian


----------



## XHelp (21. Jan 2011)

Dein Konstruktor erwartet ja 
	
	
	
	





```
Jail
```
 und du suchst nach einem Konstruktor mit 
	
	
	
	





```
Field
```


----------



## xZise (21. Jan 2011)

Moin,
ich weiß, aber wie kann ich sagen, suche nach der Klasse die ich übergeben habe? Also nicht das die Information das es vom Typ „Jail“ ist, beim übergeben als Parameter verloren geht.

MfG
Fabian


----------



## Marco13 (21. Jan 2011)

Bin noch nicht ganz wach, aber ... es KANN sein, dass du dir alle Konstruktoren holen mußt, und bei allen schauen musst, ob sie nur einen Parameter haben, und dessen Klasse mit classA.isAssignableFrom(classB) zur übergebenen Klasse passt.


----------



## xZise (21. Jan 2011)

Ich habe es jetzt nicht getestet, aber er nimmt ja an, dass der Parameter vom Typ Field ist?

Fabian


----------



## Paeddah (24. Jan 2011)

Moin!

Wie XHelp bereits erwähnte, erwartet der Konstruktor von 
	
	
	
	





```
JailDrawer
```


```
Jail
```
 als Parameter und nicht 
	
	
	
	





```
Field
```
.

Mit dem ...

-- snip --

```
FieldDrawer newDrawer = null;
    Constructor<?>[] constructors = drawer.getConstructors();
    for (Constructor<?> constructor : constructors) {
      Class<?>[] parameterTypes = constructor.getParameterTypes();
      if (parameterTypes.length == 1 && Field.class.isAssignableFrom(parameterTypes[0])) {
        newDrawer = drawer.getConstructor(parameterTypes[0]).newInstance(field);
        break;
      }
    }
    return newDrawer;
```
-- snip --

.. funktionierts. Also so, wie Marco13 es vermutet hatte.

Grüße

Päddah


----------



## me.toString (24. Jan 2011)

Auch wenn ich erstmal keinen besseren Vorschlag habe, kommt mir die Lösung von Päddah nicht so ganz geeignet vor. Es scheint zu gehen (hab's jetzt nicht ausprobiert) ... aber das Problem wird sein, wenn das Programm später erweitert wird. Wenn z.B. eine weitere Drawer-Klasse mit einem neuen Parameter-Typ dazu kommt, muss die Methode jedes mal um den neuen Parameter-Typ erweitert werden - oder sehe ich das falsch?

@xZise: du hast geschrieben: Es gibt mehrere Klassen die alle (mehr oder weniger tief) von Field abgeleitet sind. Die möchte ich aber nicht bearbeiten.
Ist das denn ein MUSS (also das mit dem "nicht bearbeiten")?


----------



## Marco13 (24. Jan 2011)

Falls ich das richtig verstanden habe: Änderungen sind eben gerade nicht notwendig, wenn die neuen Parametertypen immer von Field erben.


----------



## Paeddah (25. Jan 2011)

me.toString hat gesagt.:


> ... wenn das Programm später erweitert wird. Wenn z.B. eine weitere Drawer-Klasse mit einem neuen Parameter-Typ dazu kommt, muss die Methode jedes mal um den neuen Parameter-Typ erweitert werden ...



Ich hatte xZises Ausführung und Quellcode so verstanden, dass es jede neue Drawer-Klasse einen Konstruktor mit einem Parameter bereitstellen muss. Dieser Parameter muss eine von Field (indirekt) abgeleitete Klasse sein.

Sollte es natürlich so sein, dass hier auch beliebige Konstruktoren bedient werden sollen, so wäre der Ansatz sehr wartungsintensiv.


----------



## me.toString (25. Jan 2011)

Marco13 hat gesagt.:


> Falls ich das richtig verstanden habe: Änderungen sind eben gerade nicht notwendig, wenn die neuen Parametertypen immer von Field erben.


Dann dürfte die Fehlermeldung ja nicht kommen ... denn dann wäre ja Jail von Field abgeleitet und alles wäre OK. 

Wie wäre es denn mit einem Interface, welches von allen Parametertypen implementiert werden muss. z.B.

```
public interface FieldInterface {
   public FieldDrawer getDrawer();
}
```
Dieses interface muss von Field und Jail (und allen kommenden Parametertypen) implementiert werden. In der Klasse FieldDrawer würde dann in die Methode getDrawer so aussehen:

```
public static FieldDrawer getDrawer(FieldInterface field){
  return field.getDrawer();
}
```
Damit haben wir die Objekterzeugung an die entsprechende Fieldklasse, die sich damit eh besser auskennt, abgegeben (also Field erzeugt einen FieldDrawer, Jail erzeugt einen JailDrawer usw.) . Problem ist nur, wenn eine Drawer-Klasse keinen "eigenen Parametertyp" mitbringt und stattdessen ein "fremden Parametertyp" einfach nutzt.


----------



## Marco13 (25. Jan 2011)

Hm. Kann sein, dass (mindestens) einer von uns den jeweils anderen Falsch versteht. Ich hatte das so iterpretiert:

```
class Field {}
class SpecialField extends Field {}

class SpecialClass
{
    public SpecialClass(SpecialField f) {}
}
```

Beim Aufruf
[c]*specialClass.getConstructor(Field.class)*.newInstance(field);[/c]
wird kein Konstruktor gefunden, weil es keinen Konstruktor gibt, das nur ein "Field" erwartet. Es gibt nur einen, der ein SpecialField erwartet. Deswegen braucht man einen Befehl, mit dem man einen (beliebigen) Konstruktor finden kann, der irgendein Objekt übergeben bekommt, das von Field erbt. (Dass das konkrete Objekt dann auch zu diesem Typ passt, muss man auch überprüfen, aber auch das sollte gehen, ohne den konkreten Typ zu kennen)


----------



## me.toString (25. Jan 2011)

Marco13 hat gesagt.:


> Beim Aufruf
> [c]*specialClass.getConstructor(Field.class)*.newInstance(field);[/c]


Das Änderungs-Problem ist aber weiterhin vorhanden ... denn du brauchst ja ersteinmal eine Instanz der SpecialClass. Im ersten Post von xZise hatte er ja im static-Block jede vorhandene Field-Klasse mit der dazugehörigen Drawer-Klasse in eine Map gepackt. D.h. also, dass bei jeder Änderung der Code von FieldDrawer angepasst werden muss! 
Würde man aber, wie bei meiner Idee, die Drawer-Objekt-Erzeugung auslagern in die SpecialField-Klasse, braucht alter Code nicht mehr geändert werden ... es muss lediglich eine spezielle Field- und eine entsprechende Drawer-Klasse, die die Vorgaben des Interfaces bzw. der abstracten FieldDrawer-Klasse erfüllen,  hinzugefügt werden.


----------



## Marco13 (25. Jan 2011)

Vielleicht hatte ich das falsch verstanden. Mangels Feedback vom TO mache ich mir da jetzt mal keine weiteren Gedanken dazu.


----------

