Kalıtıma karşı kompozisyon: Nasıl seçilir

Kalıtım ve kompozisyon, geliştiricilerin sınıflar ve nesneler arasında ilişki kurmak için kullandıkları iki programlama tekniğidir. Kalıtım bir sınıfı diğerinden türetirken, kompozisyon bir sınıfı parçalarının toplamı olarak tanımlar.

Miras yoluyla oluşturulan sınıflar ve nesneler sıkı bir şekilde birleştirilir çünkü bir miras ilişkisindeki üst veya üst sınıfı değiştirmek kodunuzu kırma riski taşır. Kompozisyon yoluyla oluşturulan sınıflar ve nesneler gevşek bir şekilde birleştirilir , bu da kodunuzu bozmadan bileşen parçalarını daha kolay değiştirebileceğiniz anlamına gelir.

Gevşek bağlı kod daha fazla esneklik sağladığından, birçok geliştirici kompozisyonun kalıtımdan daha iyi bir teknik olduğunu öğrendi, ancak gerçek daha karmaşık. Bir programlama aracı seçmek, doğru mutfak aracını seçmeye benzer: Sebzeleri kesmek için tereyağı bıçağı kullanmazsınız ve aynı şekilde her programlama senaryosu için kompozisyon seçmemelisiniz. 

Bu Java Challenger'da kalıtım ve kompozisyon arasındaki farkı ve programınız için hangisinin doğru olduğuna nasıl karar vereceğinizi öğreneceksiniz. Daha sonra, size Java kalıtımının birkaç önemli ama zorlu yönünü tanıtacağım: yöntemi geçersiz kılma, superanahtar kelime ve tür atama. Son olarak, çıktının ne olması gerektiğini belirlemek için bir miras örneği üzerinde satır satır çalışarak öğrendiklerinizi test edeceksiniz.

Java'da kalıtım ne zaman kullanılır?

Nesne yönelimli programlamada, bir çocuk ve onun üst sınıfı arasında bir "var" ilişkisi olduğunu bildiğimizde kalıtımı kullanabiliriz. Bazı örnekler şunlar olabilir:

  • Bir insan bir insandır.
  • Bir kedi bir olduğunu bir hayvan.
  • Araba bir   araçtır.

Her durumda, alt sınıf veya alt sınıf, üst veya üst sınıfın özelleştirilmiş bir versiyonudur. Üst sınıftan miras almak, kodun yeniden kullanımına bir örnektir. Bu ilişkiyi daha iyi anlamak için Car, şunlardan miras kalan sınıfı inceleyin 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(); } } 

Kalıtımı kullanmayı düşündüğünüzde, kendinize alt sınıfın gerçekten süper sınıfın daha özel bir versiyonu olup olmadığını sorun. Bu durumda araba bir araç türüdür, dolayısıyla miras ilişkisi mantıklıdır. 

Java'da kompozisyon ne zaman kullanılır?

Nesne yönelimli programlamada, bir nesnenin başka bir nesneye "sahip olduğu" (veya bir parçası olduğu) durumlarda kompozisyonu kullanabiliriz. Bazı örnekler şunlar olabilir:

  • Bir araba bir sahip pili (pil parçası olan bir araba).
  • Bir kişinin bir kalbi vardır (kalp , bir kişinin parçasıdır ).
  • Bir ev bir sahiptir : (a oturma odası oturma odası bir parçası olan bir ev).

Bu tür bir ilişkiyi daha iyi anlamak için a'nın bileşimini göz önünde bulundurun 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 { } } 

Bu durumda, bir evin bir oturma odası ve bir yatak odası olduğunu biliyoruz, bu nedenle Bedroomve  LivingRoomnesnelerini a House

Kodu alın

Bu Java Challenger'daki örnekler için kaynak kodunu alın. Örnekleri takip ederken kendi testlerinizi çalıştırabilirsiniz.

Kalıtım ve kompozisyon: İki örnek

