Java - зіткнення імен методів при реалізації інтерфейсу


88

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

У C # це долається тим, що називається явною реалізацією інтерфейсу. Чи існує еквівалентний спосіб у Java?


37
Коли одному класу доводиться реалізовувати два методи з однаковим підписом, які роблять різні речі , тоді ваш клас майже напевно робить занадто багато речей.
Йоахім Зауер,

15
Вищезазначене може не завжди відповідати IMO. Іноді в одному класі потрібні методи, які повинні підтверджувати зовнішній контракт (таким чином, обмежуючи підписи), але які мають різні реалізації. Насправді це загальні вимоги при проектуванні нетривіального класу. Перевантаження та перевизначення - це обов'язково механізми, що дозволяють використовувати методи, які роблять різні речі, які можуть не відрізнятися між собою підписом або відрізнятися дуже незначно. Те, що я маю тут, є трохи більш обмежувальним, оскільки воно не дозволяє підкласифікацію / і не дозволяє навіть найменша варіація підписів.
Bhaskar,

1
Мені було б цікаво дізнатись, що це за класи та методи.
Урі

2
Я зіткнувся з таким випадком, коли застарілий клас "Адреса" реалізував інтерфейси Person і Firm, які мали метод getName (), просто повертаючи String із моделі даних. Нова бізнес-вимога вказувала, що Person.getName () повертає рядок у форматі "Прізвище, імена". Після довгих обговорень дані натомість були переформатовані в базу даних.
belwood

12
Тільки констатація того, що клас майже напевно робить забагато речей, НЕ КОНСТРУКТИВНА. Зараз у мене такий випадок, що в моєму класі є зіткнення імен з двох різних інтерфейсів, і мій клас НЕ робить занадто багато речей. Цілі досить схожі, але роблять дещо інші речі. Не намагайтеся захищати явно важку мову програмування, звинувачуючи допитувача в поганому дизайні програмного забезпечення!
j00hi

Відповіді:


75

Ні, немає можливості реалізувати один і той же метод двома різними способами в одному класі на Java.

Це може призвести до багатьох заплутаних ситуацій, тому Java заборонила це.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}

Що ви можете зробити, це скласти клас із двох класів, кожен з яких реалізує різний інтерфейс. Тоді цей клас матиме поведінку обох інтерфейсів.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}

9
Але таким чином, я не можу передати екземпляр CompositeClass кудись, як очікується посилання на інтерфейси (ISomething або ISomething2)? Я навіть не можу очікувати, що клієнтський код зможе передати мій екземпляр до відповідного інтерфейсу, тож я щось не втрачаю цим обмеженням? Також зауважте, що таким чином, під час написання класів, які насправді реалізують відповідні інтерфейси, ми втрачаємо перевагу використання коду в одному класі, що іноді може бути серйозною перешкодою.
Bhaskar,

9
@Bhaskar, ти робиш дійсні бали. Найкраща порада, яку я маю, - це додати a ISomething1 CompositeClass.asInterface1();та ISomething2 CompositeClass.asInterface2();метод до цього класу. Тоді ви можете просто отримати той чи інший із композитного класу. Хоча чудового рішення цієї проблеми немає.
jjnguy

1
Говорячи про заплутані ситуації, до яких це може призвести, можете навести приклад? Хіба ми не можемо думати про ім'я інтерфейсу, додане до імені методу, як додаткову роздільну здатність, яка може уникнути зіткнення / плутанини?
Bhaskar,

@Bhaskar Краще, якщо наші класи дотримуються принципу єдиної відповідальності. Якщо існує клас, який реалізує два дуже різні інтерфейси, я думаю, що дизайн слід переробити, щоб розділити класи, щоб подбати про одну відповідальність.
Anirudhan J

1
Як би насправді було заплутано дозволити щось на зразок public long getCountAsLong() implements interface2.getCount {...}[у випадку, якщо інтерфейс вимагає, longале користувачі класу очікують int] або private void AddStub(T newObj) implements coolectionInterface.Add[якщо припустимо, що collectionInterfaceмає canAdd()метод, і для всіх екземплярів цього класу він повертається false]?
supercat

13

На Java немає реального способу вирішити це. Ви можете використовувати внутрішні класи як обхідний шлях:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

Незважаючи на те, що це не дозволяє приведення з AlfaBetaдо Beta, зниження, як правило, є злим, і якщо можна очікувати, що Alfaекземпляр часто також має Betaаспект, і з якихось причин (зазвичай оптимізація є єдиною вагомою причиною), ви хочете мати можливість щоб перетворити його Beta, ви могли б зробити суб-інтерфейс Alfaз Beta asBeta()в ньому.


Ви маєте на увазі анонімний клас, а не внутрішній клас?
Заїд Масуд,

2
@ZaidMasud Я маю на увазі внутрішні класи, оскільки вони можуть отримати доступ до приватного стану об'єкта, що охоплює). Звичайно, ці внутрішні класи можуть бути анонімними.
gustafc

11

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

Щоб навести конкретний приклад для останнього випадку, припустимо, ви хочете реалізувати і те, Collectionі інше MyCollection(яке не успадковується від Collectionі має несумісний інтерфейс). Ви можете надати функції Collection getCollectionView()і, MyCollection getMyCollectionView()які забезпечують легку реалізацію Collectionта MyCollection, використовуючи ті самі базові дані.

У першому випадку ... припустимо, ви дійсно хочете масив цілих чисел і масив рядків. Замість того, щоб успадковувати від обох List<Integer>і List<String>, ви повинні мати одного члена типу List<Integer>та іншого члена типу List<String>, і посилатися на цих членів, а не намагатися успадкувати від обох. Навіть якщо вам потрібен лише список цілих чисел, у цьому випадку краще використовувати склад / делегування над успадкуванням.


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

1
@nightpool, якщо ви використовуєте кілька бібліотек, кожна з яких потребує різних інтерфейсів, все одно необов’язково для одного об’єкта реалізовувати обидва інтерфейси; у вас може бути об’єкт із засобами доступу для повернення кожного з двох різних інтерфейсів (і викликати відповідний модуль доступу при передачі об’єкта до однієї з базових бібліотек).
Michael Aaron Safyan

1

"Класична" проблема Java також впливає на мою розробку Android ...
Причина, здається, проста: потрібно
більше фреймворків / бібліотек, легше все може вийти з-під контролю ...

У моєму випадку у мене є клас BootStrapperApp успадкований від android.app.Application ,
тоді як той самий клас також повинен реалізовувати інтерфейс платформи платформи MVVM, щоб інтегруватися.
Зіткнення методів відбулося в методі getString () , який оголошується обома інтерфейсами і повинен мати реалізацію diffrenet в різних контекстах.
Обхідний шлях (потворний..IMO) - використання внутрішнього класу для реалізації всієї Платформиметодів, лише через один незначний конфлікт підписів методів ... в деяких випадках такий запозичений метод навіть взагалі не використовується (але впливає на основну семантику дизайну).
Я схильний погодитись, що вказівка ​​на контекст / простір імен у стилі C # корисна.


1
Я ніколи не розумів, як C # був продуманим та багатофункціональним, поки не почав використовувати Java для розробки Android. Я сприймав ці функції C # як належне. Java не має надто багатьох функцій.
Прокляті овочі

1

Єдине рішення, яке мені спало на думку, - використання об'єктів referencece до того, який ви хочете реалізувати muliple interfaceces.

наприклад: припустимо, у вас є 2 інтерфейси для реалізації

public interface Framework1Interface {

    void method(Object o);
}

і

public interface Framework2Interface {
    void method(Object o);
}

Ви можете вкласти їх у два об'єкти Facador:

public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

і

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

Врешті-решт клас, який ви хотіли, повинен бути приблизно таким

public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

тепер ви можете використовувати методи getAs *, щоб "виставити" свій клас


0

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


-1

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

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Тепер вам потрібно передати його у полицю WizzBangProcessor, яка вимагає класів для реалізації WBPInterface ..., який також має метод getName (), але замість вашої конкретної реалізації цей інтерфейс очікує, що метод поверне ім'я типу обробки Wizz Bang.

У C # це було б тривіально

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

У Java Tough вам потрібно буде визначити кожну точку в існуючій базі розгорнутого коду, де вам потрібно перетворити з одного інтерфейсу на інший. Звичайно, компанія WizzBangProcessor повинна була використовувати getWizzBangProcessName (), але вони теж розробники. У їхньому контексті getName було добре. Насправді, поза Java, більшість інших мов на основі OO підтримують це. Java рідко заставляє всі інтерфейси реалізовуватися одним і тим же методом NAME.

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

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