web-dev-qa-db-fra.com

Méthode acceptant deux types différents comme paramètre

J'écris une méthode qui devrait accepter comme paramètre un objet de l'un des deux types qui ne partagent pas un type parent autre que Object. Par exemple, les types sont Dreams et Garlic. Vous pouvez faire à la fois dreams.crush() et garlic.crush(). Je veux avoir une méthode utterlyDestroy(parameter), qui accepterait comme paramètre à la fois Dreams et Garlic.

utterlyDestroy(parameter) {
    parameter.crush()
}

L'ail et les rêves font tous deux partie d'une bibliothèque, donc les faire implémenter une interface ICrushable (pour que je puisse écrire utterlyDestroy(ICrushable parameter)) n'est pas une option.

Mon corps de méthode est assez long, donc surcharger cela signifierait dupliquer du code. Laid. Je suis sûr que je pourrais utiliser la réflexion et faire du piratage de classe. Laid.

J'ai essayé d'utiliser des génériques mais apparemment je ne peux pas écrire quelque chose comme

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

Est-il possible de transtyper Garlic to Dreams?

utterlyDestroy(Object parameter) {
    ((Dreams)parameter).crush()
}

Ce serait quand même moche. Quelles sont mes autres options et quelle est la méthode préférée pour faire face à la situation?

27
JohnEye

Que dis-tu de ça:

interface ICrushable {
    void crush();
}

utterlyDestroy(ICrushable parameter) {
    // Very long crushing process goes here
    parameter.crush()
}

utterlyDestroy(Dreams parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

utterlyDestroy(Garlic parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

Un nouveau développement devrait implémenter l'interface ICrushable, mais pour les classes existantes, le paramètre est encapsulé dans un ICrushable et passé à utterlyDestroy (ICrushable) qui fait tout le travail.

31
Devon_C_Miller

Que diriez-vous de quelque chose d'aussi simple que cela?

utterlyDestroy(Object parameter) {    
    if(parameter instanceOf Dreams){  
        Dream dream = (Dreams)parameter;  
        dream.crush();
        //Here you can use a Dream 
    }  
    else if(parameter instanceOf Garlic){  
       Garlic garlic = (Garlic)parameter;   
        //Here you can use a Garlic  
       garlic.crush();  
   }
} 

Si le utterlyDestroy est trop complexe et trop gros et que vous voulez juste appeler le crush alors cela fait ce que vous voulez

6
Cratylus

Vous pouvez implémenter une classe Either de Haskell en Java; quelque chose comme ça:

class Either<L,R>
{
    private Object value;

    public static enum Side {LEFT, RIGHT}

    public Either(L left)  {value = left;}
    public Either(R right) {value = right;}

    public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}

    // Both return null if the correct side isn't contained.
    public L getLeft() {return value instanceof L ? (L) value : null;}
    public R getRight() {return value instanceof R ? (R) value : null;}
}

Ensuite, vous laissez cette méthode prendre quelque chose de type Either<Dreams, Garlic>.

4
AardvarkSoup

Vous pouvez utiliser une interface et adapter vos types à celle-ci.

Interface:

public interface Crushable {
  public void crush();
}

Exemple d'appel:

public class Crusher {
  public static void crush(Crushable crushable) {
    crushable.crush();
  }
}

Exemple de méthode d'usine de l'adaptateur:

public final class Dreams {
  public static Crushable asCrushable(final Dream dream) {
    class DreamCrusher implements Crushable {
      @Override
      public void crush() {
        dream.crush();
      }
    }
    return new DreamCrusher();
  }

  private Dreams() {}
}

Le code de consommation ressemble à ceci:

  Dream dream = new Dream();
  Crushable crushable = Dreams.asCrushable(dream);
  Crusher.crush(crushable);

Si vous avez plusieurs types à adapter, vous pouvez envisager la réflexion. Voici une fabrique d'adaptateurs (non optimisée) qui utilise le type Proxy :

public final class Crushables {
  private static final Class<?>[] INTERFACES = { Crushable.class };

  public static Crushable adapt(final Object crushable) {
    class Handler implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        return crushable.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(crushable, args);
      }
    }

    ClassLoader loader = Thread.currentThread()
        .getContextClassLoader();
    return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
  }

  private Crushables() {}
}

Pour le consommateur d'API, ce n'est pas si moche:

  Dream dream = new Dream();
  Crushable crushable = Crushables.adapt(dream);
  Crusher.crush(crushable);

Cependant, comme d'habitude avec la réflexion, vous sacrifiez la vérification de type à la compilation.

2
McDowell

Utilisez simplement la surcharge de méthode.

public void utterlyDestroy(Dreams parameter) {
    parameter.crush();
}

public void utterlyDestroy(Garlic parameter) {
    parameter.crush();
}

Si vous souhaitez prendre en charge plus de ces deux types de la même manière, vous pouvez définir une interface commune pour tous et utiliser des génériques.

2
Jirka Hanika

La création d'une interface compressible semble être la façon la plus propre de procéder. Le sous-typage Garlic or Dreams est-il une option et l'ajout de votre interface au sous-type?

Sauf cela, vous pouvez mettre du code commun dans une méthode privée et demander aux deux versions de utterlyDestroy de faire ce qu'elles doivent faire aux objets individuels avant d'appeler le code commun. Si votre corps de méthode est long, vous devrez probablement le décomposer en méthodes privées de toute façon. Je suppose que vous y avez déjà pensé, car c'est une solution encore plus évidente que l'ajout d'une interface.

Vous pouvez importer le paramètre en tant qu'objet, puis le convertir. Est-ce ce que tu veux dire par réflexion? c'est à dire.,

public void utterlyCrush(Object crushable) {
    if (crushable instanceOf Dream) {
         ...
    }
    if (curshable instanceOf Garlic) {
         ...
    }

Mais couler de Garlic to Dream n'est pas une option étant donné que l'un n'est pas un sous-type de l'autre.

1
Phil Freihofner

Si vous allez les traiter de la même manière dans de nombreux endroits de votre projet, je vous suggère de les envelopper dans une classe, quelque chose comme un adaptateur.

1
Sergio Nakanishi

Comme j'utilise:

void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}

Mais cela ne fonctionne que si Type2 peut être converti en Type1

0
Charlie