Вопрос по java, polymorphism, generics, overloading – Метод, принимающий два разных типа в качестве параметра

25

Я пишу метод, который должен принимать в качестве своего параметра объект одного из двух типов, которые не разделяют родительский тип, кроме 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   ответов
4

что-то вроде этого:

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>.

6

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 тогда это делает то, что вы хотите

Естественно, это произошло со мной, но использование объекта в качестве параметра кажется неправильным. JohnEye
Вы не повторяете здесь один и тот же код? Не имеет смысла для меня.
29

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), который выполняет всю работу.

2

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

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

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

Как я уже сказал, мой метод довольно длинный, и это будет означать дублирование кода. JohnEye
@JohnEye - тогда вызовите интерфейс только в двух классах. См. Также ответ AadvarkSoup в случае, если вы не можете правильно структурировать иерархию классов.
2

адаптироваться ваши типы к нему.

Интерфейс:

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

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

0

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

1

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

Нет, это только в одном месте. Спасибо за ответ, хотя. JohnEye
1

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

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

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

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

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

Нет, под размышлением я имел в виду использование некоторых изящных функций, чтобы найти метод crush () и выполнить его или что-то в этом роде. Я только написал это, чтобы препятствовать «отражению троллей». от ответа. JohnEye

Похожие вопросы