Метод, принимающий два разных типа в качестве параметра

Я пишу метод, который должен принимать в качестве своего параметра объект одного из двух типов, которые не разделяют родительский тип, кроме Object. Например, это сны и чеснок. Вы можете сделать обаdreams.crush() а такжеgarlic.crush(), Я хочу иметь методutterlyDestroy(parameter), что бы принять в качестве параметра и Dreams, и Garlic.

utterlyDestroy(parameter) {
    parameter.crush()
}

И чеснок, и сны являются частью какой-то библиотеки, поэтому им нужно реализовать интерфейс ICrushable (чтобы я мог написатьutterlyDestroy(ICrushable parameter) ) не вариант.

Тело моего метода довольно длинное, поэтому его перегрузка означала бы дублирование кода. Некрасиво. Я уверен, что мог бы использовать отражение и сделать некоторый взлом класса. Некрасиво.

Я пытался использовать дженерики, но, видимо, я не могу написать что-то вроде

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

Можно ли типизировать чеснок в мечты?

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

Это все равно будет ужасно. Какие у меня есть другие варианты и какой метод борьбы с ситуацией является предпочтительным?

Ответы на вопрос(8)

Поскольку я использую:

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

Но это работает, только если Type2 может быть преобразован в Type1

Вы можете использовать интерфейс иадаптироваться ваши типы к нему.

Интерфейс:

public interface Crushable {
  public void crush();
}

Пример вызова:

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

Пример адаптера заводского метода:

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() {}
}

Код потребителя выглядит так:

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

Если у вас есть много типов для адаптации, вы можете рассмотреть рефлексию. Вот (неоптимизированная) фабрика адаптеров, которая используетполномочие тип:

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() {}
}

Для потребителя API это не так ужасно:

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

Однако, как обычно с отражением, вы жертвуете проверкой типов во время компиляции.

Как насчет чего-то такого простого?

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();  
   }
} 

ЕслиutterlyDestroy слишком сложный и большой, и вы просто хотите позвонитьcrush тогда это делает то, что вы хотите

Создание интерфейса Crushable кажется самым чистым способом. Является ли подтип «Чеснок» или «Мечты» одним из вариантов и добавляет ли ваш интерфейс к подтипу?

За исключением этого, вы можете поместить общий код в закрытый метод и заставить две версии utterlyDestroy делать то, что они должны делать с отдельными объектами перед вызовом общего кода. Если у вас тело метода длинное, возможно, вам все равно придется разбить его на частные методы. Полагаю, вы уже подумали об этом, поскольку это даже более очевидное решение, чем добавление интерфейса.

Вы можете ввести параметр в качестве объекта и затем привести его. Это то, что вы подразумеваете под отражением? т.е.

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

Но приведение от Чеснока к Мечте не вариант, учитывая, что один не является подтипом другого.

Решение Вопроса

Как насчет этого:

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();});
}

В новой разработке должен быть реализован интерфейс ICrushable, но для существующих классов параметр обернут в ICrushable и передан в utterlyDestroy (ICrushable), который выполняет всю работу.

Просто используйте метод перегрузки.

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

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

Если вы хотите поддерживать более двух типов одним и тем же способом, вы можете определить общий интерфейс для них всех и использовать универсальные шаблоны.

Если вы собираетесь обращаться с ними одинаково во многих местах вашего проекта, я предлагаю обернуть их в класс, что-то вроде адаптера.

Вы можете реализовать Haskell-esque Either-класс в Java; что-то вроде этого:

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;}
}

Затем вы позволяете этому методу принимать что-то типаEither<Dreams, Garlic>.

Ваш ответ на вопрос