Подтвердить что ты не робот

Могут ли интерфейсы развиваться со временем?

Интерфейсы великолепны с точки зрения гибкости. Но в случае, если интерфейс используется большим количеством клиентов. Добавление новых методов в интерфейс при сохранении старых недействительных mehtods приведет к потере кода всех клиентов, поскольку новые методы не будут присутствовать в клиентах. Как показано ниже:

public interface CustomInterface {

    public void method1();

}

public class CustomImplementation implements CustomInterface {

    @Override
    public void method1() {
        System.out.println("This is method1");
    }
}

Если в какой-то момент позже мы добавим еще один метод для этого интерфейса, код всех клиентов сломается.

public interface CustomInterface {

    public void method1();

    public void method2();

}

Чтобы этого избежать, мы должны явно реализовать новые методы во всех кодах клиентов.

Итак, я считаю интерфейсы и этот сценарий следующим:

  • Интерфейсы, написанные однажды, похожи на резьбу по камню. Они редко предполагаются и, как ожидается, будут меняться. И если они это сделают, они приходят с огромными затратами (переписывая весь код), для которых программисты должны быть готовы.
  • В продолжение вышеизложенного можно ли написать интерфейсы, которые могут выдержать испытание временем?
  • Как такой сценарий обрабатывается в интерфейсах, где вы ожидаете дополнительных функций в будущем? Это предполагает изменение в контракте, в котором привязаны все клиенты.

EDIT: Default метод действительно является хорошим дополнением к интерфейсам Java, о которых многие люди упомянули в своих ответах. Но мой вопрос был больше в контексте разработки кода. И как реализация метода принудительного выполнения на клиенте является внутренним характером интерфейса. Но этот контракт между интерфейсом и клиентом кажется хрупким, поскольку функциональность в конечном итоге будет развиваться.

4b9b3361

Ответ 1

Одно из решений этой проблемы было введено в Java 8 в виде методов по умолчанию в интерфейсах. Это позволило добавить новые методы к существующим интерфейсам Java SE без нарушения существующего кода, поскольку он предоставил стандартную реализацию всем новым методам.

Например, интерфейс Iterable, который широко используется (это супер интерфейс интерфейса Collection), был добавлен два новых метода по умолчанию - default void forEach(Consumer<? super T> action) и default Spliterator<T> spliterator().

Ответ 2

public interface CustomInterface {
    public void method1();
}

public interface CustomInterface2 extends CustomInterface {
    public void meathod2();
}

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

Ответ 3

Java 8 представила стандартную реализацию методов. Эти реализации находятся в интерфейсе. Если новый метод с реализацией по умолчанию создается в интерфейсе, который уже реализован многими классами, нет необходимости изменять все классы, но только те, которые мы хотим иметь для другой реализации для нового метода, по умолчанию.

Теперь, что относительно старых версий Java? Здесь мы можем иметь другой интерфейс, который расширяет первый. После этого классы, которые мы хотим реализовать вновь объявленный метод, будут изменены для реализации нового интерфейса. Как показано ниже.

public interface IFirst {
    void method1();
}

public class ClassOne implements IFirst() {
    public void method1();
}

public class ClassTwo implements IFirst() {
    public void method1();
}

Теперь мы хотим объявить method2(), но это должно быть реализовано только ClassOne.

public interface ISecond extends iFirst {
    void method2();
}

public class ClassOne implements ISecond() {
    public void method1();
    public void method2();
}

public class ClassTwo implements IFirst() {
    public void method1();
}

В большинстве случаев этот подход будет одобрен, но у него есть и недостатки. Например, мы хотим method3() (и только тот) для ClassTwo. Нам понадобится новый интерфейс IThird. Если позже мы захотим добавить method4(), который должен быть реализован как ClassOne, так и ClassTwo, нам нужно будет изменить (но не ClassThree, а также implemented IFirst), нам нужно будет изменить как ISecond, так и IThird.

Редко бывает "волшебная пуля", когда дело доходит до программирования. В случае интерфейсов лучше всего, если они не изменятся. Это не всегда происходит в реальных ситуациях. Поэтому рекомендуется, чтобы интерфейсы предлагали только "контракт" (обязательная функция) и, когда это возможно, использовали абстрактные классы.

Ответ 4

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

Чтобы добавить вещи в интерфейс, самым чистым ответом является вывод из него нового интерфейса. Это позволит использовать объекты, реализующие новые типы поведения, с кодом, ожидающим старые, и позволяя пользователю объявить соответствующим образом и/или typecast, чтобы получить доступ к новым функциям. Это немного раздражает, так как это может потребовать экземпляра тестов, но это самый надежный подход, и это тот, который вы увидите во многих отраслевых стандартах.

Ответ 5

Интерфейсы заключаются между контрактами между разработчиком и клиентами, поэтому вы правы - они вырезаны из камня и не должны меняться. Поэтому интерфейс должен выставлять (= требовать) только базовые функции, которые абсолютно необходимы для класса.

Возьмем, например, интерфейс List. Существует множество реализаций списков на Java, многие из которых развиваются со временем (лучшие алгоритмы под капотом, улучшенное запоминающее устройство), но основная "концепция" списка - добавляет элемент, ищет элемент, удаляет элемент - не должен и никогда не изменится.

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

Возьмите этот абстрактный класс (= интерфейс), например:

abstract class BaseQueue {
    abstract public Object pop();
    abstract public void push(Object o);
    abstract public int length();
    public void clearEven() {};
}

public class MyQueue extends BaseQueue {
    @Override
    public Object pop() { ... }

    ...
}

Как и в интерфейсах, каждый класс, который расширяет BaseQueue, связан с контрактом для реализации абстрактных методов. Метод clearEven(), однако, не является абстрактным методом (и уже поставляется с пустой реализацией), поэтому клиент не вынужден его реализовать или даже использовать.

Это означает, что вы можете использовать силу абстрактных классов в Java, чтобы создавать методы, не связанные с контрактами. Вы можете добавить другие методы в базовый класс в будущем столько, сколько захотите, при условии, что они не являются абстрактными методами.

Ответ 6

Я думаю, что ваш вопрос больше о дизайне и методах, поэтому ответы java8 немного вводят в заблуждение. Эта проблема была известна задолго до java8, поэтому для нее есть и другие решения.

Во-первых, нет абсолютно беспроблемных способов решения проблемы. Размер неудобств, возникающих в результате разработки интерфейса, зависит от того, как используется библиотека и насколько преднамерен ваш дизайн.

1) Никакие методы не помогут, если вы разработали интерфейс и забыли включить в него обязательный метод. Планируйте свой дизайн лучше и старайтесь предвидеть, как клиенты будут использовать ваши интерфейсы.
Пример. Представьте интерфейс Machine, который имеет метод turnOn(), но пропускает метод turnOff(). Введение нового метода с пустой реализацией по умолчанию в java8 предотвратит ошибки компиляции, но не поможет, потому что вызов метода не будет иметь никакого эффекта. Предоставление рабочей реализации иногда невозможно, потому что интерфейс не имеет полей и состояний.

2) Различные реализации обычно имеют общие черты. Не бойтесь держать общую логику в родительском классе. Наследовать классы библиотеки из этого родительского класса. Это заставит клиенты библиотеки наследовать свои собственные реализации из вашего родительского класса. Теперь вы можете внести небольшие изменения в интерфейс, не нарушая все.
Пример. Вы решили включить метод isTurnedOn() в свой интерфейс. С базовым классом вы можете написать реализацию метода по умолчанию, которая сделала бы смысл. Классы, которые не были унаследованы от родительского класса, по-прежнему должны предоставлять свои собственные реализации методов, но поскольку метод не является обязательным, им будет легко.

3) Обновление функциональности обычно достигается путем расширения интерфейсов. Нет причин заставить клиентов библиотеки внедрять множество новых методов, потому что они могут не нуждаться в них.
Пример. Вы решили добавить метод stayIdle() к вашему интерфейсу. Это делает использование классов в вашей библиотеке, но не для пользовательских классов клиентов. Поскольку эта функциональность новая, лучше создать новый интерфейс, который расширит Machine и будет использовать его, когда это необходимо.