JavaRush /Java Blog /Random-ID /Warisan berganda di Jawa. Perbandingan komposisi dan wari...
HonyaSaar
Level 26
Москва

Warisan berganda di Jawa. Perbandingan komposisi dan warisan

Dipublikasikan di grup Random-ID
Beberapa waktu lalu saya menulis beberapa postingan tentang pewarisan, antarmuka dan komposisi di Java. Pada artikel ini, kita akan melihat pewarisan berganda dan kemudian mempelajari manfaat komposisi dibandingkan pewarisan.
Warisan berganda di Jawa.  Perbandingan komposisi dan warisan - 1

Warisan Berganda di Jawa

Warisan berganda adalah kemampuan untuk membuat kelas dengan beberapa kelas induk. Tidak seperti bahasa berorientasi objek populer lainnya seperti C++, Java tidak mendukung pewarisan banyak kelas. Dia tidak mendukungnya karena kemungkinan menghadapi “masalah berlian” dan sebaliknya lebih memilih untuk memberikan semacam pendekatan komprehensif untuk menyelesaikannya, menggunakan opsi terbaik agar kita dapat mencapai hasil warisan yang serupa.

"Masalah Berlian"

Untuk memahami masalah berlian dengan lebih sederhana, mari kita asumsikan bahwa warisan berganda didukung di Java. Dalam hal ini, kita bisa mendapatkan kelas dengan hierarki yang ditunjukkan pada gambar di bawah. hierarki kelas berlianMari kita asumsikan itu SuperClassadalah kelas abstrak yang menggambarkan metode tertentu, dan kelas-kelas ClassAtersebut ClassBmerupakan kelas nyata. SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
Sekarang, mari kita asumsikan bahwa kelas tersebut ClassCmewarisi dari ClassAdan ClassBpada saat yang sama, dan pada saat yang sama memiliki implementasi berikut:
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
Perhatikan bahwa metode tersebut test()memanggil metode doSomething()kelas induk, yang akan menimbulkan ambiguitas karena kompiler tidak mengetahui metode superkelas mana yang harus dipanggil. Karena bentuk diagram pewarisan kelas pada situasi ini yang menyerupai garis bentuk berlian segi, maka permasalahan tersebut disebut “Masalah Berlian”. Inilah alasan utama mengapa Java tidak mendukung pewarisan banyak kelas. Perhatikan bahwa masalah pewarisan beberapa kelas ini juga dapat terjadi pada tiga kelas yang memiliki setidaknya satu metode umum.

Warisan berganda dan antarmuka

Anda mungkin telah memperhatikan bahwa saya selalu mengatakan "warisan berganda tidak didukung antar kelas", tetapi ini didukung antar antarmuka. Contoh sederhana ditunjukkan di bawah ini: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
Perhatikan bahwa kedua antarmuka memiliki metode dengan nama yang sama. Sekarang katakanlah kita memiliki antarmuka yang mewarisi kedua antarmuka. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
Di sini, semuanya ideal, karena antarmuka hanyalah reservasi/deskripsi suatu metode, dan implementasi metode itu sendiri akan berada di kelas konkret yang mengimplementasikan antarmuka ini, sehingga tidak ada kemungkinan menghadapi ambiguitas dengan pewarisan berganda antarmuka. Inilah sebabnya mengapa kelas-kelas di Java dapat mewarisi dari banyak antarmuka. Mari kita tunjukkan dengan contoh di bawah ini. InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    public static void main(String[] args) {
        InterfaceA objA = new InterfacesImpl();
        InterfaceB objB = new InterfacesImpl();
        InterfaceC objC = new InterfacesImpl();

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
Anda mungkin telah memperhatikan bahwa setiap kali saya mengganti metode yang dijelaskan dalam superclass atau antarmuka, saya menggunakan anotasi @Override. Ini adalah salah satu dari tiga anotasi Java bawaan dan Anda harus selalu menggunakannya saat mengganti metode.

Komposisi sebagai penyelamat

Lalu bagaimana jika kita ingin menggunakan methodA()kelas ClassAdan fungsi methodB()kelas ClassBdi ClassС? Solusi untuk hal ini bisa berupa komposisi - versi yang ditulis ulang ClassCyang mengimplementasikan kedua metode kelas ClassAdan ClassBjuga memiliki implementasi doSomething()untuk salah satu objek. ClassC.java
package com.journaldev.inheritance;

public class ClassC{

    ClassA objA = new ClassA();
    ClassB objB = new ClassB();

    public void test(){
        objA.doSomething();
    }

    public void methodA(){
        objA.methodA();
    }

    public void methodB(){
        objB.methodB();
    }
}

Komposisi atau warisan?

Merupakan praktik pemrograman Java yang baik untuk memanfaatkan komposisi dibandingkan pewarisan. Kami akan melihat beberapa aspek yang mendukung pendekatan ini.
  1. Misalkan kita mempunyai kombinasi kelas induk-pewaris berikut ini:

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    package com.journaldev.inheritance;
    
    public class ClassD extends ClassC{
    
        public int test(){
            return 0;
        }
    }

    Kode di atas dikompilasi dan berfungsi dengan baik, tetapi bagaimana jika ClassCditerapkan secara berbeda:

    package com.journaldev.inheritance;
    
    public class ClassC{
    
        public void methodC(){
        }
    
        public void test(){
        }
    }

    Perhatikan bahwa metode ini test()sudah ada di kelas turunan, tetapi mengembalikan hasil dengan tipe berbeda. Sekarang ClassD, jika Anda menggunakan IDE, itu tidak akan dapat dikompilasi. Anda akan disarankan untuk mengubah tipe pengembalian di kelas turunan atau super.

    Sekarang mari kita bayangkan situasi di mana ada pewarisan kelas multi-level dan superkelas tidak tersedia untuk perubahan kita. Sekarang, untuk menghilangkan kesalahan kompilasi, kami tidak memiliki pilihan lain selain mengubah tanda tangan atau nama metode subkelas. Kami juga harus melakukan perubahan di semua tempat di mana metode ini dipanggil. Jadi, warisan membuat kode kita rapuh.

    Masalah yang dijelaskan di atas tidak pernah terjadi dalam kasus komposisi, dan oleh karena itu membuat komposisi lebih disukai daripada warisan.

  2. Masalah selanjutnya dengan pewarisan adalah kita mengekspos semua metode induk kepada klien. Dan jika superclass tidak dirancang dengan benar dan mengandung celah keamanan. Kemudian, meskipun kita menjaga keamanan sepenuhnya dalam implementasi subkelas kita, kita masih akan bergantung pada implementasi kelas induk yang cacat.

    Komposisi membantu kita dalam menyediakan akses terkontrol ke metode superclass, sedangkan warisan tidak mempertahankan kontrol apa pun atas metodenya. Ini juga merupakan salah satu keunggulan utama komposisi dibandingkan warisan.

  3. Manfaat lain dari komposisi adalah menambah fleksibilitas saat memanggil metode. Implementasi kelas ClassCyang dijelaskan di atas tidak optimal dan menggunakan pengikatan awal ke metode yang dipanggil. Perubahan minimal akan memungkinkan kita membuat pemanggilan metode menjadi fleksibel dan memungkinkan pengikatan yang terlambat (pengikatan saat runtime).

    ClassC.java

    package com.journaldev.inheritance;
    public class ClassC{
        SuperClass obj = null;
        public ClassC(SuperClass o){
            this.obj = o;
        }
        public void test(){
            obj.doSomething();
        }
    
        public static void main(String args[]){
            ClassC obj1 = new ClassC(new ClassA());
            ClassC obj2 = new ClassC(new ClassB());
    
            obj1.test();
            obj2.test();
        }
    }

    Program di atas akan menampilkan:

    doSomething implementation of A
    doSomething implementation of B

    Fleksibilitas dalam pemanggilan metode ini tidak terlihat pada pewarisan, sehingga komposisi merupakan pendekatan terbaik.

  4. Pengujian unit lebih mudah dalam hal komposisi karena kita tahu bahwa untuk semua metode yang digunakan di superkelas kita dapat menghentikan pengujian, sedangkan dalam pewarisan kita sangat bergantung pada superkelas dan tidak tahu bagaimana metode kelas induk. akan digunakan. Jadi, karena pewarisan, kita harus menguji semua metode superclass, yang merupakan pekerjaan yang tidak perlu.

    Idealnya, pewarisan hanya boleh digunakan ketika hubungan “ is-a ” berlaku untuk kelas induk dan anak, jika tidak, komposisi harus diutamakan.

Artikel asli
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION