Java - Tabrakan nama metode dalam implementasi antarmuka

88

Jika saya memiliki dua antarmuka, keduanya sangat berbeda dalam tujuannya, tetapi dengan tanda tangan metode yang sama, bagaimana cara membuat kelas menerapkan keduanya tanpa dipaksa untuk menulis satu metode yang berfungsi untuk kedua antarmuka dan menulis beberapa logika yang berbelit-belit dalam metode tersebut implementasi yang memeriksa jenis objek yang panggilan itu dibuat dan memanggil kode yang tepat?

Dalam C #, ini diatasi dengan apa yang disebut sebagai implementasi antarmuka eksplisit. Apakah ada cara yang setara di Java?

Bhaskar
sumber
37
Ketika satu kelas harus mengimplementasikan dua metode dengan tanda tangan yang sama yang melakukan hal berbeda , maka kelas Anda hampir pasti melakukan terlalu banyak hal.
Joachim Sauer
15
Hal di atas mungkin tidak selalu benar IMO. Terkadang, dalam satu kelas, Anda memerlukan metode yang harus mengkonfirmasi ke kontrak eksternal (sehingga membatasi tanda tangan), tetapi memiliki implementasi yang berbeda. Sebenarnya, ini adalah persyaratan umum saat mendesain kelas non-trivial. Overloading dan overriding merupakan mekanisme untuk memungkinkan metode yang melakukan hal berbeda yang mungkin tidak berbeda dalam tanda tangan, atau berbeda sangat sedikit. Apa yang saya miliki di sini hanya sedikit lebih ketat di dalamnya sehingga tidak memungkinkan subclassing / dan bahkan tidak memungkinkan variasi terkecil pada tanda tangan.
Bhaskar
1
Saya akan tertarik untuk mengetahui apa kelas dan metode ini.
Uri
2
Saya mengalami kasus seperti itu di mana kelas "Alamat" warisan menerapkan antarmuka Orang dan Perusahaan yang memiliki metode getName () yang hanya mengembalikan String dari model data. Persyaratan bisnis baru menetapkan bahwa Person.getName () mengembalikan String yang diformat sebagai "Surname, Given names". Setelah banyak diskusi, data tersebut diformat ulang dalam database.
Belwood
12
Hanya menyatakan bahwa kelas hampir pasti melakukan terlalu banyak hal TIDAK BUKAN KONSTRUKTIF. Saya mendapat kasus ini sekarang bahwa kelas saya memiliki tabrakan nama mehod dari 2 antarmuka yang berbeda, dan kelas saya TIDAK melakukan terlalu banyak hal. Tujuannya sangat mirip, tetapi melakukan hal yang sedikit berbeda. Jangan mencoba membela bahasa pemrograman yang jelas-jelas cacat parah dengan menuduh penanya menerapkan desain perangkat lunak yang buruk!
j00hi

Jawaban:

75

Tidak, tidak ada cara untuk mengimplementasikan metode yang sama dengan dua cara berbeda dalam satu kelas di Java.

Itu dapat menyebabkan banyak situasi yang membingungkan, itulah sebabnya Java tidak mengizinkannya.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

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

Apa yang dapat Anda lakukan adalah membuat kelas dari dua kelas yang masing-masing menerapkan antarmuka yang berbeda. Kemudian satu kelas itu akan memiliki perilaku kedua antarmuka.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}
jjnguy
sumber
9
Tapi dengan cara ini, saya tidak bisa melewatkan sebuah instance dari CompositeClass di suatu tempat referensi dari antarmuka (ISomething atau ISomething2) diharapkan? Saya bahkan tidak dapat mengharapkan kode klien untuk dapat mentransmisikan instance saya ke antarmuka yang sesuai, jadi apakah saya tidak kehilangan sesuatu karena batasan ini? Perhatikan juga bahwa dengan cara ini, saat menulis kelas yang benar-benar mengimplementasikan antarmuka masing-masing, kita kehilangan manfaat memiliki kode ke dalam satu kelas, yang terkadang dapat menjadi hambatan yang serius.
Bhaskar
9
@Bhaskar, Anda membuat poin yang valid. Saran terbaik yang saya miliki adalah menambahkan metode ISomething1 CompositeClass.asInterface1();dan ISomething2 CompositeClass.asInterface2();ke kelas itu. Kemudian Anda bisa mengeluarkan satu atau yang lain dari kelas gabungan. Tidak ada solusi yang bagus untuk masalah ini.
jjnguy
1
Berbicara tentang situasi membingungkan yang dapat ditimbulkan, dapatkah Anda memberikan contoh? Bisakah kita tidak menganggap nama antarmuka yang ditambahkan ke nama metode sebagai resolusi cakupan tambahan yang kemudian dapat menghindari benturan / kebingungan?
Bhaskar
@Bhaskar Lebih baik jika kelas kami menganut prinsip tanggung jawab tunggal. Jika ada kelas yang mengimplementasikan dua antarmuka yang sangat berbeda, saya pikir desain harus dikerjakan ulang untuk membagi kelas untuk mengurus tanggung jawab tunggal.
Anirudhan J
1
Betapa membingungkannya, sungguh, untuk mengizinkan sesuatu seperti public long getCountAsLong() implements interface2.getCount {...}[jika antarmuka memerlukan longtetapi pengguna kelas mengharapkan int] atau private void AddStub(T newObj) implements coolectionInterface.Add[mengasumsikan collectionInterfacememiliki canAdd()metode, dan untuk semua contoh kelas ini mengembalikan false]?
supercat
13

