Warisan berbanding komposisi: Cara memilih

Warisan dan komposisi adalah dua teknik pengaturcaraan yang digunakan pembangun untuk menjalin hubungan antara kelas dan objek. Walaupun warisan berasal dari satu kelas dari kelas yang lain, komposisi mendefinisikan kelas sebagai jumlah bahagiannya.

Kelas dan objek yang dibuat melalui pewarisan digabungkan rapat kerana menukar ibu bapa atau superclass dalam hubungan warisan berisiko melanggar kod anda. Kelas dan objek yang dibuat melalui komposisi digabungkan secara longgar , yang bermaksud bahawa anda dapat menukar bahagian komponen dengan lebih mudah tanpa melanggar kod anda.

Kerana kod yang digabungkan secara longgar menawarkan lebih banyak fleksibiliti, banyak pembangun telah mengetahui bahawa komposisi adalah teknik yang lebih baik daripada pewarisan, tetapi kebenarannya lebih kompleks. Memilih alat pengaturcaraan serupa dengan memilih alat dapur yang betul: Anda tidak akan menggunakan pisau mentega untuk memotong sayur, dan dengan cara yang sama anda tidak boleh memilih komposisi untuk setiap senario pengaturcaraan. 

Dalam Java Challenger ini, anda akan mengetahui perbezaan antara pewarisan dan komposisi serta cara menentukan mana yang betul untuk program anda. Seterusnya, saya akan memperkenalkan anda kepada beberapa aspek penting tetapi mencabar warisan Java: kaedah penggantian, superkata kunci, dan pemutus jenis. Akhirnya, anda akan menguji apa yang telah anda pelajari dengan mengusahakan contoh warisan demi baris untuk menentukan apa outputnya.

Bila hendak menggunakan warisan di Jawa

Dalam pengaturcaraan berorientasikan objek, kita dapat menggunakan warisan ketika kita tahu ada hubungan "adalah" antara anak dan kelas induknya. Beberapa contohnya ialah:

  • Seseorang itu adalah manusia.
  • Kucing adalah haiwan.
  • Sebuah kereta adalah   kenderaan.

Dalam setiap kes, anak atau subkelas adalah versi khas dari ibu bapa atau superclass. Pewarisan dari superclass adalah contoh penggunaan semula kod. Untuk memahami hubungan ini dengan lebih baik, luangkan masa untuk belajar Carkelas, yang diwarisi dari Vehicle:

 class Vehicle { String brand; String color; double weight; double speed; void move() { System.out.println("The vehicle is moving"); } } public class Car extends Vehicle { String licensePlateNumber; String owner; String bodyStyle; public static void main(String... inheritanceExample) { System.out.println(new Vehicle().brand); System.out.println(new Car().brand); new Car().move(); } } 

Semasa anda mempertimbangkan untuk menggunakan warisan, tanyakan pada diri anda apakah subkelas itu benar-benar versi superclass yang lebih khusus. Dalam kes ini, kereta adalah jenis kenderaan, jadi hubungan warisan masuk akal. 

Bila hendak menggunakan komposisi di Jawa

Dalam pengaturcaraan berorientasi objek, kita dapat menggunakan komposisi dalam kasus di mana satu objek "mempunyai" (atau merupakan sebahagian dari) objek lain. Beberapa contohnya ialah:

  • Sebuah kereta mempunyai bateri (bateri adalah bahagian dari kereta).
  • Seseorang mempunyai hati (hati adalah bahagian dari seseorang).
  • Sebuah rumah mempunyai ruang tamu (ruang tamu adalah sebahagian daripada rumah).

Untuk lebih memahami jenis hubungan ini, pertimbangkan komposisi House:

 public class CompositionExample { public static void main(String... houseComposition) { new House(new Bedroom(), new LivingRoom()); // The house now is composed with a Bedroom and a LivingRoom } static class House { Bedroom bedroom; LivingRoom livingRoom; House(Bedroom bedroom, LivingRoom livingRoom) { this.bedroom = bedroom; this.livingRoom = livingRoom; } } static class Bedroom { } static class LivingRoom { } } 

Dalam kes ini, kita tahu bahawa sebuah rumah memiliki ruang tamu dan kamar tidur, jadi kita dapat menggunakan Bedroomdan  LivingRoomobjek dalam komposisi a House

Dapatkan kodnya

Dapatkan kod sumber untuk contoh dalam Java Challenger ini. Anda boleh menjalankan ujian anda sendiri sambil mengikuti contohnya.

Warisan vs komposisi: Dua contoh

Pertimbangkan kod berikut. Adakah ini contoh warisan yang baik?

 import java.util.HashSet; public class CharacterBadExampleInheritance extends HashSet { public static void main(String... badExampleOfInheritance) { BadExampleInheritance badExampleInheritance = new BadExampleInheritance(); badExampleInheritance.add("Homer"); badExampleInheritance.forEach(System.out::println); } 

Dalam kes ini, jawapannya adalah tidak. Kelas kanak-kanak mewarisi banyak kaedah yang tidak akan pernah digunakannya, menghasilkan kod yang digabungkan dengan ketat yang membingungkan dan sukar dijaga. Sekiranya anda melihat dengan teliti, jelas juga bahawa kod ini tidak lulus ujian "adalah".

Sekarang mari kita cuba contoh yang sama menggunakan komposisi:

 import java.util.HashSet; import java.util.Set; public class CharacterCompositionExample { static Set set = new HashSet(); public static void main(String... goodExampleOfComposition) { set.add("Homer"); set.forEach(System.out::println); } 

Menggunakan komposisi untuk senario ini membolehkan  CharacterCompositionExamplekelas menggunakan dua HashSetkaedah sahaja, tanpa mewarisi semuanya. Ini menghasilkan kod yang lebih ringkas dan kurang digabungkan yang akan lebih mudah difahami dan dijaga.

Contoh warisan dalam JDK

Kit Pembangunan Java penuh dengan contoh warisan yang baik:

 class IndexOutOfBoundsException extends RuntimeException {...} class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...} class FileWriter extends OutputStreamWriter {...} class OutputStreamWriter extends Writer {...} interface Stream extends BaseStream
    
      {...} 
    

Perhatikan bahawa dalam setiap contoh ini, kelas anak adalah versi khusus dari induknya; sebagai contoh, IndexOutOfBoundsExceptionadalah sejenis RuntimeException.

Kaedah mengatasi dengan pewarisan Java

Warisan membolehkan kita menggunakan semula kaedah dan atribut lain dari satu kelas dalam kelas baru, yang sangat mudah. Tetapi untuk mewarisi untuk benar-benar berfungsi, kita juga perlu dapat mengubah beberapa tingkah laku yang diwarisi dalam subkelas baru kita. Sebagai contoh, kami mungkin ingin mengkhususkan suara yang dihasilkan Cat:

 class Animal { void emitSound() { System.out.println("The animal emitted a sound"); } } class Cat extends Animal { @Override void emitSound() { System.out.println("Meow"); } } class Dog extends Animal { } public class Main { public static void main(String... doYourBest) { Animal cat = new Cat(); // Meow Animal dog = new Dog(); // The animal emitted a sound Animal animal = new Animal(); // The animal emitted a sound cat.emitSound(); dog.emitSound(); animal.emitSound(); } } 

Ini adalah contoh pewarisan Java dengan kaedah yang diganti. Pertama, kita melanjutkan yang Animalkelas untuk membuat baru Catkelas. Seterusnya, kami mengatasi kaedah Animalkelas emitSound()untuk mendapatkan bunyi tertentu yang dihasilkan Cat. Walaupun kita telah menyatakan jenis kelas sebagai Animal, ketika kita menjadikannya seperti itu, Catkita akan mendapatkan kucing itu. 

Kaedah mengatasi adalah polimorfisme

You might remember from my last post that method overriding is an example of polymorphism, or virtual method invocation.

Does Java have multiple inheritance?

Unlike some languages, such as C++, Java does not allow multiple inheritance with classes. You can use multiple inheritance with interfaces, however. The difference between a class and an interface, in this case, is that interfaces don't keep state.

If you attempt multiple inheritance like I have below, the code won't compile:

 class Animal {} class Mammal {} class Dog extends Animal, Mammal {} 

A solution using classes would be to inherit one-by-one:

 class Animal {} class Mammal extends Animal {} class Dog extends Mammal {} 

Another solution is to replace the classes with interfaces:

 interface Animal {} interface Mammal {} class Dog implements Animal, Mammal {} 

Using ‘super' to access parent classes methods

When two classes are related through inheritance, the child class must be able to access every accessible field, method, or constructor of its parent class. In Java, we use the reserved word super to ensure the child class can still access its parent's overridden method:

 public class SuperWordExample { class Character { Character() { System.out.println("A Character has been created"); } void move() { System.out.println("Character walking..."); } } class Moe extends Character { Moe() { super(); } void giveBeer() { super.move(); System.out.println("Give beer"); } } } 

In this example, Character is the parent class for Moe.  Using super, we are able to access Character's  move() method in order to give Moe a beer.

Using constructors with inheritance

When one class inherits from another, the superclass's constructor always will be loaded first, before loading its subclass. In most cases, the reserved word super will be added automatically to the constructor.  However, if the superclass has a parameter in its constructor, we will have to deliberately invoke the super constructor, as shown below:

 public class ConstructorSuper { class Character { Character() { System.out.println("The super constructor was invoked"); } } class Barney extends Character { // No need to declare the constructor or to invoke the super constructor // The JVM will to that } } 

If the parent class has a constructor with at least one parameter, then we must declare the constructor in the subclass and use super to explicitly invoke the parent constructor. The super reserved word won't be added automatically and the code won't compile without it.  For example:

 public class CustomizedConstructorSuper { class Character { Character(String name) { System.out.println(name + "was invoked"); } } class Barney extends Character { // We will have compilation error if we don't invoke the constructor explicitly // We need to add it Barney() { super("Barney Gumble"); } } } 

Type casting and the ClassCastException

Casting is a way of explicitly communicating to the compiler that you really do intend to convert a given type.  It's like saying, "Hey, JVM, I know what I'm doing so please cast this class with this type." If a class you've cast isn't compatible with the class type you declared, you will get a ClassCastException.

In inheritance, we can assign the child class to the parent class without casting but we can't assign a parent class to the child class without using casting.

Consider the following example:

 public class CastingExample { public static void main(String... castingExample) { Animal animal = new Animal(); Dog dogAnimal = (Dog) animal; // We will get ClassCastException Dog dog = new Dog(); Animal dogWithAnimalType = new Dog(); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark(); Animal anotherDog = dog; // It's fine here, no need for casting System.out.println(((Dog)anotherDog)); // This is another way to cast the object } } class Animal { } class Dog extends Animal { void bark() { System.out.println("Au au"); } } 

When we try to cast an Animal instance to a Dog we get an exception. This is because the Animal doesn't know anything about its child. It could be a cat, a bird, a lizard, etc. There is no information about the specific animal. 

The problem in this case is that we've instantiated Animal like this:

 Animal animal = new Animal(); 

Then tried to cast it like this:

 Dog dogAnimal = (Dog) animal; 

Because we don't have a Dog instance, it's impossible to assign an Animal to the Dog.  If we try, we will get a ClassCastException

In order to avoid the exception, we should instantiate the Dog like this:

 Dog dog = new Dog(); 

then assign it to Animal:

 Animal anotherDog = dog; 

In this case, because  we've extended the Animal class, the Dog instance doesn't even need to be cast; the Animal parent class type simply accepts the assignment.

Casting with supertypes

Adalah mungkin untuk menyatakan Dogdengan supertype Animal, tetapi jika kita ingin menggunakan kaedah tertentu dari Dog, kita perlu membuangnya. Sebagai contoh, bagaimana jika kita ingin menggunakan bark()kaedah ini? The Animalsupertype tidak mempunyai cara untuk mengetahui dengan tepat apa contoh haiwan kita menyeru kedua, jadi kami perlu cast Dogsecara manual sebelum kita boleh ingat kepada bark()kaedah:

 Animal dogWithAnimalType = new Dog(); Dog specificDog = (Dog) dogWithAnimalType; specificDog.bark(); 

Anda juga boleh menggunakan casting tanpa menetapkan objek ke jenis kelas. Pendekatan ini berguna apabila anda tidak mahu menyatakan pemboleh ubah lain:

 System.out.println(((Dog)anotherDog)); // This is another way to cast the object 

Ikuti cabaran warisan Jawa!

Anda telah mempelajari beberapa konsep penting mengenai pewarisan, jadi sekarang saatnya untuk mencuba cabaran pewarisan. Untuk memulakan, kaji kod berikut: