Пара-реализация интерфейса Java

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

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

Вот простой пример:

Интерфейс

public interface StuffDoer{
    public abstract void doStuff();
}

Конкретная реализация методов

public class ConcreteStuffDoer{
    public static void  doStuff(){
        dosomestuff...
    }
}

Конкретная реализация с использованием стандартных функций

public class MyClass implements StuffDoer{
    public void  doStuff(){
        ConcreteSuffDoer.doStuff();        
    } 
}

Есть ли лучший подход здесь?

ИЗМЕНИТЬ

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

Ответ 1

Вот такой подход:

public interface MyInterface {

      MyInterface DEFAULT = new MyDefaultImplementation();

      public static class MyDefaultImplemenation implements MyInterface {
      }
 }

Конечно, MyDefaultImplementation может быть закрытой или ее собственный класс верхнего уровня, в зависимости от того, что имеет смысл.

В ваших реализациях вы можете иметь следующее:

 public class MyClass implements MyInterface {
        @Override
        public int someInterfaceMethod(String param) {
             return DEFAULT.someInterfaceMethod(param);
        }
  }

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

Конечно, вышесказанное работает только в том случае, если не задействовано какое-либо государство.

Ответ 2

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

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

Ответ 3

Теперь, когда Java 8 отсутствует, этот шаблон намного приятнее:

public interface StuffDoer{
    default void doStuff() {
        dosomestuff...
    }
}

public class MyClass implements StuffDoer {
    // doStuff automatically defined
}

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

Ответ 4

Я следую более или менее образцу, который я изначально узнал из Swing. У меня есть интерфейс, тогда я создаю класс "Base" или "Adapter". Для меня адаптер часто будет иметь ничего не реализовывать для всех методов интерфейса, чтобы позволить разработчику писать только нужные им методы, игнорируя остальные. Базой будет абстрактный класс, который обеспечивает удобные реализации некоторых методов интерфейса - надеюсь, что это "наиболее вероятная" реализация.

Например, у меня есть интерфейс SearchFilter, который, помимо прочего, имеет метод apply(Collection<T>). Этот метод почти всегда будет проходить через коллекцию и вызывать метод интерфейса boolean include(T item), чтобы решить, следует ли сохранить или отфильтровать элемент. Мой SearchFilterBase обеспечивает это как реализацию для apply(), и разработчику нужно написать свою логику include().

Разработчики бесплатны, конечно, просто реализовать весь интерфейс самостоятельно и не выводить из базы, что является преимуществом перед изменением интерфейса на абстрактный класс, что заставляет их использовать их одиночное наследование (такова проблема с java.util.Observable)


В ответ на комментарий N8g. Вы можете подклассифицировать базу или адаптер, но не требуются для подкласса - вы можете реализовать интерфейс самостоятельно с нуля. База или адаптер предоставляются в качестве удобства, реализуя методы no-op, поэтому вам не нужно это делать или реализовать удобные общие функции в абстрактном базовом классе (например, мой класс SearchFilterBase). Преимущество этого в том, что превращение интерфейса в абстрактный класс состоит в том, что вы не навязываете наследование из вашего абстрактного класса.

Ответ 5

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

Для функциональности с состоянием я предпочел бы композицию вместо наследования. С составом и использованием делегатов/адаптеров вы можете комбинировать функциональность по умолчанию из многих источников.

например.

public interface StuffDoer{
    void doStuff();
    void doOtherStuff();
}

public class MyStuffDoer implements StuffDoer{
    private final StuffDoer mixin;
    public  MyStuffDoer(StuffDoer mixin){
        this.mixin = mixin;
    }
    public void doStuff(){
        mixin.doStuff();
    }
    public void doOtherStuff(){
        mixin.doOtherStuff();
    }

}

