# Designproblem bei Parent/Children-Beziehungen von Klassen



## pc-world (6. Sep 2010)

Hallo,

habe folgendes (Design-)"Problem":

Um kurz zu erklären, um was es geht: Ein Java-Programm holt sich eine XML-Datei von einem HTTP-Server. In dieser ist eine Liste von Kategorien enthalten (nach Auswahl einer Kategorie im UI können passend zur Kategorie weitere Informationen angezeigt werden) die bislang ungefähr so aussah:
[XML]<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<categories>
	<category id="1" name="Test" />
	<category id="2" name="Bar" />
</categories>[/XML]

Mittlerweile ist es so, dass eine Kategorie auch Unterkategorien enthalten kann (beliebige Pfadlängen möglich):
[XML]<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<categories>
	<category id="1" name="Test">
		<category id="3" name="Foo">
			<!-- evtl. noch weitere Kategorien ... -->
		</category>
		<category id="4" name="Hunger">
		</category>
	</category>
	<category id="2" name="Bar">
	</category>
</categories>[/XML]

Als es noch keine untergeordneten Kategorien gab, hat die folgende Klasse eine Kategorie repräsentiert:
[Java]public class Category {

	private int id;
	private String name;

	public Category(int id, String name) {
		setId(id);
		setName(name);
	}


	public int getId() {
		return id;
	}

	public void setId(int id) {
		if (id < 0) {
			throw new IllegalArgumentException("param must be >=0");
		}
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		if (name == null) {
			throw new NullPointerException("param must not be null!");
		}
		this.name = name;
	}

}[/Java]

Jetzt will ich die Klasse Category so umschreiben, dass sie ihren evtl. vorhandenen Parent bzw. ihre Children mir über get-Methoden liefern kann.

Die XML wird ungefähr folgendermaßen rekursiv ausgelesen (Pseudo-Code):

```
rootelement = ...; // damit bekomme ich das Root-Element der XML-Datei (<categories>)
readChildren(rootelement);

function readChildren(element) {
	children = element.getChildren();
	// Aus dem <category>-Element eine neue Instanz von Category erstellen und evtl. zu einer Liste hinzufügen?
	for (children : child) {
		readChildren(child);
	}
	
}
```

Jede Instanz soll ihren Parent (die übergeordnete Kategorie) und ihre Children (die untergeordneten Kategorien) kennen und mir über irgendwelche get-Methoden zurückliefern können.
Jetzt fehlt mir allerdings die Idee, wie ich das ganze sinnvoll und mit gutem Code-Design umsetzen kann.
Später soll diese Kategorieliste in einem JTree dargestellt werden, möchte es aber trotzdem unabhänig von irgendwelchen Swing-Models machen.
Auch sollen die ganzen Kategorien über eine List verfügbar gemacht werden. Wäre es hier sinnvoll, dass eine Liste nur mit den Kategorien der obersten Schicht erstellt wird, und später im Code für bspw. die GUI dann so etwas wie getChildren() aufgerufen wird?

Ich habe mir auch schon überlegt, in den Konstruktor eine Variable für den Parent einzubauen, der auch null sein kann. Doch dann frage ich mich wieder, wie eine Category an ihre Children kommen soll.
Evtl. auch eine addChildren-Methode?

Das Composite Pattern passt das auch nicht ganz zu meinem Problem. Jede Kategorie soll gleichzeitig eine Kategorie als auch ein Container ("Composite") sein. Auch wenn eine Kategorie beim Einlesen der XML noch keine untergeordneten Kategorien hat, soll trotzdem noch die Möglichkeit bestehen, später Children dazufügen zu können.
Eine Kategorie sollte auch ihren Parent (die übergeordnete Kategorie) kennen.

Mir fehlt hier völlig die Idee, wie ich das einigermaßen sinnvoll lösen könnte. Habe sowas in der Art noch nicht machen müssen. Wäre nett, wenn ihr mir auf die Sprünge helfen könntet! 

Danke,
pcworld
PS: Ich weiß, der Post ist evtl. etwas durcheinander...


----------



## Marco13 (6. Sep 2010)

pc-world hat gesagt.:


> PS: Ich weiß, der Post ist evtl. etwas durcheinander...



Wenn du die Zeit, die du gebraucht hast, um das zu schreiben, genutzt hättest um.... 
(ach, lieber nicht, sonst sagst du das gleiche, aber bei dir würde der Satz dann enden mit "...eine hilfreiche Antwort zu schreiben"  )

_Später soll diese Kategorieliste in einem JTree dargestellt werden, möchte es aber trotzdem unabhänig von irgendwelchen Swing-Models machen._

Klingt vernünftig. Das TreeModel kann dann ggf. auf die "echten" Daten abbilden.


_Jede Instanz soll ihren Parent (die übergeordnete Kategorie) und ihre Children (die untergeordneten Kategorien) kennen und mir über irgendwelche get-Methoden zurückliefern können.
Jetzt fehlt mir allerdings die Idee, wie ich das ganze sinnvoll und mit gutem Code-Design umsetzen kann._

Sooo viele Alternativen gibt's da IMHO nicht. Wenn jede Instanz ihr Parent kennen soll, kann man das Parent entweder im Konstruktor übergeben oder nachträglich setzen.

_Doch dann frage ich mich wieder, wie eine Category an ihre Children kommen soll.
*Evtl. auch eine addChildren-Methode?*

Auch wenn eine Kategorie beim Einlesen der XML noch keine untergeordneten Kategorien hat, soll trotzdem noch die Möglichkeit bestehen, *später Children dazufügen zu können.*
_

Die beiden Teile lasse ich mal so unkommentiert hervorgehoben stehen 

Aber Je nachdem, worum es da geht, könnte es schon sinnvoll sein, die eigentlichen Kategorie-Informationen und die Strukturinformationen irgendwie zu trennen. Theoretisch (!!! nur zur Verdeutlichung, dass es "immer auch anders" geht !!!) könnte man ja auch sowas machen wie


```
class Category { /* wie du sie schon hast */ }

class CategoryStructure
{
    Category getParent(Category c) { ... }
    List<Category> getChildren(Category c) { ... }
}
```
die die "Struktur"/Hierarchie intern einfach mit ein paar Maps speichert. (Die ID's machen es einem da ja leicht).

Ob das für dich passen würde.... :bahnhof: hm


----------



## dzim (6. Sep 2010)

Tach,

ich weiß nicht, ob dir das schmecken wird, aber was hältst du denn von JAXB?

Annotier deine Classen, leg dir die jaxb.info in das package und schreib dir noch alles was du brauchst, um zu marschallen (schreiben) und/oder unmarshallen (lesen).
Hilfe findest du dazu ganz sicher im Netz.

Als Starthilfe vielleicht eine (sehr einfache) Variante des Modells - ich lege aber nicht mein Hand dafür ins Feuer, da ich das eben mal per hand zusammengemeißelt hab und mich gerade nicht erinner, ob das alles so stimmt...

```
import java.util.List;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "categories", propOrder = { "category" })
@XmlRootElement(name = "categories")
public class Tree1 {

	@XmlElement(name = "category")
	protected List<Category> categories;

	public List<Category> getCategories() {
		return categories;
	}

	public void setCategories(List<Category> categories) {
		for (Category c : categories) {
			c.parent = this;
		}
		this.categories = categories;
	}

	public void addCategory(Category category) {
		category.parent = this;
		if (!categories.contains(category)) {
			this.categories.add(category);
		}
	}

	@XmlType(name = "category", propOrder = { "category" })
	public static class Category {

		/**
		 * So taucht es nicht im XML auf, kann im Modell aber genutzt werden,
		 * muss evtl. händisch nachbereitet werden!
		 */
		@XmlTransient
		protected Object parent;

		@XmlAttribute(name = "id", required = true)
		protected int id;
		@XmlAttribute(name = "name", required = true)
		protected String name;

		@XmlElement(name = "category")
		protected List<Category> categories;

		public int getId() {
			return id;
		}

		public void setId(int id) {
			this.id = id;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public List<Category> getCategories() {
			return categories;
		}

		public void setCategories(List<Category> categories) {
			for (Category c : categories) {
				c.parent = this;
			}
			this.categories = categories;
		}

		public void addCategory(Category category) {
			category.parent = this;
			if (!categories.contains(category)) {
				this.categories.add(category);
			}
		}
	}
}
```

BTW: kannst auch erst mal ein Schema bauen (dein XML-Modell ist ja nun wirklich nicht sehr komplex...) und dann mittels XJC übersetzen (dann stimmt schon mal das generelle XML-Mapping). Dann musst du nur noch die Geschichte mit dem Parent wieder einbauen - sollte aber nicht so schwer sein.

Oder: Baue die eine Liste von Elementen, in der jedes Element eine Liste mit Kind-IDs enthält - da das Parent-Objekt herauszufinde, sollte nicht sehr schwierig sein, ansonsten einfach auch noch die Parent-ID mit rein...


----------



## pc-world (7. Sep 2010)

Danke für eure Hilfe!

Ich bin jetzt zu folgender Lösung gekommen:

```
import java.util.ArrayList;
import java.util.List;

public class CategoryList {

	private List<Category> categories = new ArrayList<Category>();

	public List<Category> getCategories() {
		return categories;
	}

	public void addCategory(Category category) {
		if (!categories.contains(category)) {
			category.setParent(this);
			categories.add(category);
		}
	}

	public void removeCategory(Category category) {
		categories.remove(category);
	}

}
```


```
import java.util.List;

public class Category extends CategoryList {

	private int id;
	private String name;

	private CategoryList parent;

	public Category(int id, String name, String desc, String img) {
		setId(id);
		setName(name);
		setDesc(desc);
		setImg(img);
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		if (id < 0) {
			throw new IllegalArgumentException("param must be >=0");
		}
		this.id = id;
	}
	public String getName() {
		return name;
	}

	public void setName(String name) {
		if (name == null) {
			throw new NullPointerException("param must not be null!");
		}
		this.name = name;
	}
	
	protected void setParent(CategoryList category) {
		this.parent = category;
	}

	public CategoryList getParent() {
		return parent;
	}

}
```


----------



## Wildcard (7. Sep 2010)

EMF ist ideal für dieses Problem:
Eclipse Modeling - EMF - Home
EMF übernimmt für dich das parsen der XML, das überführen in dein Objektmodell und Dinge wie Parent/Child Beziehungen und dergleichen bekommst du frei Haus mitgeliefert.


----------



## dzim (8. Sep 2010)

Gibt es eigentlich etwas, was EMF nicht kann? ;-)


----------



## Wildcard (10. Sep 2010)

dzim hat gesagt.:


> Gibt es eigentlich etwas, was EMF nicht kann? ;-)



Ehrliche Antwort? Nein, EMF kann mehr als Chuck Norris


----------

