Конструктор в інтерфейсі?


148

Я знаю, що визначити конструктор в інтерфейсі неможливо. Але мені цікаво чому, бо я думаю, що це може бути дуже корисним.

Таким чином, ви можете бути впевнені, що деякі поля в класі визначені для кожної реалізації цього інтерфейсу.

Наприклад, розглянемо такий клас повідомлень:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Якщо визначити інтерфейс для цього класу, щоб у мене було більше класів, які реалізують інтерфейс повідомлення, я можу визначити лише метод відправки, а не конструктор. Тож як я можу гарантувати, що кожна реалізація цього класу справді має набір приймачів? Якщо я використовую такий метод, як setReceiver(String receiver)я не можу бути впевнений, що цей метод справді називається. У конструкторі я міг би це забезпечити.



2
Ви говорите: "У конструкторі я міг би переконатися, що [кожна реалізація цього класу дійсно має набір приймачів]." Але ні, ви не могли цього зробити. За умови, що можна було визначити такий конструктор, параметр буде лише сильним підказом для ваших реалізаторів - але вони могли вирішити просто проігнорувати його, якщо захочуть.
Жульєн Сілланд

3
@mattb Umm, це інша мова.
єсенни

Відповіді:


129

Беручи деякі з описаних вами речей:

"Отже, ви можете бути впевнені, що деякі поля в класі визначені для кожної реалізації цього інтерфейсу."

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

... ці вимоги - саме те , для чого призначені абстрактні заняття .


але зауважте, що випадок використання @Sebi описує (виклик перевантажених методів від батьківських конструкторів) є поганою ідеєю, як пояснено у моїй відповіді.
rsp

44
Метт, це явно вірно, але абстрактні класи страждають від обмеження єдиного успадкування, що змушує людей дивитися на інші способи визначення ієрархій.
CPerkins

6
Це правда і може вирішити найближчу проблему Себі. Але одна з причин використання інтерфейсів у Java полягає в тому, що ви не можете мати багаторазове успадкування. У випадку, коли я не можу зробити свою "річ" абстрактним класом, оскільки мені потрібно успадкувати щось інше, проблема залишається. Не те, що я претендую на рішення.
Джей

7
@CPerkins, хоча це правда, я не припускаю, що просто використання абстрактного класу вирішить випадок використання Sebi. Якщо що-небудь, то краще оголосити Messageінтерфейс, який визначає send()метод, а якщо Sebi бажає надати "базовий" клас для реалізації Messageінтерфейсу, то також надайте AbstractMessageа. Абстрактні класи не повинні займати місце інтерфейсів, ніколи цього не намагалися запропонувати.
мат б

2
Зрозумів, Метт. Я не сперечався з вами, більше підкреслюючи, що це не повна заміна того, чого хоче оператор.
CPerkins

76

Проблема, яка виникає, коли ви дозволяєте конструкторам в інтерфейсах, випливає з можливості реалізувати кілька інтерфейсів одночасно. Коли клас реалізує кілька інтерфейсів, які визначають різні конструктори, то клас повинен буде реалізувати кілька конструкторів, кожен з яких задовольняє лише один інтерфейс, але не інші. Побудувати об’єкт, який викликає кожен із цих конструкторів, буде неможливо.

Або в коді:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}

1
Не могла мова зробити це, дозволивши такі речі, якclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako

1
Найбільш корисним значенням для "конструктора в інтерфейсі", якщо це дозволено, було б, якщо new Set<Fnord>()можна інтерпретувати, що означає "Дайте мені щось, що я можу використовувати як Set<Fnord>"; якщо автор Set<T>мав HashSet<T>на меті бути реалізацією для речей, у яких не було особливої ​​потреби в чомусь іншому, інтерфейс, який можна визначити, new Set<Fnord>()може вважатися синонімом new HashSet<Fnord>(). Для класу реалізація декількох інтерфейсів не складе жодних проблем, оскільки new InterfaceName()просто побудує клас, призначений інтерфейсом .
supercat

Контраргумент: ваш A(String,List)конструктор може бути призначеним конструктором A(String)і A(List)може бути другорядним тим, хто його називає. Ваш код не є протиприкладним, а лише бідним.
Бен Леджієро

Чому ви б закликали всіх конструкторів до реалізації ?! Так, якщо він реалізує більше інтерфейсів з ctor, один з String і один з int, йому потрібно, щоб два ctors - але або вони можуть бути використані. Якщо це не застосовується, клас просто не реалізує обидва інтерфейсу. І що!? (Є й інші причини відсутності ctor в інтерфейсах).
кай

@kai Ні, для створення екземпляра потрібно викликати обидва конструктори інтерфейсу. Іншими словами: У моєму прикладі екземпляр має і ім’я, і список, тому кожному екземпляру потрібно створити ім'я та список.
Даніель Кулманн

13

Інтерфейс визначає контракт на API, тобто набір методів, з якими погоджуються і реалізатор, і користувач API. Інтерфейс не має інстальованої реалізації, отже, немає конструктора.

Описаний вами випадок використання схожий на абстрактний клас, в якому конструктор викликає метод абстрактного методу, який реалізований у дочірньому класі.

Притаманна тут проблема полягає в тому, що під час виконання базового конструктора дочірній об’єкт ще не побудований і тому знаходиться в непередбачуваному стані.

Підводячи підсумок: чи вимагає неприємностей, коли ви викликаєте перевантажені методи від батьківських конструкторів, цитувати mindprod :

Взагалі ви повинні уникати викликати будь-які не завершальні методи в конструкторі. Проблема полягає в тому, що ініціалізатори екземплярів / ініціалізація змінної у похідному класі виконуються після конструктора базового класу.


6

Робота навколо, яку ви можете спробувати, - це визначення а getInstance() вирішити проблему метод у своєму інтерфейсі, щоб реалізатор знав, з якими параметрами потрібно обробляти. Він не настільки міцний, як абстрактний клас, але він забезпечує більшу гнучкість як інтерфейс.

Однак для вирішення цього питання потрібно вимагати використання getInstance()інстанції всіх об'єктів цього інтерфейсу.

Напр

public interface Module {
    Module getInstance(Receiver receiver);
}

5

В інтерфейсі є лише статичні поля, які не потрібно ініціалізувати під час створення об'єкта в підкласі, а метод інтерфейсу повинен забезпечувати фактичну реалізацію в підкласі. Тому немає потреби в конструкторі в інтерфейсі.

Друга причина - під час створення об'єкта підкласу називається батьківський конструктор. Але якщо буде реалізовано більше одного інтерфейсу, тоді при виклику конструктора інтерфейсу виникне конфлікт щодо того, який конструктор інтерфейсу зателефонує спочатку


3

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

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • це не порушить інкапсуляцію
  • він дасть знати всім, хто використовує ваш інтерфейс, що Receiverоб’єкт потрібно якось передавати класу (або конструктором, або сетером)

2

Залежності, на які не посилаються в методах інтерфейсів, слід розглядати як деталі реалізації, а не те, що інтерфейс застосовує. Звичайно, можуть бути винятки, але, як правило, слід визначити свій інтерфейс як те, що очікується поведінка. Внутрішній стан даної реалізації не повинен стосуватися дизайну інтерфейсу.


1

Дивіться це питання для того, чому (взято з коментарів).

Якщо вам дійсно потрібно щось подібне зробити, можливо, ви захочете абстрактний базовий клас, а не інтерфейс.


1

Це тому, що інтерфейси не дозволяють визначити тіло методу в ньому. Але нам слід було б визначити конструктор у тому ж класі, що й інтерфейси за умовчанням мають абстрактний модифікатор для всіх методів визначення. Ось чому ми не можемо визначити конструктор в інтерфейсах.


0

Ось приклад використання цієї техніки. У цьому конкретному прикладі код здійснює виклик у Firebase, використовуючи макет, MyCompletionListenerякий є інтерфейсом, замаскованим як абстрактний клас, інтерфейс з конструктором

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}

Як можна використовувати privateмодифікатор доступу interface?
Рудра

0

Як правило, конструктори призначені для ініціалізації нестатичних членів певного класу щодо об'єкта.

Для інтерфейсу немає створення об’єктів, оскільки є лише оголошені методи, але не визначені методи. Чому ми не можемо створити об'єкт для оголошених методів, це створення об’єктів - це не що інше, як виділення деякої пам’яті (в купі пам'яті) для нестатичних членів.

JVM створить пам'ять для членів, які повністю розроблені та готові до використання. На основі цих членів JVM обчислює, скільки пам'яті потрібно для них, і створює пам'ять.

У випадку заявлених методів JVM не в змозі обчислити, скільки пам'яті буде потрібно для цих заявлених методів, оскільки в майбутньому це буде реалізація, що до цього часу не зроблено. тому створення об’єктів для інтерфейсу неможливо.

висновок:

без створення об'єкта немає шансів ініціалізувати нестатичні члени через конструктор. Ось чому конструктору не дозволено всередині інтерфейсу. (як немає використання конструктора всередині інтерфейсу)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.