Aşağıdaki kodu düşünün. Bu iyi bir miras örneği mi?

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

Bu durumda cevap hayırdır. Alt sınıf, asla kullanmayacağı birçok yöntemi miras alır, bu da hem kafa karıştırıcı hem de bakımı zor olan sıkı bir şekilde bağlı kodla sonuçlanır. Yakından bakarsanız, bu kodun "bir" testini geçmediği de açıktır.

Şimdi aynı örneği kompozisyon kullanarak deneyelim:

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

Bu senaryo için kompozisyon kullanmak, CharacterCompositionExamplesınıfın HashSethepsini miras almadan sadece iki metodunu kullanmasına izin verir  . Bu, anlaşılması ve bakımı daha kolay olacak daha basit, daha az bağlı kodla sonuçlanır.

JDK'da kalıtım örnekleri

Java Geliştirme Kiti, iyi miras örnekleriyle doludur:

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

Bu örneklerin her birinde, alt sınıfın, üst sınıfının özelleştirilmiş bir versiyonu olduğuna dikkat edin; örneğin, IndexOutOfBoundsExceptionbir tür RuntimeException.

Java mirasını geçersiz kılan yöntem

Miras, bir sınıfın yöntemlerini ve diğer niteliklerini yeni bir sınıfta yeniden kullanmamıza izin verir ki bu çok uygundur. Ancak kalıtımın gerçekten işe yaraması için, yeni alt sınıfımızdaki kalıtsal davranışların bir kısmını değiştirebilmemiz de gerekir. Örneğin, a'nın çıkardığı sesi özelleştirmek isteyebiliriz 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(); } } 

Bu, yöntemi geçersiz kılan bir Java kalıtım örneğidir. İlk olarak, yeni bir sınıf oluşturmak için sınıfı genişletiyoruz . Sonra, geçersiz sınıfının belirli ses almak için yöntem yapar. Sınıf türünü olarak ilan etsek bile , onu somutlaştırdığımızda kedinin miyavını elde edeceğiz. AnimalCatAnimalemitSound()CatAnimalCat

Yöntemi geçersiz kılan polimorfizmdir

Son gönderimden, yöntemin geçersiz kılınmasının bir polimorfizm veya sanal yöntem çağrısı örneği olduğunu hatırlayabilirsiniz.

Java'nın birden çok mirası var mı?

C ++ gibi bazı dillerin aksine, Java sınıflarla çoklu kalıtıma izin vermez. Bununla birlikte, arabirimlerle çoklu kalıtım kullanabilirsiniz. Bu durumda, bir sınıf ile bir arayüz arasındaki fark, arayüzlerin durumu korumamasıdır.

Aşağıdaki gibi birden fazla miras almaya çalışırsanız, kod derlenmez:

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

Sınıfları kullanan bir çözüm, birer birer miras almak olacaktır:

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

Diğer bir çözüm, sınıfları arayüzlerle değiştirmektir:

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

Üst sınıf yöntemlerine erişmek için 'süper' kullanma

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

Süper tip Dogile a bildirmek mümkündür Animal, ancak belirli bir metodu çağırmak istiyorsak Dog, onu dönüştürmemiz gerekir. Örnek olarak, bark()yöntemi çağırmak istersek ne olur ? Süper Animaltipin tam olarak hangi hayvan örneğini çağırdığımızı bilmesinin bir yolu yoktur, bu yüzden yöntemi çağırmadan Dogönce manuel olarak döküm yapmalıyız bark():

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

Nesneyi bir sınıf türüne atamadan da çevirmeyi kullanabilirsiniz. Bu yaklaşım, başka bir değişken bildirmek istemediğinizde kullanışlıdır:

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

Java kalıtım mücadelesine katılın!

Bazı önemli kalıtım kavramlarını öğrendiniz, bu yüzden şimdi bir kalıtım mücadelesini deneme zamanı. Başlamak için aşağıdaki kodu inceleyin: