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, не говоря уже о переопределении по умолчанию.... потому что, конечно, каждый компонент, предназначенный для дорожного автомобиля, должен быть в состоянии врезаться в летающий автомобиль и просто работать - эй они оба машины... Я уверен, что по умолчанию функциональность скажет, что ваша спутниковая навигация не будет затронута с настройками по умолчанию, так как машины только рыскать!