Typüberprüfung in Superklasse - geeignetes Pattern?

Status
Nicht offen für weitere Antworten.

-frank

Bekanntes Mitglied
ich habe eine abstrakte klasse A, welche bestimmte eigenschaften kapselt. zb schlüssel, name, beschreibung, etc.
zusätzlich werden bestimmte werte festgehalten. zb erwartet die Klasse FloatA float-werte (und bietet dann zusätzliche methoden um mit diesen werten etwas zu tun).

es gibt etliche klassen, die A erweitern. jede davon soll Methoden wie setValue(..) oder doSomethingWithValue(..) haben.
dadurch entsteht aber viel redundanter code, da ich zb möchte, dass bei einem null-Objekt eine fehlermeldung ausgegeben wird oder dass geprüft wird, ob der alte wert dem neuen entspricht (dann muss der wert nicht geändert werden). Diesen Code möchte ich daher in die abstrakte klasse A geben.
Weiters will ich, dass man auch nur mit dem abstrakten Typ arbeiten kann, es also ein A.setValue(..) gibt, wobei dann der werttyp eine Superklasse aller anderen Typen sein muss (in meinem Fall Object).
aufgrund der kontravarianz von java bei den methodenparametern kann ich ein setValue(Object) aber nun nicht mit setValue(Float) überschreiben (und dann super.setValue(value) callen). ich möchte aber trotzdem, dass der typ gecheckt wird.

mein ansatz war nun, den erwarteten typ (im konstruktor) zu übergeben (also zb Float.class) und dann in der abstrakten klasse den typ zu überprüfen. ich kann somit den code, der alle A betrifft in A implementieren und dann zb noch ne abstrakte methode doSomethingWithValueImpl(Object) definieren, die die untertypen dann implementieren (dort kann ich mir dann die checks sparen, hab also keine redundanzen mehr).

das funktioniert auch soweit. meine frage ist jetzt nur, ob dies ein geeignetes pattern für solche probleme ist oder ob es gründe gibt, die gegen diese variante sprechen bzw. bessere lösungen. denn ich das jetzt zwar mal so gemacht, bin mir aber nicht sicher, ob ich in kürze nicht schon wieder auf etwas stoße, wo diese variante nicht so ideal sein könnte. würde mich also freuen, wenn jemand mit mehr erfahrung ein paar worte dazu sagen könnte).

danke!
 
S

SlaterB

Gast
als Alternative gibts vielleicht eine Einzeloperation zum testen:
if (isWrongType(value)) {
..
}

diese Operation muss dann in jeder Unterklasse implementiert werden,
ist vielleicht nicht ganz so redundant,

wenn es natürlich nur ein Klassentest ist, dann scheint das aufwendig,
nützlich wirds aber wenn auch noch bestimmte Zustände abgefragt werden müssen

was da nun Pattern ist oder nicht kann ich nicht sagen ;)
 

Yzebär

Bekanntes Mitglied
Ich würde für die Typen einen eigenen Typ (abstrakte Klasse) als Basis definieren und nicht Object als Basis verwenden. Somit werden alle Typen eine einheitliche Schnittstelle haben und können zusätzliche, typenspezifische Funktionalität beinhalten.
 

kleiner_held

Top Contributor
Fuer Compile-Time checks gibt es ab 1.5 Generics.
Fuer Run-Time checks musst du auf alle Faelle das Class Objekt mit rein reichen, aber bei Verwendung von Generics und absoluter Typisierung in der Subklasse kann man sich das eigentlich sparen.
 

-frank

Bekanntes Mitglied
Ich würde für die Typen einen eigenen Typ (abstrakte Klasse) als Basis definieren und nicht Object als Basis verwenden. Somit werden alle Typen eine einheitliche Schnittstelle haben und können zusätzliche, typenspezifische Funktionalität beinhalten.

das hätte natürlich auch vorteile, wobei ich aber glaube, dass ich es nicht brauchen werde. derzeit arbeite ich mit strings, integern, floats, etc. als werten. es wird da keine zusätzliche funktionalität geben.

Fuer Compile-Time checks gibt es ab 1.5 Generics.
Fuer Run-Time checks musst du auf alle Faelle das Class Objekt mit rein reichen, aber bei Verwendung von Generics und absoluter Typisierung in der Subklasse kann man sich das eigentlich sparen.

hmm, kann man das wirklich mit generics lösen? meinst du, dass ich der abstrakten klasse einen generischen typ verpasse und diesen dann in den erweiternden klassen angebe?

wenn es natürlich nur ein Klassentest ist, dann scheint das aufwendig,
nützlich wirds aber wenn auch noch bestimmte Zustände abgefragt werden müssen.

ich habs derzeit so, dass ich in der abstrakten klasse einen check habe, der auf null überprüft + den typ und equals() (und entsprechend reagiert) und dann eine weitere methode für spezifischere tests. die ist in der abstrakten klasse leer, kann aber von den subklassen überschrieben werden, falls zusätzliche checks nötig sind.

