Konkrete Klasse verallgemeinern

Hallo, habe als Beispiel folgende Klasse (siehe unten) die Daten aus einem Buch Objekt in ein csv String schreibt, ich möchte aber je nachdem auch Daten aus anderen Objekten (Spiel, Auto u.s.w) in ein csv String schreiben können. Wie kann ich das am besten machen, dass ich ein belibiges Objekt im Konstruktor dieser Klasse übergebe und von diesem Elemente in ein csv String schreibe? Würde mich über Hilfe freuen :slight_smile:

public class CSVWriter
{

  Vector zeilen;

  Vector spalten;

  CSVWriter(Vector zeilen, Vector spalten)
  {
    this.zeilen = zeilen;
    this.spalten = spalten;

  }

  public void writeCSV()
  {

    FileWriter fw = null;
    try
    {
      fw = new FileWriter("WriteTest.csv");
    }
    catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    PrintWriter out = new PrintWriter(fw);

    for (Enumeration e = spalten.elements(); e.hasMoreElements();)
    {
      String s = (String) e.nextElement();

      out.print(s);
      if (e.hasMoreElements())
      {
        out.print(";");
      }
      else out.print("
");

      // Flush the output to the file
      out.flush();
    }

    for (Enumeration e = zeilen.elements(); e.hasMoreElements();)
    {
      Titel t = (Titel) e.nextElement();

      out.print(t.titel);
      out.print(";");
      out.print(t.autor);
      out.print(";");
      out.print(t.preis);
      out.print("
");

      // Flush the output to the file
      out.flush();
    }
    // Close the Print Writer
    out.close();

    // Close the File Writer
    try
    {
      fw.close();
    }
    catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }

}

Gruß

Du kannst ein Interface definieren, welches die in eine CSV-Datei überführbaren Objekte implementieren müssen.
Dann kann dein Writer die Daten entsprechend aus jedem Objekt holen, weil er die dafür bestimmten Methoden “kennt”.

[QUOTE=JavaFan]ich möchte aber je nachdem auch Daten aus anderen Objekten (Spiel, Auto u.s.w) in ein csv String schreiben können. Wie kann ich das am besten machen, dass ich ein belibiges Objekt im Konstruktor dieser Klasse übergebe und von diesem Elemente in ein csv String schreibe?[/QUOTE]Gar nicht.
Du wirst nicht darum herum kommen speziellen Code für jede mögliche “Eingabeklasse” zu schreiben. Das liegt daran, dass Du beim Schreiben wissen musst, welche Porperties im CSV wescheinen sollen und welche nicht.

Du kannst diese Klasse so schreiben, dass sie ein von @L-ectron-X vorgeschlagenes Interface akzeptiert. Über eine Methode dies Interfaces kannst Du dann eine Liste der zu schreibenden Werte aus Deinem Objekt holen. Welche Werte das sind muss dann das die implementierende Klasse wissen.

Mein Punkt ist: Das verschiebt den CSV-relevanten Code aus dieser Klasse in (alle) anderen. Die gute Nachricht ist: da würde er prinzipiell auch hin gehören.

bye
TT

Eine Datenklasse sollte keinen Code enthalten, der für Repräsentation oder Ausgabe in sprezifische Formate gebraucht wird. Das würde also für extra Klassen sprechen, die die Ausgabe regeln (bspw. AutoCsvRenderer oder so). Auf der anderen Seite sind diese Renderer ziemlich eng an das Datenobjekt gekoppelt. Das führt im Extremfall am Ende dazu, dass jede Datenklasse ihren eigenen Renderer bekommt. Das ganze dann möglicherweise sogar je Ausgabeformat. Ich plädiere deswegen für eine Mischform. Als Anregung dazu folgendes Interface:

public interface StringValues {
  /**
    * Gibt ein String-Array zurück, dass die Werte aller relevanten Variablen
    * als String-Repräsentation enthält.
    */
  public String[] getValuesAsStrings();
}

Deine Datenklassen implementieren dieses Interface. Ich finde den Algorithmus generalistisch genuch, um seinen Code in die Datenklassen zu packen. Alternativ wäre auch ein Interface denkbar, das eine Map<String,Strin> zurück gibt, wo die Variablennamen auf die Werte gemappt sind.

Diese Empfehlung wundert micht jetzt (entsprechend dem Beitrag von nillehammer) auch etwas. Sowas wie

class Auto implements CSVWriteable, XMLWriteable, JSONWriteable, XLSWriteable, TXTWriteable .... { ... }

kann ja nicht das Ziel sein…!?

Bei verschiedenen Serialisierern wird genau dieses (EDIT> von nillehammer angedeutete <EDIT) Verfahren verwendet: Es wird - GANZ grob gesagt - für jede Klasse ein „Writer“ registriert, der weiß, was er mit Objekten dieser Klasse zu tun hat

csvWriter.register(Auto.class, new AutoCsvWriter());
csvWriter.register(Spiel.class, new SpielCsvWriter());

// Schaut intern auf basis der .class des übergebenen Objektes
// nach, welcher Writer verwendet werden soll:
csvWriter.write(auto, "out.csv"); 

Aber je nachdem, WIE allgemeingültig das sein soll, kann man da natürlich eine gigantische Infrastruktur drumrumbauen … die dafür, dass du ein paar Zeilen in eine Datei schreiben willst, vielleicht unangebracht wäre.

Aber vielleicht braucht er das gar nicht. h2, die bekannte Java-Datenbank kann gaaanz einfach CSV lesen und schreiben.
Auch ein Beispiel gibts im h2-Tutorial von h2. CSV (Comma Separated Values) Support
Ich glaube, viel einfacher geht’s nicht.

Mal ganz dumm gefragt: Warum muss es CSV sein? Für XML oder JSON ständen dir “fertige” Lösungen zur Verfügung.

Entschulige @JavaFan , wenn ich mich hier mal mit reinhänge, aber Landei hat mich gerade neugierig gemacht.
@Landei , du sagtest eben, dass es eine “fertige” Lösung für XML geben soll. Ich muss mich jetzt erstmalig mit dem Thema beschäftigen. Kannst du mehr über die XML-Lösung verraten.
Was genau meinst du? Das Thema ist recht komplex und die Beispiele im Netz nur bedingt brauchbar, teilweise gar nicht ausführbar.

Bei XML würde ich einfach JAXB nehmen: Ein paar Annotationen, und schon kannst du die Klasse von und zu XML konvertieren.

Gerade JAXB fand ich persönlich ziemlich sperrig. Sowas wie http://xstream.codehaus.org/ ist halt “magic”: Hatte es mal ausprobiert und war beeindruckt. Aber… zugegeben… am ende bin ich dann doch wieder bei dem gelandet, was jeder als “unprofessionelles no-go” bezeichnet - nämlich das ganze per Hand zu machen…

Egal, wofür du dich entscheidest, es läuft auf jeden Fall darauf hinaus, dass du für jede Klasse einen eigenen Writer und einen eigenen Reader brauchst. Writer und Reader lassen sich natürlich auch zusammen in einer Klasse unterbringen, besser ist aber, wenn man sie getrennt hält. Das Ganze könnte man im simpelsten Fall mit Objektserialisierung lösen, das ist aber nicht wirklich toll. Besser dagegen wären eigene Datentypen-Klassen, die man per SPI realisiert.
Das Problem dürfte anschliessend sein, wie du die verschiedenen Dateien erkennst, damit das System auch den korrekten Reader bzw Writer auswählt. Die Writer sind da noch das Einfachste:

w.write(myCSVObject, myOutputStream);```
Beim Lesen aber hat man ja nur einen InputStream, dessen Inhalt man nicht kennt. Dass bedeutet, dass man aus diesem erstmal eine ID (MagicNumber o.ä) lesen muss, um einen Reader zu finden:
```ID myID = myCSVInputStream.readID();
CSVReader r = CSVReader.forID(myID);
CSVObject myCSVObject = r.readFrom(myCSVInputStream);```

[QUOTE=Marco13]...was jeder als "unprofessionelles no-go" bezeichnet - nämlich das ganze per Hand zu machen....[/QUOTE]Wie bitte? Ich bezeichne es als no-go wenn j Menschen n Reader und f Writer für einen einzigen Datentypen schreiben. Deswegen mach' ich ja eigentlich auch alles "per Hand". Jetzt such' ich eigentlich nur noch einen Hoster, wo man auch mal mehrere Projekte (Datentypen, Services und die API selbst) zusammenfassen aber doch getrennt verwalten kann, also so, dass die einzelnen Parts auch getrennte Versionhistorys haben. Bei Google-Code und GitHub muss man dafür x Projekte anlegen. Oder ich hab' keinen Plan, wie man's hinbekommt.

Wenn es so allgemein sein muss, dann könnte Reflection auch ein gangbarer Weg sein.

*** Edit ***

Das wird aber schnell eklig, wenn nicht nur grundlegende Datentypen verwendet werden. Das fängt schon bei Listen an und wird beliebig kompliziert (beispielsweise, wenn der Objektgraph zyklisch ist).

Danke für die vielen Vorschläge, mal sehen welche Lösung ich nehmen werde. :slight_smile:

Wollte es halt mit Csv versuchen, villeicht weil es das schlankste und einfachtse Format ist (meiner Meinug nach).

Gruß

Vielleicht lässt sich da analog zu JAXB auch etwas mit Annotations zaubern? Ist etwas schwieriger, aber man verändert damit die betroffenen Klassen nicht “wirklich” (und kann später auch andere Persistenz-Frameworks nutzen, da sich Annotations nicht gegenseitig stören).

… sofern sie unterschiedlich heißen :smiley:
Ein bißchen hat mich diese Vorstellung aber auch gestört: In MODELL-Klassen sowas zu haben wie

@XMLElement
@CSVWriteable(separator=";")
@JSONObject
@OhYesAndXlsByTheWay
@AndDontForgetTxt
public String getName() { ... }

Aber manchmal muss man Prioritäten setzen. Es KANN einem schon viel Arbeit sparen…