Многомерный метод Java?

Интересно, можно ли требовать, чтобы параметр java-метода был любого типа из конечного набора типов. Например, я использую библиотеку, где два (или более) типа имеют общие методы, но их младшим общим предком в иерархии типов является Object. Что я имею в виду здесь:

   public interface A {
      void myMethod();
   }

   public interface B {
      void myMethod();
   }
...
   public void useMyMethod(A a) {
      // code duplication
   }

   public void useMyMethod(B b) {
      // code duplication
   }

Я хочу избежать дублирования кода. Я думаю о чем-то вроде этого:

   public void useMyMethod(A|B obj){
      obj.myMethod();
   }

В java уже есть аналогичный тип синтаксиса. Например:

  try{
     //fail
  } catch (IllegalArgumentException | IllegalStateException e){
     // use e safely here
  }

Очевидно, это невозможно. Как я могу достичь хорошо продуманного кода, используя такую ​​иерархию типов неидентифицируемого типа?

Ответы

Ответ 1

Вы можете написать interface MyInterface одним методом myMethod. Затем для каждого типа, который вы хотите рассматривать как часть конечного набора, напишите класс-оболочку, например:

class Wrapper1 implements MyInterface {

    private final Type1 type1;

    Wrapper1(Type1 type1) {
        this.type1 = type1;
    }

    @Override
    public void myMethod() {
        type1.method1();
    }
}

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

Обратите внимание, что для фактического использования этих классов-оболочек для вызова метода myMethod вам нужно написать

myMethod(new Wrapper1(type1));

Это будет немного уродливо, так как вам нужно будет запомнить имя класса-оболочки для каждого типа в наборе. По этой причине вы можете заменить MyInterface абстрактным классом с несколькими статическими фабриками, которые производят типы обертки. Вот так:

abstract class MyWrapper {

    static MyWrapper of(Type1 type1) {
        return new Wrapper1(type1);
    }

    static MyWrapper of(Type2 type2) {
        return new Wrapper2(type2);
    }

    abstract void myMethod();
}

то вы можете вызвать метод, используя код

myMethod(MyWrapper.of(type1));

Преимущество такого подхода заключается в том, что код тот же, независимо от того, какой тип вы используете. Если вы используете этот подход, вам нужно заменить implements MyInterface в объявлении Wrapper1 на extends MyWrapper.

Ответ 2

Как передать функцию в качестве параметра вашей функции useMyMethod?

Если вы используете Java < 8:

public interface A {
    void myMethod();
}

public interface B {
    void myMethod();
}

public void useMyMethod(Callable<Void> myMethod) {
    try {
        myMethod.call();
    } catch(Exception e) {
        // handle exception of callable interface
    }
}

//Use

public void test() {
    interfaceA a = new ClassImplementingA();
    useMyMethod(new Callable<Void>() {
        public call() {
            a.myMethod();
            return null;
        }
    });

    interfaceB b = new ClassImplementingB();
    useMyMethod(new Callable<Void>() {
        public call() {
            b.myMethod();
            return null;
        }
    });
}

Для Java >= 8 вы можете использовать Лямбда-выражения:

public interface IMyMethod {
    void myMethod();
}

public void useMyMethod(IMyMethod theMethod) {
    theMethod.myMethod();
}

//Use

public void test() {
    interfaceA a = new ClassImplementingA();
    useMyMethod(() -> a.myMethod());

    interfaceB b = new ClassImplementingB();
    useMyMethod(() -> b.myMethod());
}

Ответ 3

Попробуйте использовать шаблон Adapter.

Или, если это возможно, добавьте базовый интерфейс:

public interface Base {
    void myMethod();
}

public interface A extends Base {}
public interface B extends Base {}
...
public void useMyMethod(Base b) {
    b.myMethod()
}

Кроме того, вы можете использовать что-то похожее на this

Ответ 4