public class MyStuffDoer2 implements StuffDoer{
    private final StuffDoer mixin1, mixin2;
    public  MyStuffDoer(StuffDoer mixin1, StuffDoer mixin2){
        this.mixin1 = mixin1;
        this.mixin2 = mixin2;
    }
    public void doStuff(){
        mixin1.doStuff();
    }
    public void doOtherStuff(){
        mixin2.doOtherStuff();
    }

}

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

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

public interface A{
    void doStuff();
}

public interface B{
    void doOtherStuff();
}

public class MyStuffDoer implements A, B{
    private final A mixin1;
    private final B mixin2;
    public  MyStuffDoer(A mixin1, B mixin2){
        this.mixin1 = mixin1;
        this.mixin2 = mixin2;
    }
    public void doStuff(){
        mixin1.doStuff();
    }
    public void doOtherStuff(){
        mixin2.doOtherStuff();
    }
}

Вы не можете сделать это с помощью абстрактных классов. Я использовал этот составный подход для нескольких проектов, и он работал довольно хорошо.

Ответ 6

Статические реализации имеют две проблемы:

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

  • иногда требуется наличие некоторого состояния в реализации, что не может быть сделано в статическом методе

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

Ответ 7

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

public interface Something {
  public void doSomething();

  public static class Static {
    public static void doSomething(Something self) {
      // default implementation of doSomething()
      // the name of the method is often the same
      // the arguments might differ, so state can be passed
    }
  }

  public static abstract class Abstract implements Something {
    // the default abstract implementation
  }

  public static class Default extends Abstract {
    // the default implementation
    public void doSomething() {
      Static.doSomething(this);
    }
  }

  public static interface Builder extends Provider<Something> {
    // initializes the object
  }
}

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

Кроме того, вы можете поместить класс утилиты (Static) в отдельный файл, если методы относятся не только к интерфейсу.


Кстати, проблема с декораторами заключается в том, что они скрывают другие реализованные интерфейсы. Другими словами, если у вас есть что-то декоратор, то это false new SomethingDecorator(new ArrayList<Object>()) instanceof List<?>, если SomethingDecorator не реализует интерфейс списков. Вы можете работать arround с использованием API отражения и, в частности, с помощью Proxy.

Другим хорошим aproach являются адаптеры, как указано PersicsB.

Ответ 8

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

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

@ImplementedBy( ConcreteStuffDoer.class )
public interface StuffDoer{
    void doStuff();
}

public class ConcreteStuffDoer implements StuffDoer {
    public void  doStuff(){ ... }
}

public class MyClass implements StuffDoer{
  @Inject StuffDoer m_delegate;
  public void  doStuff(){ m_delegate.doStuff(); }
}

Это означает, что для создания экземпляра MyClass требуется метод Guice, а не просто new.

Ответ 9

Состав превосходит множественное наследование с точки зрения гибкости. Как и в случае с шаблоном адаптера (см. Книгу GOF), это позволяет нам адаптировать (морфинг) классы к различным типам поведения в зависимости от запроса вызывающих абонентов. См. Интерфейс IAdaptable в Eclipse RCP. В основном использование интерфейсов аналогично использованию шаблона адаптера, встроенного в язык Java, напрямую. Но использование адаптируемых классов лучше, чем внедрение нескольких интерфейсов. Адаптируемые классы более гибкие, но у них есть некоторые накладные расходы процессора: оператор instanceof потребляет меньше CPU, чем вызов canAdapt (Class clazz). Но в современных JVM это может быть ложным, поскольку вычисление instanceof с интерфейсами более сложное, чем вычисление instanceof с наследованием.

Ответ 10

Если это ваша любая функция:-) работает как статичная, вам не нужно делегировать ее всем, так как это не требует "this" - это служебная функция.

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

Кроме того, вы можете создавать вторичные, защищенные виртуальные методы в абстрактном классе, что ваши производные от интерфейса методы будут называть того, кто дает вам больше контроля, т.е. производный класс не должен заменять методы, а копировать и вставлять 90% code - он может просто переопределить метод, вызываемый из основных методов. Дает вам степень поведенческого наследования.