Java - Kolizja nazw metod w implementacji interfejsu


88

Jeśli mam dwa interfejsy, oba całkiem różne w swoich celach, ale z tą samą sygnaturą metody, jak zrobić klasę implementującą obie bez konieczności pisania jednej metody, która służy do obu interfejsów i pisania zawiłej logiki w metodzie implementacja, która sprawdza, dla jakiego typu obiektu jest wykonywane wywołanie i wywołuje odpowiedni kod?

W języku C # można to obejść przez tak zwaną jawną implementację interfejsu. Czy jest jakiś równoważny sposób w Javie?


37
Kiedy jedna klasa musi zaimplementować dwie metody z tą samą sygnaturą, które robią różne rzeczy , wtedy twoja klasa prawie na pewno robi zbyt wiele rzeczy.
Joachim Sauer

15
Powyższe może nie zawsze być prawdą IMO. Czasami w jednej klasie potrzebne są metody, które muszą być potwierdzone kontraktem zewnętrznym (w ten sposób ograniczając podpisy), ale które mają różne implementacje. W rzeczywistości są to typowe wymagania podczas projektowania nietrywialnych klas. Przeciążanie i nadpisywanie są koniecznie mechanizmami pozwalającymi na metody, które robią różne rzeczy, które mogą nie różnić się podpisem lub różnić się bardzo nieznacznie. To, co tutaj mam, jest tylko nieco bardziej restrykcyjne w tym, że nie pozwala na tworzenie podklas / i nie pozwala nawet najmniejsze różnice w podpisach.
Bhaskar

1
Byłbym zaintrygowany, gdybyś wiedział, jakie są te klasy i metody.
Uri

2
Spotkałem taki przypadek, w którym starsza klasa "Address" implementowała interfejsy Person i Firm, które miały metodę getName (), po prostu zwracającą String z modelu danych. Nowe wymaganie biznesowe określało, że funkcja Person.getName () zwraca ciąg w formacie „Nazwisko, imiona”. Po długich dyskusjach dane zostały ponownie sformatowane w bazie danych.
belwood

12
Samo stwierdzenie, że klasa prawie na pewno robi zbyt wiele rzeczy, NIE JEST KONSTRUKCYJNE. Mam teraz taki przypadek, że moja klasa ma kolizje nazw z 2 różnych interfejsów, a moja klasa NIE robi zbyt wielu rzeczy. Cele są dość podobne, ale robią nieco inne rzeczy. Nie próbuj bronić ewidentnie poważnie upośledzonego języka programowania, oskarżając pytającego o wdrażanie złego projektu oprogramowania!
j00hi

Odpowiedzi:


75

Nie, nie ma możliwości zaimplementowania tej samej metody na dwa różne sposoby w jednej klasie w Javie.

Może to prowadzić do wielu zagmatwanych sytuacji, dlatego Java na to nie zezwala.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

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

Możesz stworzyć klasę z dwóch klas, z których każda implementuje inny interfejs. Wtedy ta jedna klasa będzie miała zachowanie obu interfejsów.

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

9
Ale w ten sposób nie mogę przekazać wystąpienia CompositeClass gdzieś oczekuje się odniesienia do interfejsów (ISomething lub ISomething2)? Nie mogę nawet oczekiwać, że kod klienta będzie mógł rzutować moją instancję do odpowiedniego interfejsu, więc czy nie tracę czegoś z powodu tego ograniczenia? Zwróć również uwagę, że w ten sposób, pisząc klasy, które faktycznie implementują odpowiednie interfejsy, tracimy korzyść z posiadania kodu w jednej klasie, co czasami może być poważnym utrudnieniem.
Bhaskar

9
@Bhaskar, robisz ważne punkty. Najlepsza rada, jaką mam, to dodanie metody ISomething1 CompositeClass.asInterface1();i ISomething2 CompositeClass.asInterface2();do tej klasy. Następnie możesz po prostu uzyskać jedną lub drugą z klasy złożonej. Nie ma jednak dobrego rozwiązania tego problemu.
jjnguy

1
Mówiąc o zagmatwanych sytuacjach, do których może to prowadzić, czy możesz podać przykład? Czy nie możemy myśleć o nazwie interfejsu dodanej do nazwy metody jako o dodatkowym rozwiązaniu zakresu, które pozwala uniknąć kolizji / zamieszania?
Bhaskar

@Bhaskar Lepiej, żeby nasze zajęcia były zgodne z zasadą pojedynczej odpowiedzialności. Jeśli istnieje klasa, która implementuje dwa bardzo różne interfejsy, myślę, że projekt powinien zostać zmieniony, aby podzielić klasy, aby zająć się jedną odpowiedzialnością.
Anirudhan J

1
Jak zagmatwane byłoby naprawdę zezwolenie na coś takiego jak public long getCountAsLong() implements interface2.getCount {...}[w przypadku, gdy interfejs wymaga a, longale użytkownicy klasy oczekują int] lub private void AddStub(T newObj) implements coolectionInterface.Add[zakładając, że collectionInterfacema canAdd()metodę i dla wszystkich instancji tej klasy zwraca false]?
supercat

13

Nie ma prawdziwego sposobu na rozwiązanie tego problemu w Javie. Możesz użyć klas wewnętrznych jako obejścia:

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()
        };
    }
}

Chociaż nie pozwala na rzucanie od AlfaBetado Beta, spadki są generalnie złe i jeśli można się spodziewać, że Alfainstancja często ma również Betaaspekt iz jakiegoś powodu (zwykle optymalizacja jest jedynym ważnym powodem), chcesz mieć możliwość aby przekonwertować go na Beta, możesz zrobić podinterfejs Alfaz Beta asBeta()in it.


Czy masz na myśli raczej klasę anonimową niż wewnętrzną?
Zaid Masud,

2
@ZaidMasud Mam na myśli klasy wewnętrzne, ponieważ mogą one uzyskać dostęp do prywatnego stanu otaczającego obiektu). Te klasy wewnętrzne mogą oczywiście być również anonimowe.
gustafc

11

Jeśli napotkasz ten problem, najprawdopodobniej jest to spowodowane dziedziczeniem, w którym powinieneś używać delegowania . Jeśli potrzebujesz zapewnić dwa różne, aczkolwiek podobne interfejsy dla tego samego podstawowego modelu danych, powinieneś użyć widoku, aby tanio zapewnić dostęp do danych za pomocą innego interfejsu.

Aby podać konkretny przykład tego drugiego przypadku, załóżmy, że chcesz zaimplementować zarówno Collectioni MyCollection(co nie dziedziczy po Collectioni ma niekompatybilny interfejs). Możesz dostarczyć a Collection getCollectionView()i MyCollection getMyCollectionView()funkcje, które zapewniają lekką implementację CollectioniMyCollection przy użyciu tych samych danych bazowych.

W pierwszym przypadku ... przypuśćmy, że naprawdę potrzebujesz tablicy liczb całkowitych i tablicy ciągów. Zamiast dziedziczyć po obu List<Integer>i List<String>, powinieneś mieć jeden element członkowski typu List<Integer>i inny element członkowski typu List<String>i odwoływać się do tych elementów członkowskich, zamiast próbować dziedziczyć z obu. Nawet jeśli potrzebna byłaby tylko lista liczb całkowitych, w tym przypadku lepiej jest użyć kompozycji / delegacji zamiast dziedziczenia.


Nie sądzę. Zapominasz o bibliotekach, które wymagają implementacji różnych interfejsów, aby były z nimi kompatybilne. Możesz napotkać to, używając wielu sprzecznych bibliotek znacznie częściej, niż napotkasz to we własnym kodzie.
basen nocny

1
@nightpool, jeśli używasz wielu bibliotek, z których każda wymaga różnych interfejsów, nadal nie jest konieczne, aby pojedynczy obiekt implementował oba interfejsy; obiekt może mieć metody dostępu do zwracania każdego z dwóch różnych interfejsów (i wywoływanie odpowiedniego akcesora podczas przekazywania obiektu do jednej z bazowych bibliotek).
Michael Aaron Safyan

1

"Klasyczny" problem z Javą wpływa również na mój programowanie w Androidzie ...
Powód wydaje się być prosty:
więcej frameworków / bibliotek, których musisz użyć, łatwiej rzeczy mogą wymknąć się spod kontroli ...

W moim przypadku mam klasę BootStrapperApp dziedziczony z android.app.Application ,
podczas gdy ta sama klasa powinna również implementować interfejs platformy frameworka MVVM w celu integracji.
Wystąpiła kolizja metod w metodzie getString () , która jest ogłaszana przez oba interfejsy i powinna mieć różną implementację w różnych kontekstach.
Obejście problemu (brzydkie..IMO) polega na użyciu klasy wewnętrznej do zaimplementowania całej platformymetody, tylko z powodu jednego pomniejszego konfliktu sygnatur metod ... w niektórych przypadkach taka pożyczona metoda nie jest nawet używana (ale wpływa na semantykę głównego projektu).
Zwykle zgadzam się, że wyraźne wskazanie kontekstu / przestrzeni nazw w stylu C # jest pomocne.


1
Nigdy nie zdawałem sobie sprawy, że C # jest przemyślany i bogaty w funkcje, dopóki nie zacząłem używać Java do programowania na Androida. Wziąłem te funkcje C # za pewnik. W Javie brakuje zbyt wielu funkcji.
Cholerne warzywa,

1

Jedynym rozwiązaniem, które przyszło mi do głowy, jest użycie obiektów referencyjnych do tego, który chcesz zaimplementować wiele interfejsów.

np .: zakładając, że masz 2 interfejsy do zaimplementowania

public interface Framework1Interface {

    void method(Object o);
}

i

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

możesz zamknąć je w dwóch obiektach 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);
    }
}

i

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);
    }
}

W końcu klasa, którą chciałeś, powinna coś podobnego

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) {
    }
}

możesz teraz użyć metod getAs * do „ujawnienia” swojej klasy


0

Możesz użyć wzorca adaptera, aby te działały. Utwórz dwa adaptery dla każdego interfejsu i użyj go. To powinno rozwiązać problem.


-1

Wszystko dobrze, gdy masz całkowitą kontrolę nad całym kodem, o którym mowa, i możesz to zaimplementować z góry. Teraz wyobraź sobie, że masz istniejącą klasę publiczną używaną w wielu miejscach za pomocą metody

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

Teraz musisz przekazać go do standardowego WizzBangProcessor, który wymaga klas do implementacji WBPInterface ... który również ma metodę getName (), ale zamiast konkretnej implementacji ten interfejs oczekuje, że metoda zwróci nazwę typu przetwarzania Wizz Bang.

W C # byłaby to trywialna

public class MyClass : WBPInterface{

    private String name;

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

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

    public String getName(){
        return name;
    }
}

W Javie Tough będziesz musiał zidentyfikować każdy punkt w istniejącej wdrożonej bazie kodu, w którym musisz dokonać konwersji z jednego interfejsu na drugi. Jasne, firma WizzBangProcessor powinna była użyć metody getWizzBangProcessName (), ale są też programistami. W ich kontekście getName było w porządku. W rzeczywistości poza Javą większość innych języków opartych na obiektach obiektowych obsługuje to. Java rzadko wymusza implementację wszystkich interfejsów za pomocą tej samej metody NAME.

Większość innych języków ma kompilator, który z przyjemnością przyjmuje instrukcję mówiącą „ta metoda w tej klasie, która pasuje do sygnatury tej metody w tym zaimplementowanym interfejsie, jest jej implementacją”. W końcu celem definiowania interfejsów jest umożliwienie wyodrębnienia definicji z implementacji. (Nawet nie zaczynaj od posiadania domyślnych metod w interfejsach w Javie, nie mówiąc już o domyślnym zastępowaniu ... ponieważ z pewnością każdy komponent zaprojektowany dla samochodu drogowego powinien być w stanie uderzyć w latający samochód i po prostu działać - hej oba są samochodami ... Jestem pewien, że domyślna funkcjonalność, powiedzmy, twojego urządzenia nawigacyjnego nie będzie miała wpływu na domyślne ustawienia nachylenia i przechylenia, ponieważ samochody tylko odchylają się!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.