web-dev-qa-db-fra.com

Méthode abstraite avec liste d'arguments variable

Je n'ai pas encore trouvé un moyen élégant de résoudre ce problème. J'ai une classe abstraite dont plusieurs autres classes héritent avec une méthode abstraite pouvant contenir de zéro à 4-5 arguments de types différents.

public abstract class Item {
public abstract void use();
}

Par exemple, j'ai une classe Book qui hérite de cela et ne prend aucun argument lors de la substitution de use (), j'ai une classe Key qui hérite et prend une chaîne et une file d'attente comme arguments lors de la substitution, etc.

J'ai essayé d'utiliser des génériques mais je dois entrer le numéro utilisé, tel que Item, alors qu'il dépend de la classe.

public abstract class Item<T,U> {
public abstract void use(T arg1, U arg2); //Number of arguments/types could be more or less
}

J'ai essayé d'envoyer une liste d'objets variable, mais les types d'objets sont toujours variables et je ne suis pas sûr de la syntaxe à recevoir dans les classes héritées.

public abstract class Item<T> {
public abstract void use(T... arguments);
}

public class Book extends Item<?> {
public void use(?);
}

public class Book extends Item<String, Queue> { //Wrong number of arguments since I can't use Item<T...>
public void use(String str, Queue q); //fails
}

Je peux simplement faire quelque chose de mal - quelqu'un peut-il offrir une aide ou un aperçu?

17
kaledev

J'ai eu du mal avec la même question, et il n'y a pas de réponse parfaite, mais je peux vous donner quelques points à considérer. Premièrement, vous essayez fondamentalement de faire quelque chose qui est intrinsèquement contraire à la programmation orientée objet, c’est-à-dire que vous essayez de créer une interface variable. Le point d’une interface est que le code qui obtient une version abstraite de l’objet (l’élément plutôt que le livre, par exemple) sait comment invoquer la méthode use (). Cela signifie qu'ils doivent savoir ce qui peut être transmis à la méthode use (). Si la réponse dépend de l'implémentation de la classe abstraite ou de l'interface, vous devez vous assurer que le code qui l'utilise sait réellement quel type d'implémentation (Livre, etc.) il utilise, sinon il ne saura pas comment appeler l'utilisation. () avec les paramètres appropriés quand même. Il semble que vous ayez besoin de reformuler votre code, en toute honnêteté.

Cependant, il existe un moyen de répondre à votre question comme indiqué sans refactoriser l'architecture. Vous pouvez créer une classe dont les données sont tous les types de paramètres pouvant être transmis à la méthode use (), demander au code d'appel de définir les champs de cette classe, puis de le transmettre à la méthode use (). Par exemple:

public class UseParameters {
    private String string;
    private Queue queue;
    // Any other potential parameters to use(...)

    public void setString(String string) {
        this.string = string;
    }

    public String getString() {
        return string;
    }

    // All of the other accessor methods, etc.
}

Ensuite, vous pouvez définir la méthode use dans Item comme ceci:

public abstract void use(UseParameters params);

Et tout code utilisant un élément devrait définir les paramètres de l'objet de manière appropriée:

Item item = // However you're going to get the item
UseParameters params = new UseParameters();
params.setString("good string");
params.setQueue(new Queue());
item.use(params);

Je tiens simplement à souligner que si le code ci-dessus sait que l'élément est un livre (c'est ainsi qu'il sait définir la chaîne et la file d'attente, pourquoi ne pas simplement obtenir un livre et ignorer la nécessité d'une classe abstraite avec une méthode use () variable Quoi qu'il en soit, le livre implémenterait alors la méthode use () comme ceci:

@Override
public void use(UseParameters params) {
    if(params.getString == null || params.getQueue() == null)
        // throw exception

    // Do what books do with strings and queues
}

Je pense que cela vous donne ce que vous voulez, mais vous devriez envisager une refactorisation, je pense.

15
Mindle Hastings

Ce que vous voulez, c'est le Value Object Pattern .

Définissez une classe qui encapsule les divers types de paramètres dans un objet value et demandez à la méthode abstraite d'accepter un paramètre de ce type. Chaque variation de paramètres que vous envisagiez aurait sa propre classe de valeur.

Ensuite, ajoutez simplement un type générique à la classe et demandez à la méthode abstraite d’accepter un paramètre de ce type:

public abstract class Item<V> {
    public abstract void use(V v);
}

Pour l'utiliser, supposons que MyItem a besoin d'un objet de valeur de type MyValueClass:

public class MyItem extends Item<MyValueClass> {
    public void use(MyValueClass v) {
    }
}
8
Bohemian

Si les types à utiliser comme arguments sont toujours variables, je ne vois aucune raison d'utiliser des génériques. Il suffit d'utiliser un type d'objet simple:

public abstract class Item {
    public abstract void use(Object ... arguments);
}

public class Book extends Item {
    public void use(Object ... arguments) { ... }
}
5
morgano

La meilleure approche à laquelle je puisse penser est de regrouper les éléments en fonction du comportement de leur méthode use()

Exemple

public abstract class QueueableItem {
    public abstract void use(String, Queue);
}

public abstract class OrdinaryItem{
    public abstract void use(String);
}

Si les éléments groupés partagent un comportement commun (commun comme dans la même signature de méthode et valeur de retour), vous pouvez définir et étendre une classe parent qui contiendra la définition de ce comportement commun.

1
Bnrdo

Oui, nous pouvons fournir des paramètres à la méthode abstraite, mais nous devons fournir le même type de paramètres aux méthodes implémentées que nous avons écrites dans les classes dérivées.

0
Arun Raaj