# Datenhaltung und Darstellung - Hat jemand eine Idee?



## White_Fox (12. Jul 2019)

Guten Morgen allerseits.
Ich schreibe mal wieder an meinem Generator für elektronische Bauteile. Diese haben ja bekanntlich verschiedene Eigenschaften (sowas wie Bauteilwert, Hersteller, Temperaturbereich, ...), die der Benutzer des Programms festlegen soll. Diese Eigenschaften werden von sog. Stamps generiert.

Verschiedene Stamps, die zusammengehören und gemeinsam dieselben (!= diegleichen) Bauteile erzeugen, sind zu einem StampSet zusammengefasst.

Hier mal eine kleine Tabelle, die das besser veranschaulicht:

```
+----------------+---------------+-------------------+
|   Hersteller   |    Toleranz   |  Verlustleistung  |
+----------------+---------------+-------------------+
|                 Hochlastwiderstände                |
+----------------------------------------------------+
|     Vishay     |       5%      | 5W, 10W, 25W, 50W |
+----------------+---------------+-------------------+
|                Präzisionswiderstände               |
+----------------------------------------------------+
| Isabellenhütte | 1%, 0,5% 0,1% |        0,1W       |
+----------------+---------------+-------------------+
```

Jetzt hab ich folgendes Problem: Ich hab drei Klassen die relativ unabhängig voneinander sind. Kein Stamp weiß etwas von einem anderen Stamp, kein StampSet weiß etwas von einem anderen StampSet. Dann gibt es noch die Library-Klasse, die die ganzen StampSets hält. Jetzt möchte ich aber, daß jedes StampSet ein Objekt für den Parameter 'Hersteller' hält.
Wie kriege ich in da jetzt Ordnung rein, und wie kann ich die Zuordnungen am Besten realisieren?

Mein erstes Konzept ging dahin, daß ich jedem Stamp-Objekt den Parameternamen mitgegeben habe. Und jedes StampSet habe ich mit einem Index versehen. Davon wollte ich gerne wegkommen. Einerseits möchte ich zumindest für die Bearbeitung die Einschränkung, immer verschiedene Strings haben zu müssen, nicht haben. Die Identifizierung eines StampSets nur über den Index...gefällt mir irgendwie nicht so recht, ist aber eher ein Bauchgefühl.

Jetzt bin ich dazu übergegangen, den Klassen Stamp und StampSet einen eineindeutigen Identifier mitzugeben. Damit sind Namen und Indizees völlig egal, aber wie mache ich die Zuordnung am Besten? Mir fallen da nur wilde Geschichten mit verschachtelten ArrayLists ein - damit schaffe ich aber eher Chaos anstatt Ordnung.

Das zweite: Ich will ja nicht die vollständigen Daten in der View haben. Daher dachte ich, daß ich ein Modell davon erstelle daß nur das enthält, was zur Anzeige und zur Ausführung von Benutzereingaben relevant ist. Also drei Klassen StampModel, StampsetModel, LibraryModel. Hier habe ich allerdings exakt das gleiche Problem mit der Zuordnung, daher würde ich das gerne erstmal im Model lösen.

Also...wie würdet ihr das machen?


----------



## mihe7 (12. Jul 2019)

Ich verstehe das Problem nicht ganz:

```
class StampSet {
    private final Set<Stamp> stamps;
    private String name;
    private Manufacturer manufacturer;
    // ...
}
```


----------



## White_Fox (12. Jul 2019)

Weil der Benutzer ja festlegen können soll, ob da Hersteller, Toleranz usw. aufgeführt werden oder nicht doch was ganz anderes. Da wird es mit `private Manufacturer manufacturerer` schwierig.


----------



## AndiE (12. Jul 2019)

Wie willst du die Bauelemente selbst halten? Passive, aktive- bei Passiven: Widerstände, Kondensatoren etc. Bei Widerständen Festwiderstände, veränderliche( Varistoren), veränderbare(Drehwiderstände)usw. usf. Das alles einfach über nen Array zu machen, ist vielleicht etwas umständlich.


----------



## mrBrown (12. Jul 2019)

Was genau ist ein Stamp und was ein StampSet?

Am Beispiel der Tabelle: jede Zelle (bzw. ein Wert pro Zelle) ist ein Stamp, und eine Zeile ein StampSet?


----------



## White_Fox (12. Jul 2019)

@AndiE 
Die Daten halte ich derzeit tatsächlich in Component-Objekten in einer ArrayList.

@mrBrown 
Genau.

```
+----------------+---------------+-------------------+
|   Hersteller   |    Toleranz   |  Verlustleistung  |
+----------------+---------------+-------------------+ ---
|                 Hochlastwiderstände                |   |
+----------------------------------------------------+   | Erstes StampSet
|     Vishay     |       5%      | 5W, 10W, 25W, 50W |   |
+----------------+---------------+-------------------+ ---
|                Präzisionswiderstände               |   |
+----------------------------------------------------+   |  Zweites StampSet
| Isabellenhütte | 1%, 0,5% 0,1% |        0,1W       |   |
+----------------+---------------+-------------------+ ---
```


----------



## mihe7 (12. Jul 2019)

Moooment. Der Stamp ist dann "Hersteller", "Toleranz" etc.? Und im StampSet bekommen die dann die Werte?


----------



## mrBrown (12. Jul 2019)

Ein StampSet für einen Hochlastwiderstand wäre dann zB {Stamp(Vishay), Stamp(5%), Stamp(5W)}?


----------



## mihe7 (12. Jul 2019)

Oder {Stamp(Hersteller), "Vishay", Stamp(Toleranz), "5%", Stamp(Leistung), "5W"} oder {Stamp(Hersteller=Vishay), Stamp(Toleranz=5%), Stamp(Leistung=5W)}?


----------



## White_Fox (12. Jul 2019)

Ich sehe schon, ich hab noch zu sehr mit Informationen gegeizt.

Der Stamp generiert das, was bei "Toleranz" usw stehen soll. Es gibt von dieser Klasse verschiedene abgeleitete Klassen, jede mit einem anderen Verhalten und anderen Möglichkeiten.
Beispiel: Ein Stamp generiert Daten anhand einer ArrayList.

```
ArrayList<String> list = new ArrayList<>();
list.add("1%"); // 0,5%, 0,1%. usw
Stamp s = new ListStamp("Toleranz", list);
```
Dieser Stamp würde jetzt eine ArrayList mit drei Componentobjekten erzeugen. Alle drei Componentobjekte haben einen Parameter, der "Toleranz" heißt. Im ersten Objekte hat dieser dann den Wert 1%, im nächsten Objekt 0,5%, im nächsten 0,1%, usw.

Dies findet alles innerhalb eines StampSets statt. Ein anderes StampSet soll jetzt ebenfalls einen Parameter "Toleranz" haben, allerdings würde dieses ein anderes Stampobjekt beinhalten das diesen Parameter mit anderen Werten füllt.


----------



## Thallius (12. Jul 2019)

Das ganze ist so verquer das, selbst wenn du das irgendwie implementierst niemals jemand kapieren wird.

Ich würde da nochmal ganz von vorne anfangen und mir ein für alle einfaches und verständliches Model ausdenken.

Claus


----------



## mrBrown (12. Jul 2019)

Stamp und StampSet kommen aus der Domäne (sind also Teil der ubiquitous language)?

Kannst du das irgendwie mit Umgangssprache erklären?
toleranz und Verlustleistung könnte ich da zumindest noch so halbwegs einordnen, Hersteller wäre aber für mich ein gänzlich anderes Konzept, deshalb bin ich mir recht sicher, dass wir unterschiedliche Ideen davon im Kopf haben...


----------



## mihe7 (12. Jul 2019)

White_Fox hat gesagt.:


> Ich sehe schon, ich hab noch zu sehr mit Informationen gegeizt.


Ich verstehe immer noch nicht, wo jetzt das eigentliche Problem liegt. Vielleicht können wird das ganze mal auf einen anderen Bereich übertragen, den jeder versteht: ein Online-Shop.

Wenn ich Dich richtig verstehe, willst Du, dass der Benutzer Artikel definieren kann, dabei Artikeleigenschaften angibt (z. B. Größe, Farbe), die Ihrerseits einen Wertebereich haben (und ggf. einschränken) und dann aus den Artikeln einen Katalog zusammenstellt?


----------



## White_Fox (12. Jul 2019)

mihe7 hat gesagt.:


> Vielleicht können wird das ganze mal auf einen anderen Bereich übertragen, den jeder versteht: ein Online-Shop.
> 
> Wenn ich Dich richtig verstehe, willst Du, dass der Benutzer Artikel definieren kann, dabei Artikeleigenschaften angibt (z. B. Größe, Farbe), die Ihrerseits einen Wertebereich haben (und ggf. einschränken) und dann aus den Artikeln einen Katalog zusammenstellt?


Ok, ich versuche es mal. Das Beispiel mit den Artikeln ist gar nicht so schlecht, das könnte klappen. Also: Wir wollen im Katalog z.B. Computer auflisten. Also brauchst du erstmal sowas wie ein Kapitel Computer. Bezogen auf mein Problem würde ich daraus eine Library machen.

Was du-für jeden Computer-im Katalog haben willst, sind Rechenleistung, Farbe und eine Artikelnummer.

Jetzt hast du zwei verschiedene Lieferanten.

Lieferant a:

Liefert Computer mit 100 GigaFLOPs, 150 GigaFLOPs und 200 GigaFLOPs.
Jeder Computer ist in den Farben schwarz, alu und rot erhältlich.
Lieferant b:

Liefert Computer mit 80 GigaFLOPs, 120 GigaFLOPs und 140 GigaFLOPs.
Jeder Computer ist in den Farben rot, gelb, grün und blau erhältlich.
Damit wir die Artikel besser einer Artikelnummer zuordnen können, wird einfach die Rechenleistung mit der Farbe kombiniert, also z.B. 100sw, 100al, 100rt, 150sw, 150al, ...

Der Grundgedanke hinter meinem Programm ist jetzt, daß der Nutzer einen Algorythmus zusammenschustern kann, eine Tabelle zu erstellen die wie folgt aussieht (das hat noch nichts mit den Tabellen weiter oben zu tun):



Spoiler





```
+----------------+---------+---------------+
| Rechenleistung |  Farbe  | Artikelnummer |
+----------------+---------+---------------+
|       100      | Schwarz |     100sw     |
+----------------+---------+---------------+
|       100      |   Alu   |     100al     |
+----------------+---------+---------------+
|       100      |   Rot   |     100rt     |
+----------------+---------+---------------+
|       150      | Schwarz |     150sw     |
+----------------+---------+---------------+
|       150      |   Alu   |     150al     |
+----------------+---------+---------------+
|       150      |   Rot   |     150rt     |
+----------------+---------+---------------+
|       200      | Schwarz |     200sw     |
+----------------+---------+---------------+
|       200      |   Alu   |     200al     |
+----------------+---------+---------------+
|       200      |   Rot   |     200rt     |
+----------------+---------+---------------+
|       80       |   Rot   |      80rt     |
+----------------+---------+---------------+
|       80       |   Gelb  |      80ge     |
+----------------+---------+---------------+
|       80       |   Grün  |      80gn     |
+----------------+---------+---------------+
|       80       |   Blau  |      80bl     |
+----------------+---------+---------------+
|       120      |   Rot   |     120rt     |
+----------------+---------+---------------+
|       120      |   Gelb  |     120ge     |
+----------------+---------+---------------+
|       120      |   Grün  |     120gr     |
+----------------+---------+---------------+
|       120      |   Blau  |     120bl     |
+----------------+---------+---------------+
|       140      |   Rot   |     140rt     |
+----------------+---------+---------------+
|       140      |   Gelb  |     140ge     |
+----------------+---------+---------------+
|       140      |   Grün  |     140gr     |
+----------------+---------+---------------+
|       140      |   Blau  |     140bl     |
+----------------+---------+---------------+
```




Jetzt kann der Benutzer hergehen und irgendwo die Datensätze unterbringen. In meinem Programm könnte dies eine neue Library 'Computer' sein. Dann muß er erstmal sagen, was für Daten er erzeugen will. In diesem Beispiel wären das 'Rechenleistung', 'Farbe' und 'Artikelnummer'.

Jetzt kann er aber nicht die Daten für alle Computer auf einmal erzeugen, jedenfalls nicht auf einfache Weise. Es gibt ja z.B. keinen blauen Computer mit 200 GigaFLOPs. Aber man kann die Daten sehr leicht mit zwei sehr einfachen Algorithmen erzeugen. Und um diese zu trennen, kann er diese in verschiedenen Datensets unterbringen.

So könnte der Benutzer ein Datenset für Lieferant a anlegen.
Für 'Rechenleistung' weist er dort ein Stamp-Objekt zu, daß mit einer ArrayList mit den Werten 100, 150 und 200 parametriert wird. Dieses Stamp-Objekt erzeugt jetzt stumpf drei Artikel, die jeweils einen Parameter 'Rechenleistung' haben.
Für 'Farbe' weist er ein anderes Stamp-Objekt zu. Dieses nimmt die drei erzeugten Artikel, kopiert sie, und fügt jeder Kopie den Parameter 'Farbe' hinzu und füllt diesen mit 'Schwarz', erzeugt weitere Kopien, fügt den Parameter 'Farbe' hinzu und füllt diesen mit 'Alu', usw. Insgesamt liefert dieses Stampobjekt 3x3 Artikelobjekte zurück.
Für die Artikelnummer gibt es ein Stampobjekt das in der Lage ist, die bereits erzeugte Parameter auszuwerten und daraus die Artikelnummer zu erzeugen, das würde aber hier jetzt zu weit führen.

Das Ganze sieht jetzt wie folgt aus:


Spoiler





```
+----------------------+---------------------------------+------------------------------------------+
|    Rechenleistung    |              Farbe              |               Artikelnummer              |
+----------------------+---------------------------------+------------------------------------------+
|                                      Datenset für Lieferant a                                     |
+---------------------------------------------------------------------------------------------------+
| Liste{100, 150, 200} | Vervielfache{Schwarz, Alu, Rot} | Erzeuge Artikel-Nr aus Rechenl. u. Farbe |
+----------------------+---------------------------------+------------------------------------------+
```




Jetzt fehlen ja aber noch die Computer von Lieferant b. Diese haben die gleichen Parameter, nämlich Rechenleistung, Farbe und Artikelnummer. Allerdings müssen die Stamp-Objekte hierfür anders parametriert werden.
Also brauchen wir ein weiteres Datenset, das wieder für jede Eigenschaft ein Stampobjekt enthält. Wenn der Benutzer wöllte, dann könnte er auch Stamps mit anderem Verhalten zuweisen, in diesem Beispiel hier ist das aber nicht notwendig.

Daher wird das zweite Datenset ebenso erzeugt wie das erste, mit den gleichen (!= denselben) Stampobjekten, aber anderer Parametrierung, und das sieht dann insgesamt so aus:


Spoiler





```
+----------------------+-------------------------------------+------------------------------------------+
|    Rechenleistung    |                Farbe                |               Artikelnummer              |
+----------------------+-------------------------------------+------------------------------------------+
|                                        Datenset für Lieferant a                                       |
+-------------------------------------------------------------------------------------------------------+
| Liste{100, 150, 200} |   Vervielfache{Schwarz, Alu, Rot}   | Erzeuge Artikel-Nr aus Rechenl. u. Farbe |
+----------------------+-------------------------------------+------------------------------------------+
|                                        Datenset für Lieferant b                                       |
+-------------------------------------------------------------------------------------------------------+
|  Liste{80, 120, 140} | Verfielfache{Rot, Gelb, Grün, Blau} | Erzeuge Artikel-Nr aus Rechenl. u. Farbe |
+----------------------+-------------------------------------+------------------------------------------+
```




Ich hoffe, das macht es etwas verständlicher.

Falls sich jemand fragt was das Ganze für einen Sinn macht: Es geht ja-wie gesagt-um Bauteilbibliotheken für ein Elektronik-CAD. Man kann wunderbar viel machen, wenn man detaillierte Informationen zu den Bauteilen hat. Z.B. kann Altium einem gleich die Verfügbarkeit anzeigen, wenn man z.B. die Hersteller-Nr in den Bauteilen hat. Auch für BOM-Export ist das sehr gut. Bisher habe ich sowas immer mit VBA-Skripten erschlagen, muß dann aber für jede Bauteilserie jedesmal ein neues Skript schreiben, bzw. ein altes Skript umschreiben, und da will ich mir gerne eine dauerhafte Lösung verschaffen. Und gleichzeitig etwas über vernünftige Softwareentwicklung lernen...


----------



## mihe7 (12. Jul 2019)

OK, mal eine erste Überlegung:



White_Fox hat gesagt.:


> Also brauchst du erstmal sowas wie ein Kapitel Computer.


Gut, dann haben wir ein Kapitel und eine Artikeldefinition (oder Konfiguration... auf jeden Fall nicht Artikel!) mit einer 1:n-Beziehung.



White_Fox hat gesagt.:


> Was du-für jeden Computer-im Katalog haben willst, sind Rechenleistung, Farbe und eine Artikelnummer.


D. h. jede Artikeldefinition besitzt beliebige Attribute, die über einen Namen verfügen und deren mögliche Werte über diesen Namen abrufbar sind. Außerdem definiert der Katalog, welche Attributnamen existieren.



White_Fox hat gesagt.:


> Dann muß er erstmal sagen, was für Daten er erzeugen will. In diesem Beispiel wären das 'Rechenleistung', 'Farbe' und 'Artikelnummer'.


Das wären dann die Namen der gewünschten Attribute, die der Benutzer aus dem gewälten Kapitel erhält.

Alles, was Du dann brauchst, ist eine Funktion, die das Kreuzprodukt bildet.

Soweit richtig?


----------



## White_Fox (12. Jul 2019)

Jap...so in etwa. Ein paar Anmerkungen:



mihe7 hat gesagt.:


> Gut, dann haben wir ein Kapitel und eine Artikeldefinition (oder Konfiguration... auf jeden Fall nicht Artikel!) mit einer 1:n-Beziehung.


Konfiguration trifft es ganz gut, denke ich. Es ist an dieser Stelle nur gesagt, daß alle Elemente später die gleichen Attribute haben werden.



mihe7 hat gesagt.:


> Alles, was Du dann brauchst, ist eine Funktion, die das Kreuzprodukt bildet.


Ja, wobei es die Stamp-Klassen bereits gibt sowie die Klassen, die damit dann Daten generieren.

Mein Problem besteht jetzt z.B. darin, sicherzustellen, daß alle Datensets innerhalb einer Konfiguration mit dem Attributsatz übereinstimmen. Was die ganze Sache etwas komplizierter macht und was ich bisher noch verschwiegen habe, ist, daß ein Datenset auch über temporäre Attribute verfügen können soll. Diese Attribute können während der Datenerzeugung genutzt werden, tauchen allerdings in den Nutzdaten, in diesem Fall in der Katalogtabelle, nicht mehr auf.

Ich vermute mal, ich hab die Zuständigkeiten der einzelnen Klassen schlecht geplant und schreib zumindest die Klassen für das Datenset und die Library komplett neu. Bei meinem ersten Ansatz hätte ich laufend lauter ArrayLists durchgehen müssen, prüfen ob alles vollständig ist, usw. Ein einziges Chaos.
Nur-wie mache ich das besser...?


----------



## AndiE (12. Jul 2019)

Ich versuche das mal im Rollen wiederzugeben. Der "Ersteller" erstellt eine Warengruppe. Zu der Warengruppe erstellt er wichtige Eigenschaften. Nun gibt er unterschiedliche Hersteller ein, die die Warengruppe liefern. Wenn er jetzt die vorhandenen  Artikel eingibt, dann entstehen für die Eigenschaften eine Menge von Werten, deren Art und Umfang abhängig ist von den Herstellern. 
Hat er so alle Mitglieder der Warengruppe eingegeben, erzeugt das Programm einen Warenschlüssel. 

Und wie geht es weiter?

Was macht der "Nutzer" mit dem Warenschlüssel? Sucht er sich zuerst die Warengruppe und gibt dann bei Eigenschaften von bis an, um dann die angebotenen Produkte zu sehen- dann aber mit Bestellnummer des Lieferanten? "27k5p0,25w"-würde einem Widerstand von 2,7 kOhm, 5% Toleranz und 0,25 W Belastbarkeit entsprechen. Soll das so funktionieren?


----------



## White_Fox (12. Jul 2019)

Der Terminus "Warengruppe" passt hier nur zufällig...es ist einfach eine grobe "Ordungseinheit" (von denen es auch mal mehrere geben soll).

Daß der Benutzer einen Hersteller eingibt, interessiert das Programm nicht, von Herstellern weiß es nichts. Die beiden Datensets hätten genauso gut z.B. Laptops und Desktop-Rechner enthalten können.
Ein Datenset definiert lediglich, wie die Werte, mit denen die Attribute befüllt werden, für einen Teil aller Artikel (Artikel war ja mein Beispiel, aber gibt es dafür einen abstrakteren Oberbegriff?) zustande kommen.



AndiE hat gesagt.:


> Und wie geht es weiter?



Das Endergebnis ist die Tabelle, die alle erzeugten Datensätze enthält. Diese sollen dann später in verschiedenen Formaten exportiert werden können, CSV, Excel-Datei, SQL-Datenbank...was du halt willst.
Ich habe nicht vor, die erzeugten Datensätze im Programm zu speichern, jedenfalls jetzt nicht. Aber all das, was die Library/Datensets/Stamps enthalten, dafür werde ich mal eine Speicherfunktion implementieren.


----------



## AndiE (12. Jul 2019)

Das beantwortet meine Frage noch nicht. Nehmen wir mal an, es geht um T-Shirts. Dann gibt es die in den Farben[weiß, blau, grün, gelb, orange, hellblau und schwarz] und in den Größen [XS,S,M,L,XL,XXl,3XL] für [Männer und Frauen]. Nehmen wir nun mal an, nicht jeder Lieferant kann alle Kombinationen aus den drei Arrays liefern. Um zu sehen, wer was liefern kann, musst du jja auch den Lieferanten halten, oder?Oder wen auch immer- aber verstehst du, was ich meine?


----------



## White_Fox (12. Jul 2019)

AndiE hat gesagt.:


> Oder wen auch immer- aber verstehst du, was ich meine?


Wenig, weil ich das Programm so allgemein wie möglich halten will, und mir die Beispiele da etwas zu konkret sind. (Man, klingt das merkwürdig wenn ich das lese...)

Aber ich versuche mal, dein Beispiel aufzugreifen und zu ergänzen:
Ja, wenn nicht jeder Lieferant alle T-Shirtkombinationen liefern kann, könntest du für unterschiedliche Lieferanten unterschiedliche Datensets anlegen.
Mehrere Datensetzs wären aber auch dann sinnvoll, wenn du nicht alle T-Shirts, die ein Lieferant liefert, mit den gegebenen Mitteln erzeugen könntest. Dann könnten auch zwei Datensets notwendig sein, um das komplette Sortiment eines einzigen Lieferanten zu erzeugen.

Den erzeugten Daten sieht man nacher nicht mehr an, aus welchem Datenset sie kommen. Das ist alles eine einzige Tabelle.

Nachtrag:
Zum Lieferanten selbst: Das Programm kennt kein Atrribut 'Lieferant'. Wenn du willst, daß der Lieferant in den Daten auftaucht, dann mußt du ein Attribut 'Lieferant' anlegen. Wenn zwei Lieferanten das gleiche Sortiment haben könntest du so z.B. alle Artikel wie oben gezeigt erstellen plus ein weiteres Stamp-Objekt, daß ein Attribut 'Lieferant' hinzufügt und mit passenden Werten versieht.

Was da für Attribute drin sind ist dem Programm völlig egal.


----------



## mihe7 (12. Jul 2019)

Lass uns noch ein wenig bei dem Shop bleiben. 

Wir haben ein Kapitel, das mehrere Artikel-Konfigurationen aufnehmen kann. Eine Artikel-Konfiguration besteht in unserer Vorstellung aus einer Menge von Attributen, kann also zu einem Attributnamen eine Liste möglicher Werte liefern.

Mal etwas Code als Skizze:

```
class Configuration {
    List<String> getValues(String attributeName);
    void setValues(String attributeName, List<String> values);
}
```

Als nächstes haben wir das Kapitel, also die Artikel.-Kategorie. Die verfügt über mehrere solcher Konfigurationen und kennt die Attributnamen aller Konfigurationen.

```
class Category {
    List<Configuration> configurations();
    Set<String> attributeNames();
    Configuration addConfiguration() {
        Configuration result = new Configuration();
        for (String name : attributeNames()) {
            result.setValues(name, new ArrayList<>());
        }
        configurations.add(result);
        return result;
    }
}
```


```
// folgendes würde Benutzer via UI erledigen:
// Kategorie anlegen
Category computer = new Category("Manufacturer", "FLOPS", "Color"); // Attributnamen
// Konfiguration anlegen
Configuration computerLieferantA = computer.addConfiguration();
// Werte der Konfiguration einstellen
computerLieferantA.setValues("Manufacturer", Collections.singletonList("HP"));
computerLieferantA.setValues("FLOPS", Arrays.asList("120 GFLOPS", "150 GFLOPS", "200 GFLOPS")); 
computerLieferantA.setValues("Color", Arrays.asList("schwarz", "alu", "rot"));
// Weitere Konfiguration anlegen
Configuration computerLieferantB = computer.addConfiguration();
// Werte der Konfiguration einstellen
computerLieferantB.setValues("Manufacturer", Collections.singletonList("Lenovo"));
computerLieferantB.setValues("FLOPS", Arrays.asList("80 GFLOPS", "120 GFLOPS", "140 GFLOPS")); 
computerLieferantB.setValues("Color", Arrays.asList("rot", "gelb", "grün", "blau"));
```

Damit hätten wir die Definition der "Stammdaten" abgeschlossen. Als nächtes wählt der Benutzer eine Kategorie aus, erhält dann die Attributnamen (Category#attributeNames()) angezeigt, wählt dort eine Menge aus und klickt auf "Erzeuge Tabelle".

Jetzt musst Du lediglich für jede Konfiguration der gewählten Kategorie aus den Werte-Listen das Kreuzprodukt erstellen und ausgeben; das könnte dann Dein Stamp-Objekt erledigen.


----------



## AndiE (12. Jul 2019)

So wie ich den letzten Post verstanden habe, ist der Lieferant egal: Wenn Lieferant A T-Shirts in rot oder blau in X und L anbietet un Lieferant B T-Shirts in gelb und M, dann gibt es diese T-Shirts: 
"rot in X", "rot in L", "blau in X" , "blau in L" und "gelb in M". 
Diese Liste soll das Ergebnis sein, das er übergibt. 
So habe ich das jetzt verstanden.


----------



## mihe7 (12. Jul 2019)

Das ist auch so, aber ich habe das ungünstig formuliert:


mihe7 hat gesagt.:


> erhält dann die Attributnamen (Category#attributeNames()) angezeigt, wählt dort eine Menge aus und klickt auf "Erzeuge Tabelle".





mihe7 hat gesagt.:


> Jetzt musst Du lediglich für jede Konfiguration der gewählten Kategorie aus den Werte-Listen das Kreuzprodukt erstellen


Hier sind nur die Werte-Listen gemeint, die zu den zuvor gewählten Attributen gehören.

D. h. in meinem Beispielfall bekäme der Benutzer zur Auswahl: "Manufacturer", "FLOPS" und "Color". Wählt er nun "FLOPS" und "Color" wird das Kreuzprodukt nur über die Mengen von "FLOPS" und "Color" (je Konfiguration) erstellt, das Ergebnis ist also

{"120 GFLOPS", "150 GFLOPS", "200 GFLOPS"} x {"schwarz", "alu", "rot"} vereinigt mit
{"80 GFLOPS", "120 GFLOPS", "140 GFLOPS"} x {"rot", "gelb", "grün", "blau"}


----------



## White_Fox (12. Jul 2019)

Wie laufen eigentlich bei Softwareentwicklern Meetings ab, wo einer dem anderen seine Ideen zu erklären versucht? 

Ich versuche es mal anders: Und zwar, wie ich auf die Bezeichnung 'Stamp' gekommen bin.

Stellt euch die Dinge, von denen ihr die Daten auflisten wollt (in meinem Projekt sind das Components, im Beispiel vorhin Computer und danach waren es T-Shirts) vor, daß sie als leerer "Datensatzbehälter" auf einem Förderband liegen und darauf an Maschinen vorbeifahren.







Jede dieser Maschinen verpasst einem Datensatzbehälter einen Stempel, wobei ein Stempel einen Wert für ein Attribut darstellt. (In meiner aktuellen Version ist es strenggenommen ein Paar aus Attributswert und Attributsbezeichnung.)

Um viele Variantionsmöglichkeiten zuzulassen, können diese Stempelmaschinen unterschiedliches Verhalten annehmen: Manche arbeiten eine Liste ab (die Rechenleistung wäre so eine Liste gewesen), andere liefern ein oder mehrere Konstanten, es gibt aber auch welche, die bereits erzeugte Attribute auswerten. Ich hab eine Klasse, dich ich mit einer HashMap und einem Attributsnamen parametrisieren kann, damit ließe sich beispielsweise jeder Farbe ein anderer Preis zuordnen (ob das hier Sinn macht sei mal dahingestellt, es geht ja nur ums Prinzip). Es gibt auch eine Stampklasse, der kann ich eine mathematische Funktion übergeben, wobei die Funktionsparameter bereits existierende Attribute sind.

Ob man das einfach so als Kreuzprodukt darstellen kann...weiß ich nicht.

Jedenfalls, eine solche "Fließbandstrecke" ist dann das, was ich bisher als Set bezeichnet habe. Im Beispiel mit den Computern gab es zwei solcher Sets, jeweils eines für jeden Lieferanten.


Nachtrag:
Danke übrigens daß ich euch das bis hierher angetan habt, meiner Problemdarstellung zu folgen.


----------



## mrBrown (12. Jul 2019)

White_Fox hat gesagt.:


> Wie laufen eigentlich bei Softwareentwicklern Meetings ab, wo einer dem anderen seine Ideen zu erklären versucht?


Man sitzt 8 Stunden zusammen und ist am Ende keinen Schritt weiter


----------



## mihe7 (13. Jul 2019)

White_Fox hat gesagt.:


> Wie laufen eigentlich bei Softwareentwicklern Meetings ab, wo einer dem anderen seine Ideen zu erklären versucht?


Das kommt darauf an, wer sich mit wem unterhält  Meine Nachfragen und Analogien haben zwei Gründe: erstens bist Du in einem Bereich unterwegs, mit dem ich nichts zu tun habe. Ich muss also erst einmal verstehen, worum es geht. Zweitens versuche ich, Deine Lösung zu ignorieren, zumindest so lange es um die Anforderungen geht  



White_Fox hat gesagt.:


> Ob man das einfach so als Kreuzprodukt darstellen kann...weiß ich nicht.


Deine ursprünglich gestellte Anforderung schon. Das spielt aber auch keine Rolle, denn das ist nicht Aufgabe von Artikelkategorie/-konfiguration.



White_Fox hat gesagt.:


> Jede dieser Maschinen verpasst einem Datensatzbehälter einen Stempel, wobei ein Stempel einen Wert für ein Attribut darstellt. (In meiner aktuellen Version ist es strenggenommen ein Paar aus Attributswert und Attributsbezeichnung.)


Mir fehlt hier die Erzeugung der Datensatzbehälter (das wären bei mir die Artikel). Du brauchst ja für jeden "Stempel" einen solchen.


----------



## White_Fox (13. Jul 2019)

mihe7 hat gesagt.:


> Mir fehlt hier die Erzeugung der Datensatzbehälter (das wären bei mir die Artikel). Du brauchst ja für jeden "Stempel" einen solchen.



Ganz einfach:

```
public class Property{
    public Property(String name, Object value)
}

public class Article{
    public void addNewProperty(Property p){}
}

public abstract class ArticleStamp{
    public ArrayList<Article> generateArticles(ArrayList<Article>);
}


public void generateComponents(ArrayList<ArticleStamp> stamplist){
    ArrayList<Article> articles = new ArrayList<>(); //leere Liste, in der die Artikel gehalten werden.
    
    for(ArticleStamp stamp : stamplist){
        articles = stamp.generateArticles(articles); //Manche Stampklassen können aufgrund ihres Verhaltens Artikel einfach erzeugen, eine solche muß natürlich am Anfang stehen. Manche erzeugen eine völlig neue Liste und geben diese zurück, andere bearbeiten tatsächlich nur.
    }
    //...
}
```


Ich habe mir, seit ich den Thread eröffnet habe, nochmal gründlich Gedanken gemacht und ich glaube nun zu wissen, wo mein Problem schonmal gelöst wurde: überall dort, wo Tabellen benutzt werden.

Als Beispiel mal was Bekanntes wie Excel: Dort sind die Zellen ja auch mehr oder weniger komplexe Objekte, die in Zeilen und Spalten angeordnet sind.
Bezogen auf mein Problem: Das, was in Excel ein Worksheet ist, wär bei mir eine Library oder im Beispiel vorhin eine Katalogseite. In jede Zelle stecke ich ein fertig parametriertes Stamp-Objekt rein.
Jetzt kann ich hergehen, alle Stampobjekte einer vollständig gefüllten Zeile in eine ArrayList stopfen, den obigen Code ausführen, und erhalte für jede Zeile eine Anzahl von Artikeln, die mit den gleichen Attributen ausgestattet sind.
Und das mache ich jetzt für alle Zeilen hintereinander.

Die Zellen (bzw. Stampobjekte, die in einer "Zelle" sind) müssen wissen, was in dem jeweiligen Spaltenheader steht. Wenn der Benutzer den Spaltenheader ändert, dann soll das Objekt in der Zelle das mitbekommen. Es soll auch Vorlagen für einen Spaltenheader geben können.

Wird das jetzt vielleicht klarer, was ich da machen will und was ich suche?


----------



## mihe7 (13. Jul 2019)

White_Fox hat gesagt.:


> überall dort, wo Tabellen benutzt werden.


Ja, das ist ja auch das Prinzip, das ich oben hatte, wollte jetzt aber nicht noch auf z. B. ResultSet und ResultSetMetaData umschwenken  Die Artikeldefinition sind die Metadaten und der Artikel ist dann eine Zeile in der "Tabelle". Dein StampObjekt (bzw. die Stempelmaschine) erzeugt dann anhand der Metadaten die Zeilen.


----------



## White_Fox (13. Jul 2019)

Schön daß mein Problem jetzt etwas klarer wird. Hättest du eine Idee, wie ich so eine Tabelle aufziehen kann? Gibt es da vielleicht ein passendes Entwurfsmuster oder sowas?

Du Suchmaschine des geringstem Mißtrauens wirft mir leider nur Sachen im Zusammenhang mit Grafikoberflächen vor die Füße.


----------



## mihe7 (13. Jul 2019)

White_Fox hat gesagt.:


> Hättest du eine Idee, wie ich so eine Tabelle aufziehen kann?


Ich dachte, das in Kommentar #21 gezeigt zu haben. 

Category wäre die Tabelle, die enthält als Metadaten die Spalten und die Configurations sind dann die Zeilen dazu. Per addConfiguration wird eine neue Zeile erstellt. Das ist nur etwas flexibler, weil man ja keine starren Spalten hat (die Configurations können zusätzliche Attribute enthalten).

Entwurfsmuster... mom. In PoEE, S. 508: Record Set (An in-memory representation of tabular data). Entspricht im Wesentlichen dem "Muster" aus #21 bzw. dem JDBC-ResultSet von Java.


----------



## White_Fox (13. Jul 2019)

mihe7 hat gesagt.:


> #21


Hm...das muß ich mir wohl mal etwas länger ansehen. Ich sehe nicht, wie ich bestimmte Dinge aus meinem bisherigen Konzept umsetzen kann. Könntest du mir ein kleines Beispiel geben, wie Werte abhängig von anderen Attributswerten gesetzt werden?
Z.B.: Wenn der Computer silber ist, soll er 10% mehr kosten, bei allen anderen nur 5%.


----------



## mihe7 (13. Jul 2019)

Das Beispiel aus #21 setzt in erster Linie die Tabelle aus Deinem Kommentar #1 um. Wie Du daraus Artikel erzeugst/Preise ermittelst, sehe ich davon unabhängig. Zum Beispiel könntest Du eine Configuration verwenden, um eine Stempelmaschine zu konfigurieren. Die erzeugt dann die notwendigen Stempel und da kann dann natürlich ein Preisstempel dabei sein, der in Abhängigkeit von der Farbe den Preis berechnet.


----------



## White_Fox (13. Jul 2019)

mihe7 hat gesagt.:


> Wie Du daraus Artikel erzeugst/Preise ermittelst, sehe ich davon unabhängig.


Ok, anderes Beispiel: Wir fügen das Attribut Gehäusematerial hinzu. Der Benutzer des Programms soll formulieren können: WENN die Farbe des Computers Alu ist, DANN hat er ein Metallgehäuse. SONST hat er ein Kunststoffgehäuse. Das Programm soll jetzt in den generierten Daten selbständig den richtigen Wert eintragen.

Oder: Wir fügen ein Attribut 'Zubehör' hinzu. Dieses füllen wir mit einer farbigen Laptoptasche, wir haben rote, blaue und schwarze Laptoptaschen. Diese will der Benutzer wie folgt auf die Rechner, basierend auf dem Attribut 'Laptopfarbe', verteilen:
weißer Laptop -> schwarze Tasche
alu Laptop -> schwarze Tasche
schwarzer Laptop -> rote Tasche
roter Laptop -> blaue Tasche
blauer Laptop -> blaue Tasche
...

Ansonsten: Ich bekomme die fertig parametrierten Stempelmaschinen bereits rein. Die Daten zur Parametrierung zu halten und später erst die Stempelmaschine zu erzeugen macht es mir eher schwerer. Es würde völlig reichen, fertige Stempelobjekte in eienr solchen Tabelle anordnen zu können.

Ich würde gerne mehr über das Record Set Pattern erfahren, aber so richtig finde ich da kaum etwas dazu-außer Hinweise auf das Buch, daß du bereits genannt hast. Und ein Video, aber da verstehe ich den Sprecher recht schlecht.
Kennst du vielleicht noch einen nützlichen Link dazu?


----------



## mihe7 (13. Jul 2019)

Mal von hinten nach vorne: https://martinfowler.com/eaaCatalog/recordSet.html, der für mich entscheidende Punkt ist der letzte Satz: "looks [exactly] like the result of an [SQL] query but can be generated and manipulated by other parts of the system" (für mich hier weniger relevante Dinge in eckigen Klammern). 



White_Fox hat gesagt.:


> Oder:


Du bist gerade dabei, die eierlegende Wollmilchsau schreiben zu wollen. Vereinfacht gesprochen hast Du eine Eingabemenge, eine Funktion und eine Ausgabemenge. 

Diese Funktionen in Code zu schreiben, wäre noch relativ harmlos, aber:


White_Fox hat gesagt.:


> Der Benutzer des Programms soll formulieren können:


D. h. Du musst Dir ein System überlegen, wie der Benutzer das formulieren könnte, wie Du das auf Objekte abbildest.


```
Function<Configuration, Article> produce = ...
Function<Article, Article> changePrice = ...
...
produce.andThen(changePrice).andThen(doThis).andThen(schlagmichtot).apply(configuration);
```


----------



## White_Fox (13. Jul 2019)

mihe7 hat gesagt.:


> Du bist gerade dabei, die eierlegende Wollmilchsau schreiben zu wollen.


Naja...so gesehen hab ich die dann schon geschrieben, denn wie gesagt: Stampobjekte parametrieren und erstellen geht, damit Daten zu erzeugen geht auch.

Es geht mir bloß nach darum, diese Stampobjekte passend zu halten und die Attribute synchron zu halten, wenn ich mich darauf beschränken würde, nur ein Datenset zu haben (also im Beispiel mit den Computern nur ein Hersteller) wäre ich schon längst fertig.


----------



## AndiE (13. Jul 2019)

Ich sehe das momentan als Array aus Parameter und Hersteller(Wertesatz). Im Schnittpunkt daraus gibt es am einfachsten eine Liste aus möglichen Parametern oder eine Formel. Dabei variieren nicht alle Parameter, sondern es gibt auch Abhängigkeiten untereinander. Das Programm ergibt dann Tupel aus Werten der Parameter aus.

Ich würde das wirklich als Grid erstellen, und beim Klicken auf eine Zelle erzeugt sich ein Dialogfeld, wo man die Parameter eingeben oder Formel erstellen kann.

Problematisch finde ich zwar immer noch, dass viel mehr Werte erzeugt werden, als Variationen real vorhanden sind.


----------



## mihe7 (13. Jul 2019)

White_Fox hat gesagt.:


> Es geht mir bloß nach darum, diese Stampobjekte passend zu halten und die Attribute synchron zu halten, wenn ich mich darauf beschränken würde, nur ein Datenset zu haben (also im Beispiel mit den Computern nur ein Hersteller) wäre ich schon längst fertig.


Naja, Du brauchst etwas, das dem Datenset "übergeordnet" ist, und das hält die Dinge synchron. Bei mir macht das die Category, dort z. B. mit #addConfiguration(). 

Wie gibt denn der Benutzer bei Dir die verschiedenen Wenn/Dann-Geschichten bzw. Formeln an?


----------



## White_Fox (14. Jul 2019)

AndiE hat gesagt.:


> Ich würde das wirklich als Grid erstellen, und beim Klicken auf eine Zelle erzeugt sich ein Dialogfeld, wo man die Parameter eingeben oder Formel erstellen kann.


So in etwa funktioniert das ja auch: Dem Benutzer wird eine Tabelle präsentiert, wo im Spaltenkopf die Attributbezeichnung steht. Dann abwechselnd eine (über die breite gemergte) Zeile, in der eine gewählte Bezeichnung steht, in der Zeile darunter steht in jeder Zelle, wie das Attribut zustande kommt. In diesen Zellen kann der Benutzer dann auswählen, wie er das Attribut erzeugen will (von einer Liste, basiernd auf existierenden Attributen, ...). Basierend auf dieser Auswahl wird eine neue Stempelmaschine erzeugt, die der Benutzer dann parametrieren kann.



mihe7 hat gesagt.:


> Naja, Du brauchst etwas, das dem Datenset "übergeordnet" ist, und das hält die Dinge synchron. Bei mir macht das die Category, dort z. B. mit #addConfiguration().


Genau...aber so richtig verstehe ich das Beispiel von dir immer noch nicht. Vermutlich, weil mit den Bezeichnungen nicht so recht klarkomme, oder zu tief in meinem Problem drinhänge und das zu schlecht abstrahieren kann.

Ich denk ich hab aber mittlerweile den Ansatz einer Idee, um dieses "übergeordnete Etwas" zu stricken.


----------



## White_Fox (26. Jul 2019)

So...ich denke ich hab jetzt meine Lösung gefunden. Vielen Dank für das Beschäftigen mit meinem Problem und den Rubberduckersatz.

So sieht der Plan jetzt aus (leicht abgekürzt, es ist so schon lang genug), wer meint Codeteile verwenden zu wollen kann das gerne auf eigene Gefahr tun:



Spoiler





```
//Wir brauchen:

public interface Itemable {
   
    <T extends ItemModel> T getItemModel(Class<T> m);
   
    void takeTableinformation(RowHeader row, ColumnHeader col);
}

@ForceDeclaredGenerics //Siehe: https://www.java-forum.org/thema/generics-vererbung.185397/
abstract class TableLine<IT extends Itemable> {
   
    private ArrayList<IT> items;
    private RT rowheader;

    public TableLine() {
        this.items = new ArrayList<>();
    }

    void addElement(IT item){
        items.add(item);
    }
   
    void removeAllElements(){
        items.removeAll(items);
    }

    public int getLenght(){
        return items.size();
    }
   
    public IT getItem(int i){
        return (i >= items.size() || i < 0) ? null : items.get(i);
    }

    public ArrayList<IT> getItemlist(){
        ArrayList<IT> list = new ArrayList<>();
       
        for(IT item : items){
            if(item != null){list.add(item);}
        }
        return list;
    }
   
    public Row(RT rowheader){
        this.rowheader = rowheader;
    }
   
    public Row(){
        this.rowheader = null;
    }
   
    RT getRowheader(){
        return this.rowheader;
    }

    boolean hasHeader(){
        return this.rowheader != null;
    }
}

@ForceDeclaredGenerics
public class RowTable<CHT extends ColumnHeader, RT extends Row, IT extends Itemable> {

    //Define element order in table
    private ArrayList<Identifier> rowOrder;
    private ArrayList<Identifier> columnOrder;
    //Link table lines to header
    private HashMap<Identifier, RT> rows;
    private HashMap<Identifier, CHT> columns;
    //Link items to rows and columns
    private HashMap<Identifier, HashMap<Identifier, IT>> table;

    public RowTable() {
        rowOrder = new ArrayList<>();
        columnOrder = new ArrayList<>();
        rows = new HashMap<>();
        columns = new HashMap<>();
        this.table = new HashMap<>();
    }

    private boolean checkRowIdentifier(Identifier i) {
        return rows.containsKey(i);
    }

    private boolean checkColumnIdentifier(Identifier i) {
        return columns.containsKey(i);
    }

    public void addRow(RT row) {
        Identifier i = new Identifier();
        row.removeAllElements();
        rowOrder.add(i);
        rows.put(i, row);
        table.put(i, new HashMap<>());
    }

    public RT getRow(Identifier i) {
        RT row;
        CHT columnheader;
        RowHeader rowheader;

        if (!checkRowIdentifier(i)) {
            return null;
        }

        row = rows.get(i);
        rowheader = row.getRowheader();
        for (Map.Entry<Identifier, IT> entry : table.get(i).entrySet()) {
            IT item = entry.getValue();
            Identifier c = entry.getKey();
            columnheader = columns.get(c);

            item.takeTableinformation(rowheader, columnheader);
            row.addElement(item);
        }
        return row;
    }

    public RT removeRow(Identifier i) {
        RT row;
        CHT columnheader;
        RowHeader rowheader;

        if (!checkRowIdentifier(i)) {
            return null;
        }

        row = rows.get(i);
        rowheader = row.getRowheader();
        for (Map.Entry<Identifier, IT> entry : table.get(i).entrySet()) {
            IT item = entry.getValue();
            Identifier c = entry.getKey();
            columnheader = columns.get(c);

            item.takeTableinformation(rowheader, columnheader);
            row.addElement(item);
        }

        rowOrder.remove(i);
        rows.remove(i);
        table.remove(i);

        return row;
    }

    public void addColumn(CHT header) {
        Identifier i = new Identifier();
        columnOrder.add(i);
        for(Map.Entry<Identifier, HashMap<Identifier, IT>> e : table.entrySet()){
            e.getValue().put(i, null);
        }
        columns.put(i, header);
    }

    public void addColumn() {
        Identifier i = new Identifier();
        columnOrder.add(i);
        columns.put(i, null);
    }

    public void removeColumn(Identifier i) {
        if (!checkColumnIdentifier(i)) {
            return;
        }

        columnOrder.remove(i);
        columns.remove(i);
        for (Map.Entry<Identifier, HashMap<Identifier, IT>> e : table.entrySet()) {
            e.getValue().remove(i);
        }
    }

    public boolean addItem(IT item, Identifier row, Identifier column) {
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return false;
        }

        rowMap = table.get(row);
        if (rowMap.get(column) == null) {
            rowMap.put(column, item);
            return true;
        }
        else {
            return false;
        }
    }

    public IT removeItem(Identifier row, Identifier column) {
        IT item;
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return null;
        }

        rowMap = table.get(row);
        item = rowMap.remove(column);
        return item;
    }

    public IT replaceItem(IT item, Identifier row, Identifier column) {
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return null;
        }

        rowMap = table.get(row);
        return rowMap.replace(column, item);
    }

    public ArrayList<Identifier> getRowIdentifier() {
        return (ArrayList<Identifier>) rowOrder.clone();
    }

    public ArrayList<Identifier> getColumnIdentifier() {
        return (ArrayList<Identifier>) columnOrder.clone();
    }
}

public class Identifier {
   
    private final UUID id;

    Identifier() {
        this.id = UUID.randomUUID();
    }
   
    UUID getId(){
        return id;
    }
   
    @Override
    public boolean equals(Object o){
        if(o.getClass().equals(Identifier.class)){
            Identifier i = (Identifier)o;
            return this.id.equals(i.getId());
        }
        return false;
    }
}

public abstract class ColumnHeader{}
public abstract class RowHeader{}
```




Um den Code kurz zu erklären: Damit eine Klasse in der RowTable als Tabelle gehalten werden kann, muß es daß Interface Itemable implementieren. Dieses sieht zwei Methoden vor: Eine Methode ist dazu da, ein Modell mit vereinfachten Informationen (z.B. für die Realisierung eines MVCs) zu erstellen, das ist aber noch nicht implementiert. Die andere Methode ist dazu da, dem Item Informationen über den Spalten- und Zeilenheader seiner Position zukommen zu lassen, indem ihm das jeweilige ColumnHeader- und RowHeaderobjekt übergeben wird. Diese Methode wird z.B. vor dem Zugriff auf ein Item aufgerufen, ich erkläre das gleich an einem Beispiel.
Die Klassen ColumnHeader und RowHeader dienen dazu, benutzerspezifische Informationen an die jeweiligen Items zu liefern.

Ansonsten ist da noch die (abstrakte) Klasse Row. Abstrakt deswegen, weil ich die Tabellenartigkeit des Ganzen Gebildes eigentilch etwas in den Hintergrund stellen will. Diese Klasse kann/soll vom Benutzer um weitere Fähigkeiten ergänzt werden können, der Tabellenzeilencharakter kann dabei völlig im Hintergrund verschwinden.

Und die Klasse RowTable. Diese kümmert sich um die korrekte Zuordnung von allem. Mit diesem Mechanismus bin ich absolut noch nicht glücklich, da ich insgesamt zwei ArrayLists<Identifier>, zwei HashMaps(Identifier, RowHeader/ColumnHeader> und eine HashMap<Identifier, HashMap<Identifier, Itemable>> synchronzuhalten habe. Wenn dazu noch jemand eine bessere Idee hat-immer her damit.

Ich werde die Anwendung mal Laptopbeispiel demonstrieren. Dazu konstruiere man erstmal ein Problem:


Spoiler





```
//Klasse, um die Daten eines Laptops zu halten
public Class LapTop{
    HashMap<String, String> properties;
    
    public void addProperty(String name, String value){properties.put(name, value);}
    public String getValue(String name){ return properties.get(name);}
}

//Klassen, um Laptops zu erzeugen
abstract public Class LaptopPropertymaker implements Itemable{
    String name;
    public void setName(String name){this.name = name;}
    abstract public ArrayList<LapTop> getLaptops(ArrayList<LapTop> laptops);
}

public Class LaptopListMaker extends LaptopPropertymaker{
    private ArrayList<String> list;
    private Stiring name;
    
    public LaptopListMaker(ArrayList<String> list){
        This.list = list;
    }
    
    public ArrayList<LapTop> getLaptops(ArrayList<LapTop> laptops){
        //Implementiere die Methode so, daß allen Objekten in laptops eine
        //Eigenschaft mit Name und einem Wert aus list hinzugefügt wird.
    }
    
    void takeTableinformation(RowHeader row, ColumnHeader col){
        this.name = col.getName(); //getName liefert den Namen der Spalte zurück,
        //was in diesem Beispiel gleichzeitig der Name des Attributs ist, das hinzugefügt wird.
    }
}


public Class LaptopConstantsMaker extends LaptopPropertymaker{
    private ArrayList<String> list;
    private Stiring name;
    
    public LaptopListMaker(ArrayList<String> list){
        This.list = list;
    }
    
    public ArrayList<LapTop> getLaptops(ArrayList<LapTop> laptops){
        //Die Implementierung ist hier anders als in der anderen Klasse. Beispiel:
        //in laptops sind zwei LapTopobjekte, in list sind drei Einträge. Dann werden
        //insgesamt sechs LapTop-objekte erzeugt, wobei die bestehenden zwei LapTop-objekte dreimal
        //geklont werden und jedes Objekt (bzw. die Klone) mit jeweils einem Eintrag aus list ausgestattet werden.
    }
    
    void takeTableinformation(RowHeader row, ColumnHeader col){
        this.name = col.getName(); //getName liefert den Namen der Spalte zurück,
        //was in diesem Beispiel gleichzeitig der Name des Attributs ist, das hinzugefügt wird.
    }
}
```




Den Gedanken dahinter habe ich ja schon erklärt: Man reicht ein ArrayList<LapTop> an einer Reihe LaptopPropertymaker-Objekte durch, und jedes dieser Objekte fügt ein spezielles Attribut (Rechenleistung, Farbe, ...) hinzu und versieht es mit einem Wert (120 MFlOPS, Schwarz, ...).

Jetzt kann ich mit dem Konstrukt da ganz oben Folgendes machen:


Spoiler





```
//Mit dieser Klasse wird der Name des Attributs festgelegt
public Class LaptopAttribute extends ColumnHeader{
    String name;
    public LaptopAttribute(String name){this.name = name;}
    public String getName(){return name;}
}

public Class EmptyRowheader extends RowHeader{} //RowHeader brauchen wir in diesem Beispiel nicht

public LaptopSet extens Row<EmptyRowheader, LaptopProterymaker>{
    //Hier kann man jeder Gruppe von LapTops nach Belieben weitere Informationen
    //hinzufügen, wie Herstellername, irgendwelche Kommentare...
}
```




Damit haben wir zwei Dinge: Einmal kann ich ein Attribut definieren. Jedem Itemable-Objekt werden, bevor man darauf zugreifen kann, die zwei jeweiligen Header-Objekte zugänglich gemacht. In diesem Fall ist es der Attributsname, der so übergeben wird.

Mit der LaptopSet-Klasse kann man jetzt einen Satz Laptops erzeugen. Braucht man mehrere (z.B. verschiedene Hersteller, oder verschiedene Chargen eines Herstellers, oder ...) kann man mehrere LaptopSets erzeugen. Aber: Wenn man mehrere LaptopSets hat, haben am Ende des Tages alle erzeugten Laptops die gleichen Attribute:



Spoiler





```
RowTable<LaptopAttribute , LaptopSet, LaptopPropertymaker> LaptopTable = new RowTable<>();

LaptopAttribute calcspeed = new LaptopAttribute("Rechenleistung");
LaptopTable.addColumn(calcspeed);

LaptopAttribute color = new LaptopAttribute("Farbe");
LaptopTable.addColumn(color);

LaptopAttribute material = new LaptopAttribute("Material");
LaptopTable.addColumn(material);


LaptopSet LaptopsA = new LaptopSet();
//Hinzufügen was man an Informationen haben will, aber noch keine Propertymaker
LaptpTable.addRow(LaptopsA);

LaptopSet LaptopsB = new LaptopSet();
//Hinzufügen was man an Informationen haben will, aber noch keine Propertymaker
LaptpTable.addRow(LaptopsB);
```




So...und das wars im Prinzip. Jetzt können LaptopPropertymaker-Objekte instanziert, parametrisiert und in die LaptopTable-Klasse eingefügt werden. Mit dem (noch nicht implementiertem) Model-Objekt können die Daten der Tabelle dann in einer GUI dargestellt werden ohne jedesmal die ganze Tabelle übergeben zu müssen.
Wenn man dann aus der LaptopTable-Klasse ein Row-Objekt wieder rausholt, dann enthält es die jeweiligen LaptopPropertymaker und man kann an einem Rutsch Laptops erstellen.

Wie gesagt, mit den vielen ArrayLists und HashMaps bin ich noch nicht glücklich, wenn jemand da Vorschläge hat-immer her damit. Was haltet ihr sonst davon?


----------



## White_Fox (24. Aug 2019)

So...nachdem das in den Unittests alles wunderbar funktioniert hat, will das Ganze schon nicht mehr so wie ich will. 

Folgendes: Die Klasse RowTable hab ich ja im vorherigen Post schon beschrieben, hier aber nochmal:

```
@ForceDeclaredGenerics
public class RowTable<
        CHT extends ColumnHeader,
        RHT extends RowHeader,
        RT extends Row,
        IT extends Itemable,
        RHMT extends RowheaderModel,
        CHMT extends ColumnheaderModel,
        IMT extends ItemModel> {

    //Define element order in table
    private ArrayList<Identifier> rowOrder;
    private ArrayList<Identifier> columnOrder;
    //Link table lines to header
    private HashMap<Identifier, RT> rows;
    private HashMap<Identifier, CHT> columns;
    //Link items to rows and columns
    private HashMap<Identifier, HashMap<Identifier, IT>> table;

    public RowTable() {
        rowOrder = new ArrayList<>();
        columnOrder = new ArrayList<>();
        rows = new HashMap<>();
        columns = new HashMap<>();
        this.table = new HashMap<>();
    }

    private boolean checkRowIdentifier(Identifier i) {
        return rows.containsKey(i);
    }

    private boolean checkColumnIdentifier(Identifier i) {
        return columns.containsKey(i);
    }
    
    public void addRow(RT row) {
        Identifier i = new Identifier();
        row.removeAllElements();
        rowOrder.add(i);
        rows.put(i, row);
        table.put(i, new HashMap<>());
    }

    public RT getRow(Identifier i) {
        RT row;
        CHT columnheader;
        RowHeader rowheader;

        //Check identifier
        if (!checkRowIdentifier(i)) {
            return null;
        }

        //Build row
        row = rows.get(i);
        rowheader = row.getRowheader();
        for (Map.Entry<Identifier, IT> entry : table.get(i).entrySet()) {
            IT item = entry.getValue();
            Identifier c = entry.getKey();
            columnheader = columns.get(c);

            item.takeTableinformation(rowheader, columnheader);
            row.addElement(item);
        }
        return row;
    }

    public RT removeRow(Identifier i) {
        RT row;
        CHT columnheader;
        RowHeader rowheader;

        if (!checkRowIdentifier(i)) {
            return null;
        }

        //Build row TODO: refactor this
        row = rows.get(i);
        rowheader = row.getRowheader();
        for (Map.Entry<Identifier, IT> entry : table.get(i).entrySet()) {
            IT item = entry.getValue();
            Identifier c = entry.getKey();
            columnheader = columns.get(c);

            item.takeTableinformation(rowheader, columnheader);
            row.addElement(item);
        }

        //Remove element and its items
        rowOrder.remove(i);
        rows.remove(i);
        table.remove(i);

        return row;
    }
    
    public void addColumn(CHT header) {
        Identifier i = new Identifier();
        columnOrder.add(i);
        for(Map.Entry<Identifier, HashMap<Identifier, IT>> e : table.entrySet()){
            e.getValue().put(i, null);
        }
        columns.put(i, header);
    }

    public void addColumn() {
        Identifier i = new Identifier();
        columnOrder.add(i);
        columns.put(i, null);
    }

    public void removeColumn(Identifier i) {
        if (!checkColumnIdentifier(i)) {
            return;
        }

        columnOrder.remove(i);
        columns.remove(i);
        for (Map.Entry<Identifier, HashMap<Identifier, IT>> e : table.entrySet()) {
            e.getValue().remove(i);
        }
    }

    public boolean addItem(IT item, Identifier row, Identifier column) {
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return false;
        }

        rowMap = table.get(row);
        if (rowMap.get(column) == null) {
            rowMap.put(column, item);
            return true;
        }
        else {
            return false;
        }
    }

    public IT removeItem(Identifier row, Identifier column) {
        IT item;
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return null;
        }

        rowMap = table.get(row);
        item = rowMap.remove(column);
        return item;
    }

    public IT replaceItem(IT item, Identifier row, Identifier column) {
        HashMap<Identifier, IT> rowMap;

        if (!(checkRowIdentifier(row) && checkColumnIdentifier(column))) {
            return null;
        }

        rowMap = table.get(row);
        return rowMap.replace(column, item);
    }

    public ArrayList<Identifier> getRowIdentifier() {
        return (ArrayList<Identifier>) rowOrder.clone();
    }

    public ArrayList<Identifier> getColumnIdentifier() {
        return (ArrayList<Identifier>) columnOrder.clone();
    }   

    public RowtableModel<RHMT, CHMT, IMT> getTablemodel(Class<IMT> m){
        RowtableModel<RHMT, CHMT, IMT> model = new RowtableModel<>(rowOrder.size(), columnOrder.size());
        Identifier rowId, colId;
        boolean columnHeadersInserted = false;
        
        for(int rowNum = 0; rowNum < rowOrder.size(); rowNum++){
            rowId = rowOrder.get(rowNum);
            model.addRowheader(rowNum, (RHMT) rows.get(rowId).getRowheader().getModel());
            
            for(int columNum = 0; columNum < columnOrder.size(); columNum++){
                colId = columnOrder.get(columNum);
                
                if(!columnHeadersInserted){
                    model.addColumnheader(columNum, (CHMT) columns.get(colId).getModel());
                }
                if(table.get(rowId).get(colId) != null){
                    table.get(rowId).get(colId).takeTableinformation(rows.get(rowId).getRowheader(), columns.get(colId));
                    model.addItem(rowNum, columNum, table.get(rowId).get(colId).getItemModel(m));
                }
                else{
                    model.addItem(rowNum, columNum, null);
                }
            }
            columnHeadersInserted = true;
        }
        return model;
    }
}
```

Jetzt will ich diese Klasse benutzen, und zwar derart:

```
@ForceDeclaredGenerics
public class StampPage extends RowTable<StamppageColumnheader,
        StampsetRowheader, StampSet, ComponentStamp,
        StampsetRowheaderModel, StamppageheaderModel, ComponentstampModel>{

}
```

Eigentlich möchte ich jetzt so etwas machen:

```
public void addStampSet(Stamppage page, StampSet set){
    page.addRow(set); //Die Klasse StampSet wird von Row abgeleitet: public class StampSet extends Row<StampsetRowheader, ComponentStamp>{}
}
```

Nur: wenn ich ein StampPage-Objekt habe, kann ich auf keine einzige RowTable-Methode zugreifen. Warum nicht, sieht jemand was ich da anscheinend übersehe?


----------



## mihe7 (24. Aug 2019)

`Stamppage` vs `StampPage`?


----------



## White_Fox (24. Aug 2019)

Hm...gute Idee, aber das ist es nicht. 

Edit:
Ich habe keine Idee warum, aber jetzt geht es plötzlich. Ich war schon verwirrt weil ich den Fehler in kleinerem Maßstab reproduzieren wollte, es da aber funktionierte.
Wer weiß was Netbeans da schon wieder quer im Hals zu stecken hatte...


----------

