Héritage en Java et instanceof

L'auteur de ce sujet a trouvé une solution à son problème.
Staff
Auteur du sujet

Salut tout le monde,

J'ai récupéré un projet avec pas mal d'historique de code, dont une grande quantité de structures de ce type :

 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
45
46
47
48
49
50
51
52
53
package com.spacefox.tests.com.spacefox.tests.lib;

public abstract class AbstractClass {
    private String data;

    public AbstractClass(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void doSomethingCommon(String parameter) {
        System.out.println("Abstract class with parameter " + parameter);
    }
}

// - - - - - - - - - - Nouveau fichier - - - - - - - - - - 

package com.spacefox.tests.com.spacefox.tests.lib;

public class ConcreteClass2 extends AbstractClass {
    public ConcreteClass2(String data) {
        super(data);
    }

    public void doSomethingSpecificToConcreteClass2(int parameter) {
        System.out.println("Concrete class 2 with data " + getData() + " and parameter " + parameter);
    }
}

// - - - - - - - - - - Nouveau fichier - - - - - - - - - - 

package com.spacefox.tests.com.spacefox.tests.lib;

public class ConcreteClass1 extends AbstractClass {
    public ConcreteClass1(String data) {
        super(data);
    }

    public void doSomethingSpecificToConcreteClass1() {
        System.out.println("Concrete class 1 with data " + getData());
    }
}

// - - - - - - - - - - Nouveau fichier - - - - - - - - - - 

package com.spacefox.tests.com.spacefox.tests.lib;

public interface ClassUser {
    void doSomethingWith(AbstractClass something);
}

Ce code est utilisé de cette manière :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
AbstractClass class1a = new ConcreteClass1("Class 1 object A");
AbstractClass class1b = new ConcreteClass1("Class 1 object B");
AbstractClass class2a = new ConcreteClass2("Class 2 object C");
AbstractClass class2b = new ConcreteClass2("Class 2 object D");

System.out.println("Intsanceof implementation");
ClassUser instanceofImplementation = new ClassUser() {
    @Override
    public void doSomethingWith(AbstractClass something) {
        something.doSomethingCommon("Bla");
        if (something instanceof ConcreteClass1) {
            ((ConcreteClass1) something).doSomethingSpecificToConcreteClass1();
        } else if (something instanceof ConcreteClass2) {
            ((ConcreteClass2) something).doSomethingSpecificToConcreteClass2(42);
        }
    }
};
instanceofImplementation.doSomethingWith(class1a);
instanceofImplementation.doSomethingWith(class1b);
instanceofImplementation.doSomethingWith(class2a);
instanceofImplementation.doSomethingWith(class2b);

Ce qui me donne :

1
2
3
4
5
6
7
8
9
Intsanceof implementation
Abstract class with parameter Bla
Concrete class 1 with data Class 1 object A
Abstract class with parameter Bla
Concrete class 1 with data Class 1 object B
Abstract class with parameter Bla
Concrete class 2 with data Class 2 object C and parameter 42
Abstract class with parameter Bla
Concrete class 2 with data Class 2 object D and parameter 42

Or, les batteries de instanceof ne me plaisent pas, parce que ça pose des tas de problèmes (comportements incohérents à cause de instanceof perdus au fond de sous-classes et inconnus de tous, par exemple).

Est-ce que quelqu'un a une solution plus intelligente que la suivante pour supprimer ces instanceof ?
Je modifie l'interface de ClassUser et assez lourdement l'utilisation. Les classes non réécrites restent les mêmes.

 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
package com.spacefox.tests.com.spacefox.tests.lib;

public interface ClassUser {
    void doSomethingWith(ConcreteClass1 something);
    void doSomethingWith(ConcreteClass2 something);
}

// - - - - - - - - - - Nouveau fichier - - - - - - - - - - 
// Utilisation :
ConcreteClass1 class1a = new ConcreteClass1("Class 1 object A");
ConcreteClass1 class1b = new ConcreteClass1("Class 1 object B");
ConcreteClass2 class2a = new ConcreteClass2("Class 2 object C");
ConcreteClass2 class2b = new ConcreteClass2("Class 2 object D");

System.out.println("Specified interface implementation");
ClassUser specifiedInterfaceImplementation = new ClassUser() {
    @Override
    public void doSomethingWith(ConcreteClass1 something) {
        commonProcess(something);
        something.doSomethingSpecificToConcreteClass1();
    }

    @Override
    public void doSomethingWith(ConcreteClass2 something) {
        commonProcess(something);
        something.doSomethingSpecificToConcreteClass2(42);

    }

    private void commonProcess(AbstractClass something) {
        something.doSomethingCommon("Bla");
    }
};

specifiedInterfaceImplementation.doSomethingWith(class1a);
specifiedInterfaceImplementation.doSomethingWith(class1b);
specifiedInterfaceImplementation.doSomethingWith(class2a);
specifiedInterfaceImplementation.doSomethingWith(class2b);

Hello,

Je poste juste pour dire que j'aime la méthode que tu proposes et qu'elle me parait cohérente. Une solution à ton problème serait d'utiliser le pattern Visitor.

Tu as des structures de données distinctes et non seulement tu ne sais pas quel traitement tu veux leur octroyer pour le moment, mais en plus tu voudrais peut-être leur octroyer un traitement supplémentaire plus tard. L'idée, c'est d'avoir à chaque traitement son visiteur.

C'est ce qui me paraît le plus cohérent, en tout cas. :)

+2 -0

Je ne sais pas s'il existe des solutions miracle pour "éliminer" les instanceof, j'aurais peut-être fait comme toi, sauf :

1
2
3
    private void commonProcess(AbstractClass something) {
        something.doSomethingCommon("Bla");
    }

Tu peux utiliser la signature " @Override public void doSomethingWith(AbstractClass something) " pour le cas par défaut.

PS: c'est toujours mieux que le gars (un indien ? je sais plus) qui a tout codé dans une seule classe non ?

Miagiste. A la recherche d'un emploi sur Paris (QlikView ou Java). En savoir plus sur moi : https://gokan-ekinci.appspot.com

+1 -0
Staff
Auteur du sujet

Merci à vous !

Ge0, je vais voir pour le pattern Visitor mais je crains de ne pas avoir le temps de faire des modifications aussi lourdes.

Tu peux utiliser la signature " @Override public void doSomethingWith(AbstractClass something) " pour le cas par défaut.

Gugelhupf

En fait non. Ma première idée ressemblait à ça, sauf que ça ne marche pas. En Java (je ne sais pas dans d'autres langage), si tu as :

1
2
3
public void method(AbstractClass something) { /* stuff here */ }
public void method(ConcreteClass1 something) { /* stuff here */ }
public void method(ConcreteClass2 something) { /* stuff here */ }

Les méthodes 2 et 3 avec les classes spécialisées ne seront jamais utilisées, ce sera toujours celle avec la classe abstraite qui sera utilisée.

C'est le contraire : http://ideone.com/Jdl8w6

 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
import java.util.*;
import java.lang.*;
import java.io.*;

abstract class A {
  public void coucou(){
    System.out.println("Je suis A");
  } 
}

class B extends A {
  @Override
  public void coucou(){
    System.out.println("Je suis B");
  } 
}

class C extends A {
  @Override
  public void coucou(){
    System.out.println("Je suis C");
  } 
}

class UneClasse {
  public void uneMethode(A a){
    a.coucou();
  }
}

class Ideone {
  public static void main (String[] args) throws java.lang.Exception {
    UneClasse c = new UneClasse();
    c.uneMethode(new A(){});
    A a1 = new B();
    c.uneMethode(a1);
    A a2 = new C();
    c.uneMethode(a2);
  }
}

Output :

1
2
3
Je suis A
Je suis B
Je suis C

Miagiste. A la recherche d'un emploi sur Paris (QlikView ou Java). En savoir plus sur moi : https://gokan-ekinci.appspot.com

+0 -1
Staff
Auteur du sujet

Attention, le cas que tu présentes n'est pas le mien !

Là tu présentes la surcharge de méthode dans les classes enfant, ça bien sûr que ça fonctionne.

Ce que j'expose, c'est plusieurs surcharges polymorphiques de la même méthode et au sein de la même classe.

Exemple avec mon code du premier post :

 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
AbstractClass class1a = new ConcreteClass1("Class 1 object A");
AbstractClass class1b = new ConcreteClass1("Class 1 object B");
AbstractClass class2a = new ConcreteClass2("Class 2 object C");
AbstractClass class2b = new ConcreteClass2("Class 2 object D");

System.out.println("Methods implementation");
ClassUser methodsImplementation = new ClassUser() {
    @Override
    public void doSomethingWith(AbstractClass something) {
        something.doSomethingCommon("Bla");
        System.out.println("ERROR No behaviour defined!");
    }
    public void doSomethingWith(ConcreteClass1 something) {
        something.doSomethingCommon("Bla");
        something.doSomethingSpecificToConcreteClass1();
    }
    public void doSomethingWith(ConcreteClass2 something) {
        something.doSomethingCommon("Bla");
        something.doSomethingSpecificToConcreteClass2(42);
    }
};
methodsImplementation.doSomethingWith(class1a);
methodsImplementation.doSomethingWith(class1b);
methodsImplementation.doSomethingWith(class2a);
methodsImplementation.doSomethingWith(class2b);

Sortie :

1
2
3
4
5
6
7
8
9
Methods implementation
Abstract class with parameter Bla
ERROR No behaviour defined!
Abstract class with parameter Bla
ERROR No behaviour defined!
Abstract class with parameter Bla
ERROR No behaviour defined!
Abstract class with parameter Bla
ERROR No behaviour defined!

Et soit j'ai mal compris ce que tu proposes, soit c'est le cas dans lequel je me retrouve avec ta proposition d'utiliser @Override public void doSomethingWith(AbstractClass something).

Bonjour,

Votre approche n'a malheureusement aucune chance d'aboutir sans l'usage de instanceof. En effet vous passez dans la méthode doSomethingWith() un AbstractClass qui n'expose que le service :

1
public void doSomethingCommon(String parameter)

Vous ne pourrez donc à partir de là qu'appeler something.doSomethingCommon(unString)

Et ce n'est pas votre tentative d'emploi des surcharges

1
2
public void doSomethingWith(ConcreteClass1 something)
public void doSomethingWith(ConcreteClass2 something)

qui va changer quelque chose. En effet vous manipulez de base des AbstractClass sur ces lignes :

1
2
3
4
AbstractClass class1a = new ConcreteClass1("Class 1 object A");
AbstractClass class1b = new ConcreteClass1("Class 1 object B");
AbstractClass class2a = new ConcreteClass2("Class 2 object C");
AbstractClass class2b = new ConcreteClass2("Class 2 object D");

Il vous faudra de même d'abord downcaster ces AbstractClass en ConcreteClass1/2 avant de pouvoir les appeler.

La seule bonne solution réside dans l'utilisation du mécanisme de double dispatch présent dans le pattern Visitor que vous a présenté Ge0.

Édité par Xavier45

+0 -0

Ah oui SpaceFox, désolé boulette de ma part, je viens de faire un test et ça ne fonctionne que si le pointeur correspond à la signature de la méthode (nouveau test : http://ideone.com/aXx6I8 ) :

 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
45
46
import java.util.*;
import java.lang.*;
import java.io.*;

abstract class A {
    public void coucou(){
        System.out.println("Je suis A");
    }   
}

class B extends A {
    @Override
    public void coucou(){
        System.out.println("Je suis B");
    }   
}

class C extends A {
    @Override
    public void coucou(){
        System.out.println("Je suis C");
    }   
}

class UneClasse {
    public void uneMethode(A a){
        System.out.println("Signature (A) : ");
        a.coucou();
    }

    public void uneMethode(B a){
        System.out.println("Signature (B) : ");
        a.coucou();
    }
}

class Ideone {
    public static void main (String[] args) throws java.lang.Exception {
        UneClasse c = new UneClasse();
        c.uneMethode(new A(){});
        B a1 = new B();
        c.uneMethode(a1);
        C a2 = new C();
        c.uneMethode(a2);
    }
}

Output :

1
2
3
4
5
6
Signature (A) : 
Je suis A
Signature (B) : 
Je suis B
Signature (A) : 
Je suis C

Perso je ne suis pas trop fan du pattern Visitor, un pti pattern Strategy peut-être ? (Test : http://ideone.com/FtCTcv )

 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
45
46
@FunctionalInterface
interface ShiaLabeouf{
    void justDoIt();
}
class DoA implements ShiaLabeouf{
    public void justDoIt(){
        System.out.println("Yesterday you said tomorrow !");
    }
}

abstract class ClassUser {
    private ShiaLabeouf s;
    public ClassUser(ShiaLabeouf s){
        this.s = s;
    }
    public void doIt(){
        s.justDoIt();
    }
}

class Test {
    public static void main(String[] args){
        ClassUser cu1 = new ClassUser(new DoA()){
            // D'autres trucs ici
        };

        ClassUser cu2 = new ClassUser(new ShiaLabeouf(){
            @Override
            public void justDoIt(){
                System.out.println("Nothing is impossible");
            }
        }){
            // D'autres trucs ici
        };

        ClassUser cu3 = new ClassUser(() -> {
            System.out.println("Make your dreams come true");
        }){
            // D'autres trucs ici
        };

        cu1.doIt();
        cu2.doIt();
        cu3.doIt();
    }
}

Miagiste. A la recherche d'un emploi sur Paris (QlikView ou Java). En savoir plus sur moi : https://gokan-ekinci.appspot.com

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte