Datentypbewahrung trotz Erbung

mavinatic

Bekanntes Mitglied
Hallo Community,

ich habe ein Problem mit der Gestaltung meines Programms. Ich habe eine Abstrakte Klasse "Value". Davon leiten sich folgende Klassen ab: BinaryValue, BooleanValue, TextValue, ... und so weiter. Nun möchte ich ein Objekt zurückgeben welches eine Ansammlung (Array) verschiedener Datentypen hat.
Jede Klasse hat einen "Key" und ein Value jeweils nach Datentyp - Klassennamen.

Aber wenn ich Value-Array als abstrakten Datentyp zurückgebe, weiß der Benutzer meiner API nicht, welcher DataType am implementiert ist. :-(

Gibts dafür eine gute Lösung?
 
Nunja, du könntest natürlich [c]getType()[/c] oder [c]isBinary()[/c] o.ä. anbieten. Aber da kommts aufs Use Case drauf an. Was hast du damit vor? Wie sollen diese Values verwendet werden?
 
Der Use-Case ist folgender: Ich fordere über eine Schnittstelle ein Objekt an und übergebe der Schnittstelle ein Schema. Nun soll von der Schnittstelle ein Objekt zurückkommen, welches ein Array mit Value-Objekten hat. Je nach Schema wird sind das verschiedene Unterklassen von dem abstrakten Objekt "Value" (BinaryValue, BooleanValue,...).
 
das ist alles ziemlich ungenau,
was weiß der Aufrufer, was will er haben?
ist der Aufruf (quasi-)statisch oder inwiefern spielt noch Konfiguration eines erzeugten Schnittstellen-Objekts eine Rolle?

vereinfacht reden wir vielleicht von einer statischen Methode X.parse(String), die ein Date, einen Integer sowie ein byte[] zurückgeben kann,
diese Methode kann von sich aus offensichtlich nur Object zurückgeben,

denkbar wären:
- Aufrufer caste einfach, weiß ja was er will?

- mehrere unterschiedliche Methoden, X.parseInteger(String) mit Rückgabewert Integer

- Übergabe der Klasse für generisch korrekten Cast:
> public static<T> T parse(String toParse, Class<T> returnClass)

- frei generische Methode, castet auf beliebiges:
> public static<T> T parse(String toParse)

Aufruf:
> Integer k = X.parse("");
kompiliert




ist das das Thema?
Generics bekannt oder zu den letzten beiden Punkten noch Fragen?
die beiden Varianten unterscheiden sich hier weniger als zunächst von mir gedacht,
Class<T> nur für den Cast des Rückgabewert zu übergeben scheint recht sinnlos, hat manchmal noch andere Funktionen,
für Anlegen eines Arrays schon etwas interessanter


wenn es nicht allein um Rückgabewert geht, dann bitte weiter erläutern
 
Zuletzt bearbeitet von einem Moderator:
Ich drücke mich wahrscheinlich ungenau aus :-(

Der Aufrufer fragt ein "Container"-Objekt an...dieses Container-Objekt beinhaltet ein Schema, d.h. verschiedene "Felder" mit Value-Objekten und verschiedenen Datentypen.
Nun soll der Benutzer auf die Value-Objekte zugreifen. Jede Subklasse bietet die Methode addValue(). Jedoch für BinaryValue addValue(byte[] arr) für BooleanValue addValue(Boolean b);
Dieses Container objekt muss nun so designed werden, dass der Benutzer alle ValueObjekte abrufen kann und mit dem jeweiligen Datentyp ausfüllen kann.

D.h. wenn ich meine Klasse
ValueContainer habe kann ich dort kein AbstractValueObject-Array anbieten, denn dadurch weiß der Benutzer nicht, welcher Datentyp verwendet wird für das jeweilige Value-Objekt.

Versteht ihr das vielleicht jetzt?
 
Ein paar Zeilen Pseudocode sagen vielleicht mehr als 363 Worte.
Java:
Value value = container.getValue(someID);
value.addValue(x);
Soll gehen, wobei 'x' dann verschiedene Typen haben kann:
Java:
Value booleanValue = container.getValue(someID);
booleanValue.addValue(someBoolean);

Value intValue = container.getValue(someID);
intValue.addValue(someInt);
Das geht natürlich nur (vernünftig) indem man dort über das Obekt mehr weiß, als dass es nur eine "Value" ist - man MUSS es dort als "BooleanValue" oder "IntValue" kennen:
Java:
BooleanValue booleanValue = container.getValue(someID);
booleanValue.addValue(someBoolean);

IntValue intValue = container.getValue(someID);
intValue.addValue(someInt);

Und das funktioniert wiederumm nur (typsicher), wenn man bei "getValue" schon mit übergibt, welchen Rückgabetyp man erwartet - in der einen oder anderen Form. Z.B.
Java:
BooleanValue booleanValue = container.getValue(someID, BooleanValue.class);
booleanValue.addValue(someBoolean);

IntValue intValue = container.getValue(someID, IntValue.class);
intValue.addValue(someInt);

oder ggf. etwas "distanzierter" und vielleicht allgemeiner:
Java:
interface Value<T>
{
    void setValue(T t);
}

class IntValue implements Value<Integer>
{
    void setValue(Integer t);
}

class Container 
{
    public <T> Value<T> getValue(String id, Class<T> contentType) { ... }
}


Value<Integer> intValue = container.getValue(someID, Integer.class);
intValue.addValue(someInteger);

Aber da ist (wegen der schwammigen beschreibung) noch viel Spekulation drin...
 
Ja...ich finde das schwer zu erklären, weil es alles in meinem Kopf eine Idee zu einem bestehenden Problem ist.

Java:
Container c = myInterfaceObject.requestContainer("MySchema"); //Benutzer fordert einen Container für ein Schema an.
AbstractValue[] values = c.getValues(); // Benutzer ruft alle Felder des Schemas ab. Durch das Array wurde der Datentyp aufgehoben und ich möchte jetzt die abfrage "instanceof" umgehen sowie eine generische Methode
//Durch c.getValues() muss der Benutzer nicht wissen welcher Wert in das ValueObjekt kommt, denn er hat nur die eine Auswahl, welcher Datentyp reinkommt.

Ich hoffe ich konnte das mit dem CodeSnippet verständlich erklären.
 
Zuletzt bearbeitet:
Wie gesagt, dazu muss man an dieser Stelle genauer wissen, welchen Typ man vor sich hat. Zu wissen, dass es AbstractValue ist, reicht nicht, es muss IntValue oder BooleanValue sein. So, wie du es beschrieben hast, HAT die Klasse "AbstractValue" ja gar nicht die Methode "addValue" (sie könnte sie haben, aber auch das wäre auf Basis von AbstractValue dann nicht typsicher)
 
Java:
public abstract class AbstractValueItem
{
    protected byte[] value;
    ....
    public abstract void setValue(object o);

    public abstract string getDataType();

    public abstract object getValue();
    ....
}

public class TextValueItem extends AbstractValueItem
{
    public void setValue(object o)
    {
         // hier müssen instance of abfragen rein, natürlich wäre eigentlich fast alles erlaubt da man es als text speichern kann
    }

    public string getDataType()
    {
        return "TEXT";
    }

    public string getValue()
    {
        // wandelt das byte[] in einen string um
    }
}

public class NumberValueItem extends AbstractValueItem
{
    public void setValue(object o)
    {
         // hier müssen instance of abfragen rein, nicht alles kann als nummer/zahl gespeichert werden
         // zwar speichert man es in wirklichkeit als byte[] aber da "text" als number ungültig sein wird/sollt, muss man solche angaben abfangen und entsprechend behandeln
    }

    public string getDataType()
    {
        return "NUMB";
    }

    public int getValue()
    {
        // wandelt das byte[] in ein int oder einen string um (je nachdem wie du es implementieren willst)
    }
}

So kannst du ein AbstractValueItem[] zurückgeben und bei jedem zum Beispiel die setValue Methode aufrufen um einen Wert zu setzen. Natürlich muss eben jede Subklasse von AbstractValueItem die Methode setValue implementieren und intern den Type des Objekts abfragen. Passt es ok, wenn nicht ist es ein Fehler
 
Zuletzt bearbeitet von einem Moderator:
So wäre der Entwurf ja generisch. Kann man das auch machen, das es nicht generisch ist? D.h. setValue(byte[] bytes)?
oder setValue(String s)?
 
Naja wirklich generisch ist es ja nicht!

Nein, weil du das Problem hast wenn du nur ein AbstractValueItem[] zurück gibst kannst du ja nur auf Methoden zugreifen die diese Klasse anbietet (natürlich könntest du sie um ... erweitern)
Java:
...
public abstract void setValue(string s);

public abstract void setValue(int i);
...

Aber jeder der Subklassen muss dann jede dieser Methoden implementieren, und auf ein TextValueItem die setValue(int) Methode aufrufen ist wahrscheinlich nicht gewollt!
[EDIT]
In C# gibt es eine Möglichkeit ein Methode als Obsolete zu makieren und wenn die verwendet wird, wird es als Compile Error angezeigt! Weiß nicht ob es sowas für Java auch gibt ...
[/EDIT]

Natürlich kannst du TextValueItem um ... erweitern
Java:
...
public void setValue(string s); // leitet dann intern weiter auf das setValue(object o);
...

Aber hier ist eben das Problem du hast nur ein AbstractItemValue[] zur Verfügung und kannst deshalb diese Methoden nicht aufrufen, oder aber castest es (nach einer Typüberprüfung)
Womit aber das ganze doch wieder etwas unnötig wäre
 
Zuletzt bearbeitet von einem Moderator:
Markieren kann man zumindest mit der Annotation @Deprecated. Man kann Eclipse sicher dazu bringen, die Verwendung als Fehler zu ahnden.

Ich glaube, dass hier aber ein intelligenteres Verfahren sinnvoll wäre. Dieses "Schema" sollte in meinen Augen nicht nur ein String sein. Man könnte statt einen schlichten Array zu erzeugen, ein eigenes listenartiges Objekt erzeugen, was anhand des Schemas weiß, an welche Stelle seiner Elemente welche genaue Unterklasse stehen muss und daher beim Abruf dieser auf diese castet. Dann kann man das von außen ganz bequem benutzen.
 
Zuletzt bearbeitet:
So ich hab mal was gebastelt dazu. Ob das so die geschickteste Lösung ist, bezweifele ich noch, aber es geht so.

Java:
package forumProblems.values;

public abstract class Value {

    private int key;

    public Value(int key) {
        this.key = key;
    }

    public int getKey() {
        return key;
    }

    public void setKey(int key) {
        this.key = key;
    }
}

Java:
package forumProblems.values;

public class IntegerValue extends Value {

    private int value;

    public IntegerValue(int key, int value) {
        super(key);
        this.setValue(value);
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "IntegerValue [value=" + value + ", getKey()=" + getKey() + "]";
    }

}

Java:
package forumProblems.values;

public class BooleanValue extends Value {

    private boolean value;

    public BooleanValue(int key, boolean value) {
        super(key);
        this.value = value;
    }

    public boolean isValue() {
        return value;
    }

    public void setValue(boolean value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "BooleanValue [value=" + value + ", getKey()=" + getKey() + "]";
    }

}

Java:
package forumProblems.values;

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

public class Schema {

    private String identifier;
    private List<Class<? extends Value>> classes;

    @SafeVarargs
    public Schema(String identifier, final Class<? extends Value>... classes) {
        this.setIdentifier(identifier);
        this.classes = new ArrayList<>();
        for (Class<? extends Value> c : classes) {
            this.classes.add(c);
        }
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public List<Class<? extends Value>> getClasses() {
        return classes;
    }

    @Override
    public String toString() {
        return "Schema [identifier=" + identifier + ", classes=" + classes
                + "]";
    }

}

Java:
package forumProblems.values;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;

public class ValueList {

    private Schema schema;

    private List<Value> values;

    public ValueList(Schema schema) {
        this.setSchema(schema);
        values = new ArrayList<>();
    }

    public Schema getSchema() {
        return schema;
    }

    public void setSchema(Schema schema) {
        this.schema = schema;
    }

    public boolean addNextValue(Value value) {
        if (values.size() < schema.getClasses().size()) {
            if (value.getClass() != schema.getClasses().get(values.size())) {
                throw new InvalidParameterException("An Index " + values.size()
                        + " wurde die Klasse "
                        + schema.getClasses().get(values.size())
                        + " erwartet, geliefert wurde aber die Klasse "
                        + value.getClass() + "!");
            }
            values.add(value);
            return true;
        }
        else {
            return false;
        }
    }

    public void clear() {
        values.clear();
    }

    public Value getValueAt(int index) {
        if (index < values.size()) {
            return values.get(index);
        }
        else {
            throw new InvalidParameterException("values hat nur "
                    + values.size() + " Elemente, angefordert wurde Index "
                    + index + "!");
        }
    }

    public Class<? extends Value> getClassAt(int index) {
        if (index < values.size() && index < schema.getClasses().size()) {
            return schema.getClasses().get(index);
        }
        else {
            throw new InvalidParameterException("classes hat nur "
                    + schema.getClasses().size()
                    + " Elemente, angefordert wurde Index " + index + "!");
        }
    }

    public int size() {
        return values.size();
    }

    public boolean isComplete() {
        return values.size() == schema.getClasses().size();
    }

    @Override
    public String toString() {
        return "ValueList [schema=" + schema + ", values=" + values + "]";
    }

}

Java:
package forumProblems.values;

public class AblaufTest {

    public AblaufTest() {
        ValueList list = generateList();
        System.out.println("Liste: " + list);
        for (int i=0; i<list.size(); ++i) {
            Value value = list.getValueAt(i);
            System.out.println("Value: " + value);
            Class<? extends Value> c = list.getClassAt(i);
            System.out.println("Class: " + c);
        }

    }

    private ValueList generateList() {
        Schema schema = new Schema(
                "b-i-i-b-i",
                BooleanValue.class,
                IntegerValue.class,
                IntegerValue.class,
                BooleanValue.class,
                IntegerValue.class
        );
        ValueList list = new ValueList(schema);
        list.addNextValue(new BooleanValue(10, true));
        list.addNextValue(new IntegerValue(20, 17));
        list.addNextValue(new IntegerValue(21, 59));
        list.addNextValue(new BooleanValue(11, true));
        System.out.println("Liste sollte unkomplett sein: Komplett=" + list.isComplete());
        list.addNextValue(new IntegerValue(22, 92));
        System.out.println("Liste sollte komplett sein: Komplett=" + list.isComplete());
        return list;
    }

    public static void main(String[] args) {
        new AblaufTest();
    }

}

Ausgabe:

Code:
Liste sollte unkomplett sein: Komplett=false
Liste sollte komplett sein: Komplett=true
Liste: ValueList [schema=Schema [identifier=b-i-i-b-i, classes=[class forumProblems.values.BooleanValue, class forumProblems.values.IntegerValue, class forumProblems.values.IntegerValue, class forumProblems.values.BooleanValue, class forumProblems.values.IntegerValue]], values=[BooleanValue [value=true, getKey()=10], IntegerValue [value=17, getKey()=20], IntegerValue [value=59, getKey()=21], BooleanValue [value=true, getKey()=11], IntegerValue [value=92, getKey()=22]]]
Value: BooleanValue [value=true, getKey()=10]
Class: class forumProblems.values.BooleanValue
Value: IntegerValue [value=17, getKey()=20]
Class: class forumProblems.values.IntegerValue
Value: IntegerValue [value=59, getKey()=21]
Class: class forumProblems.values.IntegerValue
Value: BooleanValue [value=true, getKey()=11]
Class: class forumProblems.values.BooleanValue
Value: IntegerValue [value=92, getKey()=22]
Class: class forumProblems.values.IntegerValue
 

Zurück
Oben