[JAVA] Polymorfisme

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Bekos
  • Registratie: Maart 2010
  • Laatst online: 20-08 15:09
Hey,

Zou iemand me kunnen helpen met een probleem dat ik al meermaals ben tegengekomen?

Het probleem gaat als volgt:
Stel dat meerdere subklassen overerven van een bepaalde superklasse. In een andere klasse wil ik nu een operatie definiëren die iets anders doet afhankelijk van de subklasse.

Een voorbeeld scenario:

Afbeeldingslocatie: http://i39.tinypic.com/be8ohu.jpg

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Main {
    
    public static void main(String[] args) {
        SuperK sup1 = new SubA();
        SuperK sup2 = new SubB();
        
        String str= null;
        
        str = printenMaar(sup1);
        
        System.out.println(str);
        
        str = printenMaar(sup2);
        
        System.out.println(str);
    }

    private static String printenMaar(SuperK sup1) {
        //Not used (overloading)
        return null;
    }
    
    private static String printenMaar(SubA sup1) {
        return "subA";
    }
    
    private static String printenMaar(SubB sup1) {
        return "subB";
    }
}
 


Dit geeft als resultaat 2 keer null. Terwijl ik als resultaat "subA", "subB" zou willen krijgen. Ik weet dat dit zou kunnen door de printenMaar methode in de subklassen te definiëren en zo met overriding te werken maar dat is hier niet echt de bedoeling. Het is niet mijn bedoeling dat de subklassen weet hebben van de operatie.

De enige oplossingen die ik hiervoor heb kunnen bedenken zijn:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
    
    public static void main(String[] args) {
        SuperK sup1 = new SubA();
        SuperK sup2 = new SubB();
        
        String str= null;
    
        str = printenMaar(sup1);
        
        System.out.println(str);
        
        str = printenMaar(sup2);
        
        System.out.println(str);
    }

 private static String printenMaar(SuperK sup1) {
        if (sup1 instanceof SubA)
            return "SubA";
        else if (sup1 instanceof SubB)
            return "SubB";
        return null;
    }
}


of

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
    
    public static void main(String[] args) {
        SuperK sup1 = new SubA();
        SuperK sup2 = new SubB();
        
        String str= null;
    
        str = printenMaar(sup1);
        
        System.out.println(str);
        
        str = printenMaar(sup2);
        
        System.out.println(str);
    }

 private static String printenMaar(SuperK sup1) {
        if (sup1.getType() ==  SubclassTypes.SubA)
            return "SubA";
        else if (sup1.getType() == SubclassTypes.SubB)
            return "SubB";
        return null;
    }
}

Je kan java als taal meegeven aan de [code=java]-tag ;)

Waarbij de tweede oplossing die met enumeraties werkt in feite een verdoken instanceof is.

Dit zijn natuurlijk geen mooie oplossingen.
Zijn er hiervoor alternatieven waar ik niet aan gedacht heb?

Alvast bedankt,
Bekos

[ Voor 1% gewijzigd door BtM909 op 29-03-2012 14:56 ]


Acties:
  • 0 Henk 'm!

  • Arie-
  • Registratie: December 2008
  • Niet online
Wil je niet gewoon een abstract method in een abstract class definiëren, bij het extenden van deze super class moet je dan de abstract method implementeren in de child. Vervolgens kun je op elk object welke een extensie is van de super dezelfde methode aanroepen omdat deze geïmplementeerd moeten zijn wil je de super class mogen/kunnen extenden.

Edit: argh, niet goed gelezen, overriding is juist wat je niet wilt :F

[ Voor 9% gewijzigd door Arie- op 29-03-2012 14:32 ]


Acties:
  • 0 Henk 'm!

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 08-10 21:07

Macros

I'm watching...

Wat je zoekt is het visitor pattern. Dan moet je een visitor definieren, welke de operator voor alle subklassen definieerd. Deze visitor apply je dan op je subclasse.

Zie voo meer informatie: Wikipedia: Visitor pattern

"Beauty is the ultimate defence against complexity." David Gelernter


Acties:
  • 0 Henk 'm!

  • Jegorex
  • Registratie: April 2004
  • Laatst online: 03-09 23:24
edit: ook gemist dat je geen overriding wilt gebruiken.

[ Voor 91% gewijzigd door Jegorex op 29-03-2012 14:53 ]


Acties:
  • 0 Henk 'm!

  • Bekos
  • Registratie: Maart 2010
  • Laatst online: 20-08 15:09
Bedankt voor de antwoorden!
Macros schreef op donderdag 29 maart 2012 @ 14:22:
Wat je zoekt is het visitor pattern. Dan moet je een visitor definieren, welke de operator voor alle subklassen definieerd. Deze visitor apply je dan op je subclasse.

Zie voor meer informatie: Wikipedia: Visitor pattern
Het probleem met het visitor patroon is dat het effectief een functionaliteit toevoegt aan die objecten zelf. Ik zoek eerder een manier om een methode in een andere klasse anders te laten functioneren afhankelijk van de subklasse. Dit is dus niet zozeer een functionaliteit van de subklassen zelf.

Om een voorbeeld te geven van wanneer ik een soortgelijk probleem al ben tegengekomen in een echt project:
Ik had een aantal use cases gedefinieerd in de aard van "Vraag de resultaten van een Project op.". De resultaten die opgevraagd moesten worden, waren afhankelijk van het type Project. Het was dan de bedoeling om al deze gegevens te verzamelen alvorens ze mee te geven aan controllers en zo aan de domeinlaag.

Acties:
  • 0 Henk 'm!

  • azerty
  • Registratie: Maart 2009
  • Laatst online: 14-10 09:30
Ik herken dit probleem ^^

Helaas kan ik zelf geen antwoord geven, ik heb zelf uiteindelijk instanceof gebruikt en methodes aan de overervende klasses toegevoegd.

Acties:
  • 0 Henk 'm!

  • MonkeySleeve
  • Registratie: Februari 2009
  • Laatst online: 16:19

MonkeySleeve

It's just Monkey business

Je wilt dus aan de hand van de subklasse, een bepaalde methode aanroepen die in de super klasse staat?

Misschien Hook Method gebruiken? Dit zijn methodes die de subklasses niet hoeven te gebruiken, maar ze wel kunnen aanroepen.

Did you bring the banana's? Steam-id: MonkeySleeve


Acties:
  • 0 Henk 'm!

  • Bekos
  • Registratie: Maart 2010
  • Laatst online: 20-08 15:09
ThE_OwNeR schreef op donderdag 29 maart 2012 @ 16:55:
Je wilt dus aan de hand van de subklasse, een bepaalde methode aanroepen die in de super klasse staat?
De methode die ik wil aanroepen staat niet in de superklasse.

Mijn eerste voorbeeld van deze middag was misschien niet zo duidelijk. Een ander voorbeeld:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main { 
     
    public static void main(String[] args) { 
        SuperK sup1 = new SubA(); 
        SuperK sup2 = new SubB(); 
         
       String[] args =  vraagResultatenOp(sup1);
        //Gebruik de resultaten
    } 

    private static String[] vraagResultatenOp(SuperK sup1) { 
        //Not used (overloading) 
        return null; 
    } 
     
    private static String[] vraagResultatenOp(SubA sup1) { 
       //Vraag bijvoorbeeld de leeftijd op aan de gebruiker (via de user interface).
    } 
     
    private static String[] vraagResultatenOp(SubB sup1) { 
       //Vraag bijvoorbeeld de kwaliteit van het project op aan de gebruiker (via de user interface).
    } 
}


Ik zou dus willen dat vraagResultatenOp(...), een methode die niet in SuperK of de subklassen staat, iets anders doet bij een SubA dan bij een SubB.

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Je bent toch gewoon op zoek naar method overloading? Dan moet je natuurlijk wel zorgen dat de objects die je als argument meegeeft van het derived type zijn en niet van de base class.

Acties:
  • 0 Henk 'm!

  • Bekos
  • Registratie: Maart 2010
  • Laatst online: 20-08 15:09
EddoH schreef op donderdag 29 maart 2012 @ 17:21:
Je bent toch gewoon op zoek naar method overloading? Dan moet je natuurlijk wel zorgen dat de objects die je als argument meegeeft van het derived type zijn en niet van de base class.
Klopt. Ik dacht hiervoor overloading te gebruiken (zoals in mijn voorbeeld). Zoals je aangeeft, moet ik dan wel objecten van het derived type meegeven en dat kan ik niet op die plaats in mijn applicatie (tenzij m.b.v. instanceof en een cast).

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

De enige oplossing die ik dan zie is idd een visitor pattern / abstract method in de base die je in de derived implementeert / instanceof en een cast.

Je hoeft natuurlijk niet de volledige functionaliteit in de objecten zelf te maken. Een visitor die een interface naar een andere class meegeeft en het object vervolgens de method uit die interface met zijn eigen type aanroept kan natuurlijk ook.

Acties:
  • 0 Henk 'm!

  • Big Womly
  • Registratie: Oktober 2007
  • Laatst online: 01-09 13:39

Big Womly

Live forever, or die trying

Ik zat eerder te denken om printerobjecten te maken die zijn subclasses kunnen printen, en via een PrinterFactory de juiste printer te laten genereren.

Wat is het voordeel/nadeel van het visitor pattern tov factory?

When you talk to God it's called prayer, but when God talks to you it's called schizophrenia


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Je weet het exacte type van de subclass niet, dus hoe wil je de Factory laten bepalen wat voor printer hij moet maken?

Acties:
  • 0 Henk 'm!

  • Big Womly
  • Registratie: Oktober 2007
  • Laatst online: 01-09 13:39

Big Womly

Live forever, or die trying

Door je factory te laten beslissen welke printer hij moet creëren adhv instanceof?

When you talk to God it's called prayer, but when God talks to you it's called schizophrenia


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Dat kan, maar dan heb je nog steeds instanceof nodig natuurlijk, wat de TS wilde vermijden :)

Acties:
  • 0 Henk 'm!

  • Big Womly
  • Registratie: Oktober 2007
  • Laatst online: 01-09 13:39

Big Womly

Live forever, or die trying

Klopt, maar de instanceof gebeurt dan in een klasse die daar speciaal voor gemaakt is en staat dus niet langer in zijn applicatiecode ;)

Bij het Visitor pattern moet de TS alsnog zijn klasses gaan uitbreiden met functies, wat hij eigenlijk ook niet echt wou.

Zo heb ik het toch begrepen

edit:
typo

[ Voor 39% gewijzigd door Big Womly op 30-03-2012 14:25 ]

When you talk to God it's called prayer, but when God talks to you it's called schizophrenia


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Voor allebei de opties valt iets te zeggen. Persoonlijk hou ik niet zo van instancof icm switch constructies, maar als de TS de object per se niet wil uitbreiden is dat natuurlijk een goed alternatief.

Iig heeft de TS genoeg opties om uit te kiezen :)

Acties:
  • 0 Henk 'm!

  • Bekos
  • Registratie: Maart 2010
  • Laatst online: 20-08 15:09
Ik ben zelf ook niet zo'n fan van instanceof + switches. Omwille van de locatie van de switch (in de interface laag) ga ik daar toch voor kiezen. Een aanpassing aan de objecten vind ik daar wat te ingrijpend voor. De Factory suggestie is ook een goed idee. Hier ga ik het echter houden bij een soort van factory method (ook weer o.w.v. het feit dat het in de interfacelaag zit).

Het resultaat ziet er dan ongeveer als volgt uit (met mijn eerste voorbeeld):
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Main { 
     
    public static void main(String[] args) { 
        SuperK sup1 = new SubA(); 
        SuperK sup2 = new SubB(); 
         
        String str= null; 
         
        str = printenMaar(sup1); 
         
        System.out.println(str); 
         
        str = printenMaar(sup2); 
         
        System.out.println(str); 
    } 

    private static String printenMaar(SuperK sup1) { 
        if(sup1 instanceof SubA){
             printenMaar((SubA) sup1);
        }
        else if (sup1 instanceof SubB){
             printenMaar((SubB) sup1);
        }
        return null;
    } 
     
    private static String printenMaar(SubA sup1) { 
        return "subA"; 
    } 
     
    private static String printenMaar(SubB sup1) { 
        return "subB"; 
    } 
}


Moest iemand nog een ander idee hebben, mag hij/zij dat altijd nog delen ;)

Ik ga al jullie suggesties zeker in gedachte houden voor als ik het probleem nog eens tegenkom :)

Bedankt iedereen!

Acties:
  • 0 Henk 'm!

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 08-10 21:07

Macros

I'm watching...

Er is nog 1 methode. Die is een beetje apart. Je gebruikt method overloading en reflectie.
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Main { 
     
    public static void main(String[] args) { 
        SuperK sup1 = new SubA(); 
        SuperK sup2 = new SubB(); 
         
        String str= null; 
         
        str = printenMaar(sup1); 
         
        System.out.println(str); 
         
        str = printenMaar(sup2); 
         
        System.out.println(str); 
    } 

    private static String printenMaar(SuperK sup1) { 
        try{
            Method method = this.getClass().getDeclaredMethod("printenMaar", sup1.getClass();
            return (String) method.invoke(null, sup1);
        } catch (Exception e)
            e.printStacktrace();
        }
    } 
     
    private static String printenMaar(SubA sup1) { 
        return "subA"; 
    } 
     
    private static String printenMaar(SubB sup1) { 
        return "subB"; 
    } 
}

Dit is alleen ter illustratie. Je gebruikt hier reflectie in combinatie met getClass() om de goede methode op te halen. Reflectie is altijd langzamer dan normaal, maar als performance geen issue is, dan is dit een mooie methode om te doen wat je wilt. Je kan nog spelen met het cachen van de methode bij de class in een Map<Class<?>, Method>, maar dat moet je zelf maar bekijken.

"Beauty is the ultimate defence against complexity." David Gelernter


Acties:
  • 0 Henk 'm!

  • Kwistnix
  • Registratie: Juni 2001
  • Laatst online: 22:34
Serieus, ga geen instanceof vermijden omwille van het feit dat het "volgens men niet zo zou moeten, want niet OO, huil huil" om in plaats daarvan een of andere draconische Visitor oplossing in de lucht hijsen.
Als een instanceof check de eenvoudigste oplossing is, gebruik die dan gewoon als dat de complexiteit reduceert en geen dramatische gevolgen heeft voor de onderhoudbaarheid van de code: wanneer de type hiërarchy waarschijnlijk niet drastisch zal veranderen. Dat is namelijk vele malen belangrijker dat een onnodig complexe oplossing die resulteert uit het dwangmatig vasthouden aan een puristische OO zienswijze. Java biedt nou eenmaal geen double dispatch mechanisme, nou ja jammer dan.

Acties:
  • 0 Henk 'm!

  • Dricus
  • Registratie: Februari 2002
  • Laatst online: 18:30

Dricus

ils sont fous, ces tweakers

Je kunt ook wat OO "magic" gebruiken om instanceof checks te elimineren. Het is echter enorm afhankelijk van de situatie of dit echt de moeite waard is. In de onderstaande oplossing kun je de anonymous inner class ook als losse class implementeren, waardoor printenMaar uit maar 1 regel code hoeft te bestaan. Zie ook het inline commentaar voor meer toelichting.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Implementeer deze interface om gedrag voor ClassA en ClassB te implementeren.
public interface Filter<T> {
    T filter(ClassA input);
    T filter(ClassB input);
}

// Implementeer deze interface in ClassA en ClassB
public interface AcceptFilter {
    <T> T acceptFilter(Filter<T> filter);
}

public class ClassA implements AcceptFilter {
    @Override
    public <T> T acceptFilter(Filter<T> filter) {
        // Doordat hier this wordt meegegeven, wordt de juiste filter overload van de
        // Filter interface aangeroepen.
        return filter.filter(this);
    }
}

public class ClassB implements AcceptFilter {
    @Override
    public <T> T acceptFilter(Filter<T> filter) {
        // Doordat hier this wordt meegegeven, wordt de juiste filter overload van de
        // Filter interface aangeroepen.
        return filter.filter(this);
    }
}

public String printenMaar(AcceptFilter dinges) {
    // Hier roepen we dus acceptFilter aan met een implementatie van Filter, waarmee
    // we voor ClassA en ClassB definiëren wat er teruggegeven moet worden.
    return dinges.acceptFilter(new Filter<String>() {
        @Override
        public String filter(ClassA input) {
            return "ClassA";
        }

        @Override
        public String filter(ClassB input) {
            return "ClassB";
        }
    });
}

Stel niet uit tot morgen wat je vandaag nog tot morgen kunt uitstellen...


Acties:
  • 0 Henk 'm!

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 08-10 21:07

Macros

I'm watching...

Dricus, dat is dus het visitor pattern alleen met wat andere namen en met een generic return type.

"Beauty is the ultimate defence against complexity." David Gelernter


Acties:
  • 0 Henk 'm!

  • Kajel
  • Registratie: Oktober 2004
  • Laatst online: 23-09 09:07

Kajel

Development in Style

Ik ben geneigd het met FallenAngel666 eens te zijn, al zou ik het niet zo in die termen verwoorden ;) Waar het in dit geval op neerkomt: KISS
Waar jij tegenaan loopt, is het feit dat Java bij overloading de method selecteert at compile time, op basis van het type van de reference variable. Dit kan enigszins verwarrend werken, omdat Java in het geval van method overriding, de method selecteert at runtime, op basis van het type van het daadwerkelijke object ("polymorphic method calling"). De constructie die jij dus wilt, is niet mogelijk zonder omwegen. Afhankelijk van de werkelijke situatie, is het misschien gewoon het beste om de kortste route te nemen, in plaats van het construeren van ogenschijnlijk elegante - maar in de praktijk overkill - oplossingen in het kader van dat (op zich nooit verkeerde) OO gevoel.
Als het om een handvol verschillende classes gaat, zou ik gewoon instanceof gebruiken dus :)

Acties:
  • 0 Henk 'm!

  • Dricus
  • Registratie: Februari 2002
  • Laatst online: 18:30

Dricus

ils sont fous, ces tweakers

@Macros: Oh, derp, je hebt gelijk :P.

Ik lees nu trouwens ook dat TS ClassA en ClassB graag ongewijzigd wil laten. In dat geval vind ik persoonlijk de instanceof (met eventueel een cast, zoals TS zelf beschrijft in z'n laatste post) methode de meest wenselijke methode. Het doet precies wat je wilt en vereist geen over-geëngineerde (is dat een woord? :P) class structuren of onnodige reflection truukjes.

Als je goede redenen hebt om voor een meer generieke oplossing te kiezen dan is een Visitor pattern de way to go.

TLDR: Ik sluit me aan bij FallenAngel666 en Kajel.

Stel niet uit tot morgen wat je vandaag nog tot morgen kunt uitstellen...

Pagina: 1