Tidak ada cara nyata untuk menyelesaikan ini di Java. Anda dapat menggunakan kelas dalam sebagai solusinya:

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

Meskipun tidak mengizinkan cast dari AlfaBetake Beta, downcast umumnya jahat, dan jika dapat diharapkan bahwa sebuah Alfainstance juga sering memiliki Betaaspek, dan untuk beberapa alasan (biasanya pengoptimalan adalah satu-satunya alasan yang valid) Anda ingin dapat melakukannya untuk mengubahnya Beta, Anda dapat membuat sub-antarmuka Alfadengan Beta asBeta()di dalamnya.

gustafc
sumber
Apakah yang Anda maksud adalah kelas anonim daripada kelas dalam?
Zaid Masud
2
@ZaidMasud yang saya maksud adalah kelas-kelas dalam, karena mereka dapat mengakses keadaan pribadi dari objek yang melampirkan). Kelas-kelas dalam ini tentu saja juga bisa anonim.
gustafc
11

Jika Anda mengalami masalah ini, kemungkinan besar karena Anda menggunakan warisan di mana Anda harus menggunakan delegasi . Jika Anda perlu menyediakan dua antarmuka yang berbeda, meskipun serupa, untuk model data dasar yang sama, Anda harus menggunakan tampilan untuk menyediakan akses ke data dengan murah menggunakan beberapa antarmuka lain.

Untuk memberikan contoh konkret untuk kasus terakhir, misalkan Anda ingin mengimplementasikan keduanya Collectiondan MyCollection(yang tidak mewarisi dari Collectiondan memiliki antarmuka yang tidak kompatibel). Anda dapat memberikan fungsi Collection getCollectionView()dan MyCollection getMyCollectionView()yang menyediakan implementasi ringan Collectiondan MyCollection, menggunakan data dasar yang sama.

Untuk kasus sebelumnya ... misalkan Anda benar-benar menginginkan array bilangan bulat dan array string. Alih-alih mewarisi dari keduanya List<Integer>dan List<String>, Anda harus memiliki satu anggota tipe List<Integer>dan anggota tipe lain List<String>, dan merujuk ke anggota tersebut, daripada mencoba mewarisi dari keduanya. Bahkan jika Anda hanya memerlukan daftar bilangan bulat, lebih baik menggunakan komposisi / delegasi daripada warisan dalam kasus ini.

Michael Aaron Safyan
sumber
Saya kira tidak. Anda lupa pustaka yang mengharuskan Anda mengimplementasikan antarmuka berbeda agar kompatibel dengannya. Anda dapat mengalami ini dengan menggunakan beberapa pustaka yang saling bertentangan lebih sering daripada Anda mengalami ini di kode Anda sendiri.
nightpool
1
@nightpool jika Anda menggunakan beberapa pustaka yang masing-masing membutuhkan antarmuka yang berbeda, masih tidak diperlukan satu objek untuk mengimplementasikan kedua antarmuka; Anda dapat membuat objek memiliki pengakses untuk mengembalikan masing-masing dari dua antarmuka yang berbeda (dan memanggil pengakses yang sesuai saat meneruskan objek ke salah satu pustaka yang mendasari).
Michael Aaron Safyan
1

Masalah Java "klasik" juga memengaruhi pengembangan Android saya ...
Alasannya tampaknya sederhana:
Lebih banyak framework / pustaka yang harus Anda gunakan, lebih mudah hal-hal menjadi tidak terkontrol ...

Dalam kasus saya, saya memiliki kelas BootStrapperApp diwarisi dari android.app.Application ,
sedangkan kelas yang sama juga harus mengimplementasikan antarmuka Platform dari kerangka kerja MVVM agar bisa terintegrasi.
Tabrakan metode terjadi pada metode getString () , yang diumumkan oleh kedua antarmuka dan harus memiliki implementasi yang berbeda dalam konteks yang berbeda.
Solusinya (ugly..IMO) menggunakan kelas dalam untuk mengimplementasikan semua Platformmetode, hanya karena satu konflik tanda tangan metode kecil ... dalam beberapa kasus, metode pinjaman tersebut bahkan tidak digunakan sama sekali (tetapi mempengaruhi semantik desain utama).
Saya cenderung setuju C # -style indikasi konteks / namespace eksplisit membantu.

Tiancheng
sumber
1
Saya tidak pernah menyadari bagaimana C # bijaksana dan kaya fitur sampai saya mulai menggunakan Java untuk pengembangan Android. Saya mengambil fitur C # begitu saja. Java kekurangan banyak fitur.
Sayuran Sial
1

Satu-satunya solusi yang terlintas dalam pikiran saya adalah menggunakan objek referece ke objek yang Anda inginkan untuk menanamkan banyak antarmuka.

misalnya: misalkan Anda memiliki 2 antarmuka untuk diterapkan

public interface Framework1Interface {

    void method(Object o);
}

dan

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

Anda dapat menyertakannya ke dalam dua objek 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);
    }
}

dan

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

Pada akhirnya kelas yang Anda inginkan harus seperti itu

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

Anda sekarang dapat menggunakan metode getAs * untuk "mengekspos" kelas Anda

tidak semuanya
sumber
0

Anda dapat menggunakan pola Adaptor untuk membuatnya berfungsi. Buat dua adaptor untuk setiap antarmuka dan gunakan itu. Itu harus menyelesaikan masalah.

AlexCon
sumber
-1

Semuanya baik dan bagus bila Anda memiliki kontrol penuh atas semua kode yang dipermasalahkan dan dapat menerapkannya di awal. Sekarang bayangkan Anda memiliki kelas publik yang digunakan di banyak tempat dengan sebuah metode

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

Sekarang Anda perlu meneruskannya ke WizzBangProcessor yang membutuhkan kelas untuk mengimplementasikan WBPInterface ... yang juga memiliki metode getName (), tetapi alih-alih implementasi konkret Anda, antarmuka ini mengharapkan metode untuk mengembalikan nama tipe Pengolahan Wizz Bang.

Dalam C # itu akan menjadi sepele

public class MyClass : WBPInterface{

    private String name;

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

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

    public String getName(){
        return name;
    }
}

Di Java Tough, Anda harus mengidentifikasi setiap titik dalam basis kode yang telah diterapkan di mana Anda perlu mengonversi dari satu antarmuka ke antarmuka lainnya. Tentu perusahaan WizzBangProcessor seharusnya menggunakan getWizzBangProcessName (), tetapi mereka juga pengembang. Dalam konteks mereka getName baik-baik saja. Sebenarnya, di luar Java, sebagian besar bahasa berbasis OO lainnya mendukung ini. Java jarang memaksa semua antarmuka untuk diimplementasikan dengan metode yang sama NAME.

Kebanyakan bahasa lain memiliki kompilator yang dengan senang hati menerima instruksi untuk mengatakan "metode ini di kelas ini yang cocok dengan tanda tangan metode ini dalam antarmuka yang diimplementasikan ini adalah implementasinya". Bagaimanapun, inti dari mendefinisikan antarmuka adalah untuk memungkinkan definisi disarikan dari implementasi. (Jangan biarkan saya mulai memiliki metode default di Interfaces di Java, apalagi default overriding .... karena tentu saja, setiap komponen yang dirancang untuk mobil jalan raya harus dapat menabrak mobil terbang dan bekerja - hei keduanya adalah mobil ... Saya yakin fungsi default katakanlah sat nav Anda tidak akan terpengaruh dengan input pitch and roll default, karena mobil hanya menguap!

pengguna8232920
sumber