Ну, правильный способ моделирования вашего требования состоял бы в том, чтобы myMethod() объявлялся в супертипном интерфейсе C, который расширяется как A, так и B; ваш метод затем принимает тип C в качестве параметра. Тот факт, что у вас возникли проблемы с этим в описанной вами ситуации, указывает, что вы не моделируете иерархию классов таким образом, чтобы они действительно отражали, как они себя ведут.

Конечно, если вы не можете изменить структуру интерфейса, вы всегда можете сделать это с помощью отражений.

public static void useMyMethod(Object classAorB) throws Exception {
    classAorB.getClass().getMethod("myMethod").invoke(classAorB);
}

Ответ 5

Это может быть не самая лучшая практика, но вы можете создать новый класс (назовите его C), который содержит части из A и B, которые дублируются, и сделать новый метод, который принимает C, иметь ваши методы, которые взять A и B сделать экземпляр C и вызвать новый метод?

Итак, у вас есть

class C {
    // Stuff from both A and B
}

public void useMyMethod(A a) {
    // Make a C
    useMyMethod(c);
}

public void useMyMethod(B b) {
    // Make a C
    useMyMethod(c);
}

public void useMyMethod(C c) {
    // previously duplicated code
}

Это также позволит вам сохранить любой не дублированный код в методах для A и B (если они есть).

Ответ 6

Это выглядит очень похоже на шаблон шаблона:

public interface A {

    void myMethod();
}

public interface B {

    void myMethod();
}

public class C {

    private abstract class AorBCaller {

        abstract void myMethod();

    }

    public void useMyMethod(A a) {
        commonAndUseMyMethod(new AorBCaller() {

            @Override
            void myMethod() {
                a.myMethod();
            }
        });
    }

    public void useMyMethod(B b) {
        commonAndUseMyMethod(new AorBCaller() {

            @Override
            void myMethod() {
                b.myMethod();
            }
        });
    }

    private void commonAndUseMyMethod(AorBCaller aOrB) {
        // ... Loads of stuff.
        aOrB.myMethod();
        // ... Loads more stuff
    }
}

В Java 8 это намного более красноречиво:

public class C {

    // Expose an "A" form of the method.
    public void useMyMethod(A a) {
        commonAndUseMyMethod(() -> a.myMethod());
    }

    // And a "B" form.
    public void useMyMethod(B b) {
        commonAndUseMyMethod(() -> b.myMethod());
    }

    private void commonAndUseMyMethod(Runnable aOrB) {
        // ... Loads of stuff -- no longer duplicated.
        aOrB.run();
        // ... Loads more stuff
    }
}

Ответ 7

Динамический прокси можно использовать для создания моста между общим интерфейсом, который вы определяете, и объектами, реализующими другие интерфейсы, которые соответствуют новому интерфейсу. Затем вы можете преобразовать этот useMyMethod в новый интерфейс (как динамический прокси) и использовать свой общий код в терминах только нового интерфейса.

Это будет новый интерфейс:

interface Common {
  void myMethod();
}

Затем с помощью этого обработчика вызовов:

class ForwardInvocationHandler implements InvocationHandler {
  private final Object wrapped;
  public ForwardInvocationHandler(Object wrapped) {
    this.wrapped = wrapped;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Method match = wrapped.getClass().getMethod(method.getName(), method.getParameterTypes());
    return match.invoke(wrapped, args);
  }
}

У вас могут быть такие методы:

public void useMyMethod(A a) {
  useMyMethod(toCommon(a));
}

public void useMyMethod(B b) {
  useMyMethod(toCommon(b));
}

public void useMyMethod(Common common) {
  // ...
}

private Common toCommon(Object o) {
  return (Common)Proxy.newProxyInstance(
    Common.class.getClassLoader(), 
    new Class[] { Common.class }, 
    new ForwardInvocationHandler(o));   
}

Обратите внимание, что для упрощения вопросов вы можете выбрать один из существующих интерфейсов (A или B), который будет использоваться в качестве общего интерфейса.

(Посмотрите на другой пример здесь, а также на другие идеи по этому вопросу)