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

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

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

7 ответов

Решение

Нет, в 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();}
}

Нет реального способа решить это на 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() в этом.

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

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

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

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

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

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

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

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

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

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.

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

Другие вопросы по тегам