Как интерфейс может включать метод, который ссылается на конкретный тип реализации интерфейса в его сигнатуре или типе возврата? - PullRequest
14 голосов
/ 30 сентября 2011

Предположим, я проектирую что-то вроде следующего интерфейса:

public interface MyInterface{
  public MyInterface method1();
  public void method2(MyInterface mi);
}

Однако есть предостережение, что тип возвращаемого значения для method1 и параметр для method2 соответствуют конкретной реализации, а не только MyInterface. То есть, если у меня есть MyInterfaceImpl, который реализует MyInterface, он должен иметь следующее:

public class MyInterfaceImpl implements MyInterface{
  @Override
  public MyInterfaceImpl method1(){...}

  @Override
  public void method2(MyInterfaceImpl mi){...}
}

Как написано выше, method1 не вызовет ошибок компиляции, но нет ничего, что гарантировало бы, что возвращаемый тип совпадает во всех реализациях. Конечно, method2 даже не скомпилируется, потому что подпись не соответствует интерфейсу.

Одним из возможных решений является использование самообращенных или рекурсивных границ в обобщенных типах:

public interface MyInterface<T extends MyInterface<T>>{
  public T method1();
  public void method2(T mi);
}

public class MyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
  @Override
  public MyInterfaceImpl method1();

  @Override
  public void method2(MyInterfaceImpl mi);
}

Это даст мне то, что я хочу, с одним исключением: другие реализации могут передавать неправильный универсальный тип (ничто не заставляет T соответствовать конкретному типу). Таким образом, потенциально кто-то еще может реализовать следующее:

public class NotMyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
  @Override
  public MyInterfaceImpl method1();

  @Override
  public void method2(MyInterfaceImpl mi);
} 

Это скомпилируется просто отлично, хотя NotMyInterfaceImpl должно реализовать MyInterface<NotMyInterfaceImpl>. * Это заставляет меня думать, что мне нужно что-то еще.

* Обратите внимание, что я не думаю, что пытаюсь нарушить LSP; Я в порядке с типом возвращаемого значения / параметром, являющимся подклассами NotMyInterfaceImpl.

Так что я не знаю чистого способа сделать это. Это наводит меня на мысль, что я мог бы слишком сосредоточиться на деталях реализации в интерфейсе, но мне так не кажется. Есть ли способ сделать то, что я описал, или это какой-то запах, который я помещаю в интерфейс, который там не принадлежит?

Ответы [ 5 ]

13 голосов
/ 30 сентября 2011

Это именно та ситуация, с которой сталкивается интерфейс Comparable (его метод compareTo хочет получить аргумент того же типа, что и объект, для которого он вызывается).Так что же это делает?Это просто определяется как Comparable<T>.Идея состоит в том, что реализующий класс «должен» реализовать Comparable с самим собой в качестве параметра (что позволяет ему «сравнивать» с самим собой);но это не применяется (так как нет способа сделать это).

Да, как вы заметили, это позволит любому классу реализовать Comparable с параметром любого другого класса: class Foo implements Comparable<Bar> гдеFoo и Bar не имеют никакого отношения друг к другу.Однако на самом деле это не проблема.

Все методы и классы (сортировка, максимум и т. Д.), Которым требуются Comparable объекты, имеют следующее ограничение общего типа <T extends Comparable<? super T>>.Это гарантирует, что объекты типа T сравнимы между собой.Таким образом, он полностью безопасен для типов.Таким образом, принудительное применение выполняется не в объявлении интерфейса Comparable, а в местах, которые его используют.

(Я заметил, что вы используете <T extends MyInterface<T>>, а Comparable использует просто <T>. Хотя <T extends MyInterface<T>> исключит случаи, когда параметр типа не реализует MyInterface, он не исключит случаи, когда параметр типа действительно реализует MyInterface, но отличается от класса. Так какой смысл наполовину исключать некоторые случаи?принять Comparable способ ограничить его там, где они используются, в любом случае он безопасен от типа, поэтому нет смысла добавлять дополнительные ограничения.)

2 голосов
/ 30 сентября 2011

Я считаю, что это невозможно сделать. Просто нет способа сослаться на класс реализации объекта в рамках обобщенных элементов, и, насколько мне известно, нет способа создать клетку из чистых обобщенных элементов, которая способна ограничить класс реализации для соответствия параметру типа.

Самая полезная вещь, которую я могу предложить, - это использование параметра со ссылкой на себя, а затем всегда получать экземпляры реализаций из фабричных методов, которые выглядят следующим образом:

public <T extends MyInterface<T>> T newInstance();

Верблюду легче пройти через игольное ушко, чем в случае NotMyInterfaceImpl через этот тип возврата. Таким образом, хотя нарушители спокойствия могли писать классы, которые не соответствуют вашему генеральному плану, они не могли вернуть их с заводов. Если NotMyInterfaceImpl не расширен MyInterfaceImpl; но тогда, в некотором смысле, это также будет MyInterfaceImpl, так что, возможно, это будет кошерным?

РЕДАКТИРОВАТЬ: чуть более полезная версия этой идеи состоит в том, чтобы всегда передавать экземпляры реализаций интерфейса в подходящем ограничительном держателе, например:

class Holder<T extends MyInterface<T>> {
    public final T value;
}

Если кто-то дает вам Holder<Q>, то вы знаете, что Q должна быть версией MyInterface, связанной с самим собой, а это то, что вам нужно.

0 голосов
/ 30 сентября 2011

Это то, что вы ищете?

public interface MyInterface {

    static abstract class MyInterfaceImpl implements MyInterface {

        @Override
        public abstract MyInterfaceImpl method1();

        @Override
        public abstract void method2(MyInterfaceImpl mi);

    }    

    MyInterfaceImpl method1();
    void method2(MyInterfaceImpl mi);

}

И вы можете даже реализовать метод 1 или 2 вместо того, чтобы делать их абстрактными.

0 голосов
/ 30 сентября 2011

Точка возврата интерфейса такова, что метод не заботится о фактической реализации возвращаемого объекта.В вашем случае вы на самом деле хотите, чтобы указали, что тип является частичной реализацией этого интерфейса.

Чтобы применить ограничения, которые вы описали выше, ИМХО дизайн, вероятно, должен быть основойкласс вместо интерфейса.Это позволяет вам контролировать реализацию, например поток верхнего уровня, и оставлять низкоуровневую стратегию для реализации подклассами:

class MyBaseImpl {
    public final void fixedFlow() {
        MyBaseImpl obj = method1();
        obj.method2(this);
    }
    protected abstract MyBaseImpl method1();
    ....
}

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

Надеюсь, это поможет!

0 голосов
/ 30 сентября 2011

То, что вы пытаетесь сделать, не является законным, потому что вы пытаетесь сузить параметр реализованного типа, а это «не имеет смысла» . Вы пытаетесь использовать "ковариантные" параметры , и разрешены только ковариантные типы возврата (и даже логика, и только поддерживается из Java 5 ).

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

MyInterface instance = new MyInterfaceImpl();

И затем вызовите «экземпляр» метода с другой реализацией, поддерживаемой интерфейсом, но не поддерживаемой классом MyInterfaceImpl, следующим образом:

instance.method2(new MyInterfaceImpl_2());

Java не может преобразовать MyInterfaceImpl_2 в MyInterfaceImpl, поэтому он не позволяет вам сделать это во время компиляции.

Что вы могли бы сделать, это расширить параметр, используя «контравариантный» параметр , который был бы логическим. Для более подробной информации, проверьте этот anser:

Продемонстрировать ковариацию и контравариантность в Java?

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

public class MyInterfaceImpl implements MyInterface{
  @Override
  public void method2(MyInterface mi){
        realMethod((MyInterfaceImpl) mi);
  }
  public void realMethod(MyInterfaceImpl) {...}
}

Но, конечно, вы можете получить исключение ClassCast.

...