dazu auch ne frage: würdet ihr diese methode leer machen oder abstrakt oder garnicht und es so lösen, dass die ursprüngliche methode überschrieben wird und dann als erstes die supermethode aufgerufen wird?
gegen das überschreiben der ursprünglichen methode spricht in meinem fall IMO, dass die methoden einen returnwert haben und ich diesen abfragen muss (und nur in bestimmten situationen müsste die überschreibende methode dann überhaupt ausgeführt werden. da wären also wieder ein paar redundante code zeilen in jeder überschreibenden methode).
ich hab mich jetzt für die leere variante entschieden, da etliche subklassen keine weiteren checks brauchen und ich mir somit eine leere implementierung der abstrakten methode erspare. also ich war der ansicht, dass eine leere methode besser sei als viele.
 
S

SlaterB

Gast
> hmm, kann man das wirklich mit generics lösen?

kannst nur du wissen, wie die Voraussetzungen im Programm sind,
wenn du immer nur mit Floats und Doubles hantierst, dann siehst gut aus,

wenn du diese Objekte aber bereits in einer anderen Liste drin hast und sie nur als Object ansprichst,
dann hilft dir Generics wenig bzw. zwingt dich Generics auf den richtigen Typ zu casten,
eine Typprüfung ist dann zu spät
 

kleiner_held

Top Contributor
Hmm so wie deine Beschreibung klingt solltest du schon generics verwenden, sorry aber ich hab nicht alle deine Erklaerungen im Detail verstanden.

Ich werf hier einfach mal ein Beispiel in den Raum:
Code:
public abstract class Holder<T>
{
    private T memberVar;

    public Holder(T memberVar)
    {
        if (memberVar == null)
        {
            throw new NullPointerException();
        }
        this.memberVar = memberVar;
    }

    public T getMemberVar()
    {
        return memberVar;
    }

    public void setMemberVar(T memberVar)
    {
        if (memberVar == null)
        {
            throw new NullPointerException();
        }
        this.memberVar = memberVar;
    }
    
    public abstract T addToMember(T t);
}

public class StringHolder extends Holder<String>
{
    
    public StringHolder(String memberVar)
    {
        super(memberVar);
    }

    public String addToMember(String t)
    {
        return getMemberVar().concat(t);
    }
}

public class IntegerHolder extends Holder<Integer>
{
    
    public IntegerHolder(Integer memberVar)
    {
        super(memberVar);
    }

    public Integer addToMember(Integer t)
    {
        return new Integer(getMemberVar().intValue() + t.intValue());
    }
}
 

-frank

Bekanntes Mitglied
hmm, also ich hab es jetzt mal versucht. meine A erweiternden Klassen haben nicht nur Werte eines bestimmten Typs, sondern auch Verweise auf andere A (desselben Typs):

Code:
public abstract class A<V, T extends A<V, T>>

sieht IMO ein wenig kompliziert aus.könnte ich das "T extends A<V,T>" irgendwie anders schreiben oder geht das eh nur so? was ich ausdrücken möchte, ist, dass T vom selben Typ ist wie das jeweilige Objekt selbst.

---

im großen und ganzen klappt es so ganz gut! in einem fall muss ich aber ein Object auf den jeweiligen typ (also V) casten. dies geht typ-sicher leider nur in den subklassen, da ein "return (V) object;" ja unchecked ist. hierfür müsste ich dann doch wieder ein Class-Objekt übergeben, aber ist ja nicht wo entscheidend.

was ich allerdings nicht verstehe:

ich habe in A eine methode
Code:
List<T> getList() {..}

probiert habe ich auch noch
Code:
List<? extends A> getList() {..}
in beiden fällen funktioniert die zeile
Code:
List<? extends A> list = a.getList();
nicht. mir ist klar, dass nicht sicher ist, welchen typ genau die elemente in der liste haben, aber dass es A-Objekte sind müsste doch in beiden variante klar sein. könnt ihr mir sagen, was ich falsche mache (bzw. warum dies nicht möglich ist)?

danke aber in jedem fall! der code scheint mir jetzt besser gelungen.
 

kleiner_held

Top Contributor
Also die Klassensignatur ist schon sehr komplex. Es ist deine Entscheidung, ob die Klasse, auf die verwiesen wird, auch generisch abgebildet sein muss.
Auf alle Faelle solltest du nach deiner Bescheibung
Code:
public abstract class A<V, T extends A<V, ?>>
verwenden, da ansonsten T ein Selbstverweis waere (was nur in wenigen Faellen sinnvoll ist).

Es macht auch keinen Sinn, dass wirklich alle deine Klassen einen Referenz auf eine gleich typisierte Klasse haben, du solltest die Grund-Typisierung und die Relationsbeziehung trennen:
Code:
   public abstract class Basis<V>
    {
    }
    
    public abstract class Container<V, T extends Basis<V>> extends Basis<V> 
    {
        public List<T> getList()
        {
            return null;
        }
    }
    
    public class StringEntry extends Basis<String>
    {
        
    }
    
    public class StringArchiv extends Container<String, StringEntry>
    {
        
    }

    public static void main(String[] args)
    {
        StringArchiv archiv = new StringArchiv();
        List<StringEntry> entries = archiv.getList();
    }

PS: Bei Generalisierung kann man gnaz schnell viel zu komplex werden, so dass dann keiner mehr durchsieht und der Code damit unwartbar wird. Man sollte sich immer ueberlegen, ob man das wirklich braucht, oder ob die Sache zum Selbstzweck wird. Code-Redundanz vermeiden um jeden Preis ist mMn der falsche Weg, manchmal sollte man auch aus Gruenden der Uebersichtlichkeit auf ein High-End Design-Schema verzichten.
siehe auch: KISS
 

-frank

Bekanntes Mitglied
kleiner_held hat gesagt.:
Also die Klassensignatur ist schon sehr komplex. Es ist deine Entscheidung, ob die Klasse, auf die verwiesen wird, auch generisch abgebildet sein muss.
Auf alle Faelle solltest du nach deiner Bescheibung
Code:
public abstract class A<V, T extends A<V, ?>>
verwenden, da ansonsten T ein Selbstverweis waere (was nur in wenigen Faellen sinnvoll ist).

hmm, ein "selbstverweis" war eigentlich das, was ich im sinne hatte. es geht bei mir quasi um pointer. ein klasse hat verweise auf andere klassen desselben typs. ist es dann deiner meinung nach sinnvoll?

Es macht auch keinen Sinn, dass wirklich alle deine Klassen einen Referenz auf eine gleich typisierte Klasse haben, du solltest die Grund-Typisierung und die Relationsbeziehung trennen:

bin nicht 100% sicher, ob ich (richtig) verstanden habe, was du meinst, aber die Trennung verstehe ich schon.
ich hätte aber in deinem Beispiel wirklich nur Container, weil wie gesagt jedes Objekt Verweise auf andere desselben Typs haben kann.

das mit dem KISS prinzip finde ich interessant. ist IMO aber eine uneindeutige geschichte. zb wäre ein komplexerer code mit intelligentem design IMO vielleicht schwerer zu verstehen aber einfacher zu warten, wenn man ihn mal verstanden hat. für mich stellt sich oft auch das problem "einfach zu programmierender code vs. einfach zu verwendender code". mein problem in diesem thread ist eh ein ganz gutes beispiel: am einfachsten für mich wäre es, wenn ich einfach casts mache, wo ich sie benötige. ich weiß eh, dass alle objekte an richtiger stelle sind. aber wenn jemand anderes den code mal verwendet bzw. modifikationen machen will, dann weiß ich ja nicht, was dieser jemand falsch macht. also wollte ich entsprechende fehlermeldungen und typüberprüfungen haben. da entstanden dann viel coderedundanzen. dann hab ichs generisch gemacht. der code selbst ist FÜR MICH jetzt einfacher zu warten und der compiler übernimmt die typüberprüfungen. andererseits ist die klassensignatur aber wie du ja selbst sagst recht komplex geworden und eventuell verwirrend. die dokumentation wird wohl ausführlicher sein müssen, man wird nicht auf den ersten blick kapieren, was da gemacht wird.
ich persönlich weiß jetzt nicht, was die beste lösung ist. da der code noch in progress ist und ich öfters kleine änderungen mache (wobei das eventuell eh kein gutes zeichen, ich weiß...), schätze ich es halt, dass die redundanzen raus sind. aber ob jemand anderes, der den code zum ersten mal zu gesicht bekommt, genauso sieht? keine ahnung...
 

kleiner_held

Top Contributor
-frank hat gesagt.:
hmm, ein "selbstverweis" war eigentlich das, was ich im sinne hatte. es geht bei mir quasi um pointer. ein klasse hat verweise auf andere klassen desselben typs. ist es dann deiner meinung nach sinnvoll?

Also so etwas in der Art?

Code:
public class Test
{
    public static void main(String[] args)
    {
        new Test().start();
    }

    public void start()
    {
        StringContainer stringContainer = new StringContainer();
        IntegerContainer integerContainer = new IntegerContainer();

        List<? extends Container> list = stringContainer.getList();
        list = integerContainer.getList();
    }

    public abstract class Container<V, T extends Container<V, T>>
    {
        List<T> list = new ArrayList<T>();

        public List<T> getList()
        {
            return list;
        }
    }

    public class StringContainer extends Container<String, StringContainer>
    {
    }

    public class AnotherStringContainer extends Container<String, StringContainer>
    {
    }

    public class IntegerContainer extends Container<Integer, IntegerContainer>
    {
    }
}
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben