Warisan dan Komposisi (hubungan Is-a vs Has-a) di Jawa

1. Gambaran keseluruhan

Warisan dan komposisi - bersama dengan abstraksi, enkapsulasi, dan polimorfisme - adalah asas pengaturcaraan berorientasikan objek (OOP).

Dalam tutorial ini, kita akan membahas asas-asas pewarisan dan komposisi, dan kita akan memberi tumpuan kuat untuk melihat perbezaan antara dua jenis hubungan.

2. Asas Warisan

Pewarisan adalah mekanisme yang kuat namun terlalu digunakan dan disalahgunakan.

Ringkasnya, dengan pewarisan, kelas asas (alias jenis asas) mentakrifkan keadaan dan tingkah laku yang biasa untuk jenis tertentu dan membiarkan subkelas (alias subtipe) menyediakan versi khas keadaan dan tingkah laku tersebut.

Untuk mempunyai idea yang jelas mengenai cara bekerja dengan warisan, mari kita buat contoh naif: Orang kelas asas yang menentukan bidang dan kaedah umum untuk seseorang, sementara Pelayan dan Pelakon subkelas memberikan tambahan kaedah pelaksanaan yang halus.

Inilah kelas Orang :

public class Person { private final String name; // other fields, standard constructors, getters }

Dan ini adalah subkelas:

public class Waitress extends Person { public String serveStarter(String starter) { return "Serving a " + starter; } // additional methods/constructors } 
public class Actress extends Person { public String readScript(String movie) { return "Reading the script of " + movie; } // additional methods/constructors }

Di samping itu, mari buat ujian unit untuk mengesahkan bahawa contoh kelas Pelayan dan Pelakon wanita juga merupakan contoh Orang , sehingga menunjukkan bahawa syarat "is-a" dipenuhi pada tahap jenis:

@Test public void givenWaitressInstance_whenCheckedType_thenIsInstanceOfPerson() { assertThat(new Waitress("Mary", "[email protected]", 22)) .isInstanceOf(Person.class); } @Test public void givenActressInstance_whenCheckedType_thenIsInstanceOfPerson() { assertThat(new Actress("Susan", "[email protected]", 30)) .isInstanceOf(Person.class); }

Penting untuk menekankan di sini aspek semantik warisan . Selain daripada menggunakan semula pelaksanaan kelas Orang , kami telah membuat yang jelas "adalah-a" Hubungan antara jenis asas Orang dan subjenis Waitress dan Pelakon . Pelayan dan pelakon wanita, secara efektif, adalah orang.

Ini boleh menyebabkan kita bertanya: dalam kes penggunaan mana warisan merupakan pendekatan yang tepat untuk diambil?

Sekiranya subtipe memenuhi syarat "is-a" dan terutamanya menyediakan fungsi penambahan lebih jauh ke bawah hierarki kelas, maka pewarisan adalah cara untuk pergi.

Sudah tentu, kaedah penggantian dibenarkan selagi kaedah yang diganti mengekalkan penggantian jenis / subtipe asas yang dipromosikan oleh Prinsip Penggantian Liskov.

Selain itu, kita harus ingat bahawa subtipe mewarisi API jenis asas , yang mana beberapa kes mungkin berlebihan atau hanya tidak diingini.

Jika tidak, kita harus menggunakan komposisi sebagai gantinya.

3. Warisan dalam Corak Reka Bentuk

Walaupun konsensus adalah bahawa kita harus memilih komposisi daripada pewarisan bila mungkin, ada beberapa kes penggunaan khas di mana pewarisan mempunyai tempatnya.

3.1. Corak Supertype Lapisan

Dalam kes ini, kami menggunakan pewarisan untuk memindahkan kod umum ke kelas dasar (supertype), setiap lapisan .

Berikut adalah pelaksanaan asas corak ini di lapisan domain:

public class Entity { protected long id; // setters } 
public class User extends Entity { // additional fields and methods } 

Kami dapat menerapkan pendekatan yang sama dengan lapisan lain dalam sistem, seperti lapisan layanan dan ketekunan.

3.2. Corak Kaedah Templat

Dalam corak kaedah templat, kita dapat menggunakan kelas dasar untuk menentukan bahagian invariant algoritma, dan kemudian menerapkan bahagian varian dalam subkelas :

public abstract class ComputerBuilder { public final Computer buildComputer() { addProcessor(); addMemory(); } public abstract void addProcessor(); public abstract void addMemory(); } 
public class StandardComputerBuilder extends ComputerBuilder { @Override public void addProcessor() { // method implementation } @Override public void addMemory() { // method implementation } }

4. Asas Komposisi

Komposisi adalah mekanisme lain yang disediakan oleh OOP untuk menggunakan semula pelaksanaan.

Singkatnya, komposisi memungkinkan kita memodelkan objek yang terdiri dari objek lain , sehingga menentukan hubungan "has-a" di antara mereka.

Selanjutnya, komposisi adalah bentuk pergaulan terkuat , yang bermaksud bahawa objek yang menyusun atau terkandung oleh satu objek musnah juga ketika objek itu dihancurkan .

Untuk lebih memahami bagaimana komposisi berfungsi, mari kita anggap bahawa kita perlu bekerja dengan objek yang mewakili komputer .

Komputer terdiri daripada pelbagai bahagian, termasuk mikropemproses, memori, kad suara dan sebagainya, jadi kita dapat memodelkan komputer dan setiap bahagiannya sebagai kelas individu.

Inilah cara pelaksanaan kelas Komputer yang mudah :

public class Computer { private Processor processor; private Memory memory; private SoundCard soundCard; // standard getters/setters/constructors public Optional getSoundCard() { return Optional.ofNullable(soundCard); } }

Kelas berikut memodelkan mikroprosesor, memori, dan kad suara (antara muka dihilangkan untuk kepentingan singkat):

public class StandardProcessor implements Processor { private String model; // standard getters/setters }
public class StandardMemory implements Memory { private String brand; private String size; // standard constructors, getters, toString } 
public class StandardSoundCard implements SoundCard { private String brand; // standard constructors, getters, toString } 

Sangat mudah untuk memahami motivasi di sebalik mendorong komposisi ke atas pewarisan. Dalam setiap senario di mana mungkin untuk menjalin hubungan "has-a" yang semantik antara kelas tertentu dengan yang lain, komposisi adalah pilihan yang tepat untuk dibuat.

Dalam contoh di atas, Komputer memenuhi syarat "has-a" dengan kelas yang memodelkan bahagiannya.

Perlu diingat juga bahawa dalam hal ini, objek Komputer yang mengandung memiliki kepemilikan objek yang terkandung jika dan hanya jika objek tersebut tidak dapat digunakan kembali dalam objek Komputer lain . Sekiranya mereka dapat, kita akan menggunakan penggabungan, bukan komposisi, di mana pemilikan tidak tersirat.

5. Komposisi Tanpa Abstraksi

Sebagai alternatif, kita boleh menentukan hubungan komposisi dengan mengkodkan kebergantungan kelas Komputer , dan bukannya menyatakannya dalam konstruktor:

public class Computer { private StandardProcessor processor = new StandardProcessor("Intel I3"); private StandardMemory memory = new StandardMemory("Kingston", "1TB"); // additional fields / methods }

Sudah tentu, ini akan menjadi reka bentuk yang kaku dan berpasangan erat, kerana kita akan menjadikan Komputer sangat bergantung pada pelaksanaan tertentu dari Prosesor dan Memori .

Kami tidak akan memanfaatkan tahap abstraksi yang disediakan oleh antara muka dan suntikan ketergantungan.

Dengan reka bentuk awal berdasarkan antaramuka, kita mendapat reka bentuk berpasangan longgar, yang juga lebih mudah untuk diuji.

6. Kesimpulannya

Dalam artikel ini, kami mempelajari dasar-dasar pewarisan dan komposisi di Jawa, dan kami menjelajahi secara mendalam perbezaan antara dua jenis hubungan ("is-a" vs. "has-a").

Seperti biasa, semua contoh kod yang ditunjukkan dalam tutorial ini terdapat di GitHub.