Java - конфликт имен методов в реализации интерфейса

88

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

В C # это преодолевается тем, что называется явной реализацией интерфейса. Есть ли эквивалентный способ в Java?

Бхаскар
источник
37
Когда один класс должен реализовать два метода с одной и той же сигнатурой, которые делают разные вещи , тогда ваш класс почти наверняка делает слишком много вещей.
Иоахим Зауэр
15
Вышеупомянутое может не всегда верно ИМО. Иногда в одном классе вам нужны методы, которые должны подтверждать внешний контракт (таким образом, ограничивая подписи), но которые имеют разные реализации. Фактически, это общие требования при разработке нетривиального класса. Перегрузка и переопределение обязательно являются механизмами, позволяющими использовать методы, которые делают разные вещи, которые могут не отличаться по сигнатуре или очень незначительно отличаться. То, что у меня здесь, просто немного более ограничительно, поскольку оно не позволяет создавать подклассы / и не позволяет даже малейшее изменение подписей.
Bhaskar
1
Я был бы заинтригован, узнав, что это за классы и методы.
Ури
2
Я столкнулся с таким случаем, когда унаследованный класс Address реализовал интерфейсы Person и Firm, в которых метод getName () просто возвращал String из модели данных. В новом бизнес-требовании указано, что Person.getName () возвращает строку в формате «Фамилия, данные имена». После долгих обсуждений данные были преобразованы в базу данных.
Belwood
12
Просто заявить, что класс почти наверняка делает слишком много вещей, НЕ КОНСТРУКТИВНО. У меня сейчас тот самый случай, когда мой класс имеет коллизии имен методов из 2 разных интерфейсов, и мой класс НЕ делает слишком много вещей. Цели очень похожи, но делают немного разные вещи. Не пытайтесь защитить явно несовершенный язык программирования, обвиняя собеседника в плохой разработке программного обеспечения!
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();}
}
jjnguy
источник
9
Но таким образом я не могу передать экземпляр CompositeClass куда-нибудь, где ожидается ссылка на интерфейсы (ISomething или ISomething2)? Я даже не могу ожидать, что клиентский код сможет привести мой экземпляр к соответствующему интерфейсу, поэтому не теряю ли я что-то из-за этого ограничения? Также обратите внимание, что таким образом при написании классов, которые фактически реализуют соответствующие интерфейсы, мы теряем преимущество наличия кода в одном классе, что иногда может быть серьезным препятствием.
Bhaskar
9
@Bhaskar, у вас есть веские аргументы. Лучший совет , который я имею добавить ISomething1 CompositeClass.asInterface1();и ISomething2 CompositeClass.asInterface2();метод этого класса. Тогда вы можете просто получить тот или иной из составного класса. Однако нет отличного решения этой проблемы.
jjnguy
1
Говоря о запутанных ситуациях, к которым это может привести, вы можете привести пример? Разве мы не можем думать об имени интерфейса, добавленном к имени метода, как о дополнительном разрешении области видимости, которое затем может избежать столкновения / путаницы?
Bhaskar
@Bhaskar Лучше, если наши классы будут придерживаться принципа единой ответственности. Если существует класс, реализующий два очень разных интерфейса, я думаю, что дизайн следует переработать, чтобы разделить классы, чтобы обеспечить единую ответственность.
Anirudhan J
1
Насколько запутанным было бы разрешить что-то вроде public long getCountAsLong() implements interface2.getCount {...}[в случае, если интерфейс требует a, 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, вы можете сделать в нем субинтерфейс Alfawith Beta asBeta().

Gustafc
источник
Вы имеете в виду анонимный класс, а не внутренний класс?
Зайд Масуд
2
@ZaidMasud Я имею в виду внутренние классы, поскольку они могут получить доступ к закрытому состоянию окружающего объекта). Эти внутренние классы, конечно, тоже могут быть анонимными.
gustafc
11

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

Чтобы дать конкретный пример для последнего случая, предположим, что вы хотите реализовать оба Collectionи MyCollection(которые не наследуются Collectionи имеют несовместимый интерфейс). Можно обеспечить Collection getCollectionView()и MyCollection getMyCollectionView()функции , которые обеспечивают реализацию светового веса Collectionи MyCollection, используя те же данные.

В первом случае ... предположим, что вам действительно нужен массив целых чисел и массив строк. Вместо наследования от обоих List<Integer>и List<String>вы должны иметь один член типа List<Integer>и другой член типа List<String>и ссылаться на эти члены, а не пытаться наследовать от обоих. Даже если вам нужен только список целых чисел, в этом случае лучше использовать композицию / делегирование вместо наследования.

Михаил Аарон Сафян
источник
Я так не думаю. Вы забываете библиотеки, которые требуют от вас реализации различных интерфейсов для совместимости с ними. Вы можете столкнуться с этим, используя несколько конфликтующих библиотек гораздо чаще, чем вы столкнетесь с этим в своем собственном коде.
nightpool
1
@nightpool, если вы используете несколько библиотек, каждая из которых требует разных интерфейсов, для одного объекта по-прежнему нет необходимости реализовывать оба интерфейса; у объекта могут быть средства доступа для возврата каждого из двух различных интерфейсов (и вызова соответствующего средства доступа при передаче объекта в одну из базовых библиотек).
Майкл Аарон Сафян
1

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

В моем случае у меня есть класс BootStrapperApp унаследован от android.app.Application , в
то время как тот же класс должен также реализовать интерфейс платформы платформы MVVM для интеграции.
Конфликт методов произошел в методе getString () , который объявляется обоими интерфейсами и должен иметь разные реализации в разных контекстах.
Обходной путь (уродливый .. IMO) использует внутренний класс для реализации всех платформметоды, просто из-за одного незначительного конфликта сигнатур метода ... в некоторых случаях такой заимствованный метод вообще не используется (но влияет на основную семантику проекта).
Я склонен согласиться с тем, что явное указание контекста / пространства имен в стиле C # полезно.

Тяньчэн
источник
1
Я никогда не понимал, насколько C # продуман и многофункциональн, пока не начал использовать Java для разработки под Android. Я воспринимал эти возможности C # как должное. В Java отсутствует слишком много функций.
Damn Vegetables
1

Единственное решение, которое пришло мне в голову, - использовать объекты ссылки на тот, который вы хотите реализовать с несколькими интерфейсами.

например: предположим, что у вас есть 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

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

AlexCon
источник
-1

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

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

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

В 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, большинство других языков, основанных на объектно-ориентированных технологиях, поддерживают это. Java редко заставляет реализовывать все интерфейсы одним и тем же методом NAME.

В большинстве других языков есть компилятор, который более чем счастлив принять инструкцию, чтобы сказать: «этот метод в этом классе, который соответствует сигнатуре этого метода в этом реализованном интерфейсе, является его реализацией». В конце концов, весь смысл определения интерфейсов состоит в том, чтобы позволить абстрагировать определение от реализации. (Даже не заставляйте меня использовать методы по умолчанию в интерфейсах в Java, не говоря уже о переопределении по умолчанию .... потому что, конечно, каждый компонент, разработанный для дорожного автомобиля, должен иметь возможность врезаться в летающий автомобиль и просто работать - эй они обе машины ... Я уверен, что на функции по умолчанию, скажем, вашу спутниковую навигацию не повлияют входные данные по тангажу и крену, потому что машины только рыскают!

user8232920
источник