Antarmuka vs kelas Basis

767

Kapan saya harus menggunakan antarmuka dan kapan saya harus menggunakan kelas dasar?

Haruskah selalu menjadi antarmuka jika saya tidak ingin benar-benar menentukan implementasi dasar dari metode?

Jika saya memiliki kelas Anjing dan Kucing. Mengapa saya ingin mengimplementasikan IPet daripada PetBase? Saya dapat memahami memiliki antarmuka untuk ISheds atau IBarks (IMakesNoise?), Karena mereka dapat ditempatkan pada hewan peliharaan dengan basis hewan peliharaan, tetapi saya tidak mengerti yang harus digunakan untuk hewan peliharaan generik.

Howler
sumber
11
Hanya satu hal yang saya pikir Anda harus mempertimbangkan - antarmuka telah dapat menimbulkan beberapa batasan Anda mungkin tidak menyadari sampai tahap yang sangat terlambat. Misalnya, dengan .NET Anda tidak dapat membuat serial variabel anggota antarmuka, jadi jika Anda memiliki Zoo class dan array variabel anggota IAnimal Anda tidak akan bisa membuat serialisasi Zoo (dan itu berarti menulis WebServices atau hal-hal lain yang memerlukan serialisasi akan menjadi sakit).
synhershko
1
Pertanyaan ini mungkin membantu untuk memahami konsep antarmuka. stackoverflow.com/q/8531292/1055241
gprathour
Saya hanya ingin tahu. Aku bertemu di CLR melalui C # sebagai berikut kutipan: I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.. Saya tidak bisa memahami apa yang dimaksud dalam kutipan. Kami dapat membuat beberapa jenis dasar dan membuat jenis turunan untuk salah satu dari mereka, sehingga pengembang dapat memilih jenis dasar. Bisakah seseorang menjelaskan, apa yang saya lewatkan? Saya percaya ini bisa menjadi bagian dari pertanyaan ini. Atau haruskah saya memposting satu lagi tentang kutipan spesifik?
qqqqqqq

Jawaban:

501

Mari ambil contoh Anda tentang kelas Anjing dan Kucing, dan mari kita ilustrasikan menggunakan C #:

Baik anjing dan kucing adalah hewan, khususnya mamalia berkaki empat (hewan terlalu umum). Mari kita asumsikan bahwa Anda memiliki kelas abstrak Mamalia, untuk keduanya:

public abstract class Mammal

Kelas dasar ini mungkin akan memiliki metode default seperti:

  • Makan
  • Pasangan

Semuanya adalah perilaku yang memiliki implementasi yang kurang lebih sama antara kedua spesies. Untuk menentukan ini, Anda harus:

public class Dog : Mammal
public class Cat : Mammal

Sekarang anggaplah ada mamalia lain, yang biasanya akan kita lihat di kebun binatang:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Ini akan tetap valid karena pada intinya fungsionalitas Feed()dan Mate()masih akan sama.

Namun, jerapah, badak, dan kuda nil bukanlah hewan yang bisa membuat hewan peliharaan. Di situlah antarmuka akan berguna:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

Implementasi untuk kontrak di atas tidak akan sama antara kucing dan anjing; menempatkan implementasinya dalam kelas abstrak untuk diwariskan akan menjadi ide yang buruk.

Definisi Anjing dan Kucing Anda sekarang akan terlihat seperti:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Secara teoritis Anda dapat mengesampingkan mereka dari kelas dasar yang lebih tinggi, tetapi pada dasarnya antarmuka memungkinkan Anda untuk menambahkan hanya hal-hal yang Anda butuhkan ke dalam kelas tanpa perlu warisan.

Akibatnya, karena Anda biasanya hanya dapat mewarisi dari satu kelas abstrak (dalam sebagian besar bahasa OO yang diketik secara statis yaitu ... pengecualian termasuk C ++) tetapi dapat mengimplementasikan beberapa antarmuka, ini memungkinkan Anda untuk membangun objek secara ketat sesuai kebutuhan .

Jon Limjap
sumber
149
Saya pikir itu tidak sesederhana itu. Anda telah sedikit mengubah pertanyaan (persyaratan) agar antarmuka lebih masuk akal. Anda harus selalu bertanya pada diri sendiri apakah Anda mendefinisikan kontrak (antarmuka) atau implementasi bersama (kelas dasar).
David Pokluda
18
Antarmuka adalah kontrak. Anda hanya mengekspos bagian dari kontrak yang diperlukan layanan. Jika Anda memiliki 'PettingZoo', Anda tentu tidak ingin mengekspos 'Pasangan' kepada pengguna!
Anthony Mastrean
10
@ David Touche, meskipun saya melakukannya untuk lebih menggambarkan apa antarmuka untuk dan apa kelas abstrak untuk vis-a-vis pemahamannya. Seekor anjing dan kucing tampaknya bukan persyaratan yang kaku!
Jon Limjap
2
Perlu dicatat bahwa dalam lingkungan JIT-ing yang diinterpretasikan (khususnya JVM), panggilan metode virtual secara signifikan lebih cepat daripada panggilan metode antarmuka , tergantung pada konteksnya. Saya menekankan "konteks" karena JVM sering dapat mengoptimalkan pencarian metode lambat. (misalnya, jika daftar pewaris antarmuka cenderung menjadi instance dari kelas yang sama. Ini menyedihkan membuat pembandingan sedikit sulit.) Jika Anda mencoba mengoptimalkan kinerja sesuatu yang sensitif, dan hanya kemudian, Anda harus mempertimbangkan ini.
Philip Guin
14
Juga, antarmuka memungkinkan untuk batu peliharaan, memungkinkannya untuk dimandikan dan diajarkan trik, tetapi memberi makan dan kawin tidak akan didukung karena itu akan konyol untuk batu.
eclux
146

Yah, Josh Bloch berkata pada dirinya dalam Java 2d yang Efektif :

Lebih suka antarmuka daripada kelas abstrak

Beberapa poin utama:

  • Kelas yang ada dapat dengan mudah dipasang untuk mengimplementasikan antarmuka baru . Yang harus Anda lakukan adalah menambahkan metode yang diperlukan jika belum ada dan menambahkan klausa implementasi ke deklarasi kelas.

  • Antarmuka ideal untuk mendefinisikan mixin . Secara longgar, mixin adalah tipe yang dapat diterapkan oleh sebuah kelas selain "tipe primer" -nya untuk menyatakan bahwa ia menyediakan beberapa perilaku opsional. Sebagai contoh, Comparable adalah antarmuka mixin yang memungkinkan suatu kelas untuk menyatakan bahwa instansnya diurutkan sehubungan dengan objek lain yang dapat dibandingkan satu sama lain.

  • Antarmuka memungkinkan konstruksi kerangka kerja tipe nonhierarkis . Tipe hierarki sangat bagus untuk mengatur beberapa hal, tetapi hal-hal lain tidak termasuk dalam hierarki yang kaku.

  • Antarmuka memungkinkan peningkatan fungsionalitas yang aman dan kuat melalui idiom kelas. Jika Anda menggunakan kelas abstrak untuk mendefinisikan tipe, Anda meninggalkan programmer yang ingin menambahkan fungsionalitas tanpa alternatif selain menggunakan warisan.

Selain itu, Anda dapat menggabungkan keutamaan antarmuka dan kelas abstrak dengan memberikan kelas implementasi kerangka abstrak untuk digunakan dengan setiap antarmuka nontrivial yang Anda ekspor.

Di sisi lain, antarmuka sangat sulit untuk berkembang. Jika Anda menambahkan metode ke antarmuka, itu akan merusak semua implementasinya.

PS .: Beli bukunya. Jauh lebih detail.

Marcio Aguiar
sumber
71
Setiap kali perubahan perlu dilakukan ke antarmuka, cara yang tidak putus-putus untuk melakukannya adalah membuat antarmuka baru yang diwarisi dari yang lama. Ini menjaga implementasi yang ada dan memungkinkan Anda melakukan apa pun yang Anda inginkan di yang baru.
Scott Lawrence
5
kami memiliki "Prinsip Pemisahan Antarmuka". Prinsip ini mengajarkan kita untuk berhati-hati bagaimana kita menulis antarmuka kita. Ketika kita menulis antarmuka kita harus berhati-hati untuk menambahkan hanya metode yang seharusnya ada di sana. Jika kita menambahkan metode yang seharusnya tidak ada, kelas yang mengimplementasikan antarmuka harus menerapkan metode tersebut juga. Sebagai contoh jika kita membuat antarmuka yang disebut Worker dan menambahkan metode istirahat makan siang, semua pekerja harus menerapkannya. Bagaimana jika pekerja itu adalah robot? Sebagai kesimpulan, Antarmuka yang berisi metode yang tidak spesifik untuk itu disebut antarmuka tercemar atau gemuk.
Eklavyaa
Sejak Java 8, metode Default memungkinkan Anda untuk menambahkan fungsionalitas baru ke antarmuka dan memastikan kompatibilitas mundur untuk kelas yang ada yang mengimplementasikan antarmuka itu. Metode default dipanggil secara default, jika tidak diganti di kelas implementasi. Semua kelas pelaksana dapat mengganti metode default atau mereka dapat langsung memanggilnya menggunakan instance.defaultMethod ()
Arpit Rastogi
118

Antarmuka dan kelas dasar mewakili dua bentuk hubungan yang berbeda.

Warisan (kelas dasar) mewakili hubungan "is-a". Misalnya anjing atau kucing peliharaan "is-a". Hubungan ini selalu mewakili (tunggal) tujuan kelas (dalam hubungannya dengan "single prinsip tanggung jawab" ).

Antarmuka , di sisi lain, merupakan fitur tambahan dari suatu kelas. Saya menyebutnya hubungan "adalah", seperti dalam " Foodapat digunakan", karenanya IDisposableantarmuka dalam C #.

Thomas Danecker
sumber
10
Dari semua jawaban, jawaban ini memberikan campuran singkat yang terbaik tanpa kehilangan kejelasan
Matt Wilko
Seseorang pernah mengatakan kepada saya untuk menggunakan antarmuka ketika ada hubungan "has-a". Saya tidak yakin apakah itu selalu benar; Laptop saya memiliki layar, jadi apakah laptop harus menerapkan IScreen, atau memiliki properti Layar? Yang terakhir sepertinya lebih alami bagi saya.
Berend
1
@berend lucu bahwa Anda menulis itu karena layar diimplementasikan melalui antarmuka - VGA, HDMI dll
Konstantin
Hanya karena itu OOP, kita harus memperluas imajinasi kita untuk mengikuti skenario kehidupan nyata. Itu tidak selalu berlaku.
Crismogram
110

Gaya modern adalah mendefinisikan IPet dan PetBase.

Keuntungan dari antarmuka adalah bahwa kode lain dapat menggunakannya tanpa ikatan apa pun dengan kode yang dapat dieksekusi lainnya. Benar-benar "bersih." Antarmuka juga dapat dicampur.

Tetapi kelas dasar berguna untuk implementasi sederhana dan utilitas umum. Jadi sediakan kelas dasar abstrak juga untuk menghemat waktu dan kode.

Jason Cohen
sumber
ambil kue kamu dan makan juga!
Darren Kopp
3
Antarmuka mendefinisikan bagaimana kelas lain dapat menggunakan kode Anda. Kelas dasar membantu implementer untuk mengimplementasikan antarmuka Anda. Dua hal berbeda untuk dua tujuan berbeda.
Bill Michell
27
Tidak ada yang "modern" di sini. Kelas dasar dan antarmuka dengan API yang sama hanya mubazir. Anda dapat menggunakan pendekatan ini dalam beberapa kasus tetapi Anda tidak boleh menyamaratakan!
smentek
3
Komentar yang paling didukung untuk jawaban yang didukung kedua sebenarnya tidak setuju dengan jawaban itu sendiri, menarik. Saya harus mengatakan saya melihat banyak contoh antarmuka dan kelas dasar hidup berdampingan. Dalam pengertian itu, ini adalah cara "modern". Misalnya, dalam pola MVVM, sebenarnya ada kelas ViewModelBase yang mengimplementasikan INotifyPropertyChanged. Tetapi ketika kolega saya bertanya mengapa saya memiliki kelas dasar, alih-alih mengimplementasikan antarmuka dalam setiap model tampilan, saya tidak tahu bagaimana meyakinkannya
tete
1
Benar. Ini bukan pertanyaan 1 atau yang lainnya. Mereka ada untuk mengatasi 2 masalah yang sangat berbeda. Antarmuka adalah kontrak yang harus ditangani oleh kelas pelaksana. Mereka telah menemukan bantuan terbaru (kadang-kadang secara fanatik) untuk aspek IoC dan TDD. Kelas abstrak / Base berfungsi untuk mengelompokkan logika dan properti yang secara hierarkis umum. Ini mengurangi kode duplikat yang pada gilirannya meningkatkan kemampuan pemeliharaan dan membuatnya kurang rentan terhadap kesalahan.
Datang
63

Antarmuka

  • Menentukan kontrak antara 2 modul. Tidak dapat memiliki implementasi apa pun.
  • Sebagian besar bahasa memungkinkan Anda untuk mengimplementasikan banyak antarmuka
  • Mengubah antarmuka adalah perubahan besar. Semua implementasi perlu dikompilasi ulang / dimodifikasi.
  • Semua anggota bersifat publik. Implementasi harus menerapkan semua anggota.
  • Antarmuka membantu dalam Decoupling. Anda bisa menggunakan mock frameworks untuk mengejek apa pun di balik antarmuka
  • Antarmuka biasanya menunjukkan semacam perilaku
  • Implementasi antarmuka dipisahkan / terisolasi satu sama lain

Kelas dasar

  • Memungkinkan Anda menambahkan beberapa implementasi default yang Anda dapatkan secara gratis melalui derivasi
  • Kecuali C ++, Anda hanya bisa berasal dari satu kelas. Kalaupun bisa dari berbagai kelas, biasanya itu ide yang buruk.
  • Mengubah kelas dasar relatif mudah. Derivasi tidak perlu melakukan sesuatu yang istimewa
  • Kelas basis dapat mendeklarasikan fungsi yang dilindungi dan publik yang dapat diakses oleh derivasi
  • Abstrak Kelas dasar tidak dapat diejek dengan mudah seperti antarmuka
  • Kelas dasar biasanya menunjukkan tipe hierarki (IS A)
  • Derivasi kelas mungkin tergantung pada beberapa perilaku dasar (memiliki pengetahuan yang rumit tentang implementasi orangtua). Hal-hal bisa berantakan jika Anda membuat perubahan pada implementasi basis untuk satu pria dan menghancurkan yang lainnya.
Gishu
sumber
Catatan: pedoman desain kerangka merekomendasikan menggunakan kelas dasar (sebagai lawan antarmuka) karena mereka versi yang lebih baik. Menambahkan metode baru ke kelas dasar abstrak di vNext adalah perubahan yang tidak putus-putus ..
Gishu
59

Secara umum, Anda harus memilih antarmuka daripada kelas abstrak. Salah satu alasan untuk menggunakan kelas abstrak adalah jika Anda memiliki implementasi umum di antara kelas-kelas konkret. Tentu saja, Anda masih harus mendeklarasikan antarmuka (IPet) dan memiliki kelas abstrak (PetBase) mengimplementasikan antarmuka itu. Menggunakan antarmuka kecil dan berbeda, Anda dapat menggunakan banyak untuk meningkatkan fleksibilitas lebih lanjut. Antarmuka memungkinkan jumlah maksimum fleksibilitas dan portabilitas tipe lintas batas. Saat melewati referensi melintasi batas, selalu melewati antarmuka dan bukan tipe konkret. Ini memungkinkan pihak penerima untuk menentukan implementasi konkret dan memberikan fleksibilitas maksimum. Ini benar-benar benar ketika memprogram dengan gaya TDD / BDD.

Geng Empat menyatakan dalam buku mereka "Karena warisan memperlihatkan subkelas ke detail implementasi orang tuanya, sering dikatakan bahwa 'warisan memecah enkapsulasi". Saya percaya ini benar.

Kilhoffer
sumber
Ya. Secara pribadi, saya percaya ini keliru. Antarmuka harus memiliki fungsi minimum untuk suatu jenis, dan kelas dasar harus menyediakan kerangka kerja yang kaya tempat kustomisasi dapat dibangun. Masukkan itu dalam antarmuka dan Anda membuatnya sangat sulit untuk diimplementasikan.
Tidak ada yang mengatakan antarmuka Anda harus besar. Lebih kecil, banyak antarmuka dan kelas basis yang lebih kaya membuat API yang hebat.
Kilhoffer
3
Apakah hanya saya, atau sebagian besar kelas "pekerja biasa" berbagi implementasi yang sama? Dalam konteks itu bertentangan dengan aturan umum Anda yang mendukung antarmuka. Saya akan menyatakan kembali generalisasi Anda menjadi 2 generalisasi: Kelas-kelas umum yang tidak mengandung atau sedikit logika harus mengimplementasikan antarmuka. Kelas-kelas umum yang mengandung jumlah logika "layak" harus berasal dari kelas dasar (karena kemungkinan besar mereka akan berbagi fungsionalitas).
goku_da_master
@Kilhoffer "Antarmuka memungkinkan jumlah maksimum fleksibilitas dan portabilitas jenis lintas batas" tolong uraikan pernyataan ini.
JAVA
49

Ini cukup .NET spesifik, tetapi buku Kerangka Desain Pedoman berpendapat bahwa di kelas umum memberikan lebih banyak fleksibilitas dalam kerangka kerja yang berkembang. Setelah antarmuka dikirimkan, Anda tidak mendapatkan kesempatan untuk mengubahnya tanpa melanggar kode yang menggunakan antarmuka itu. Namun dengan kelas, Anda dapat memodifikasinya dan tidak merusak kode yang menautkannya. Selama Anda melakukan modifikasi yang tepat, yang mencakup penambahan fungsionalitas baru, Anda akan dapat memperluas dan mengembangkan kode Anda.

Krzysztof Cwalina mengatakan di halaman 81:

Selama tiga versi .NET Framework, saya telah berbicara tentang pedoman ini dengan beberapa pengembang di tim kami. Banyak dari mereka, termasuk mereka yang awalnya tidak setuju dengan pedoman, mengatakan bahwa mereka menyesal telah mengirimkan beberapa API sebagai antarmuka. Saya belum pernah mendengar satu pun kasus di mana seseorang menyesal bahwa mereka mengirim kelas.

Yang sedang berkata pasti ada tempat untuk antarmuka. Sebagai pedoman umum selalu menyediakan implementasi kelas dasar abstrak antarmuka jika tidak lain sebagai contoh cara untuk mengimplementasikan antarmuka. Dalam kasus terbaik, kelas dasar akan menghemat banyak pekerjaan.

fryguybob
sumber
19

Juan,

Saya suka menganggap antarmuka sebagai cara untuk mengkarakterisasi kelas. Kelas jenis anjing tertentu, misalnya YorkshireTerrier, mungkin merupakan turunan dari kelas induk anjing, tetapi ia juga mengimplementasikan IFurry, IStubby, dan IYippieDog. Jadi kelas mendefinisikan apa itu kelas tetapi antarmuka memberi tahu kita tentang hal itu.

Keuntungan dari ini adalah memungkinkan saya untuk, misalnya, mengumpulkan semua IYippieDog dan membuangnya ke dalam koleksi Ocean saya. Jadi sekarang saya dapat menjangkau satu set objek tertentu dan menemukan yang memenuhi kriteria yang saya lihat tanpa memeriksa kelas terlalu dekat.

Saya menemukan bahwa antarmuka benar-benar harus mendefinisikan sub-set perilaku publik suatu kelas. Jika ia mendefinisikan semua perilaku publik untuk semua kelas yang mengimplementasikannya maka biasanya tidak perlu ada. Mereka tidak memberi tahu saya apa pun yang bermanfaat.

Namun pemikiran ini bertentangan dengan gagasan bahwa setiap kelas harus memiliki antarmuka dan Anda harus kode ke antarmuka. Tidak apa-apa, tetapi Anda berakhir dengan banyak antarmuka satu-ke-satu ke kelas dan itu membuat segalanya membingungkan. Saya mengerti bahwa idenya adalah tidak benar-benar memerlukan biaya apa pun untuk dilakukan dan sekarang Anda dapat bertukar barang dengan mudah. Namun, saya jarang melakukannya. Sebagian besar waktu saya hanya memodifikasi kelas yang ada di tempat dan memiliki masalah yang sama persis seperti yang selalu saya lakukan jika antarmuka publik kelas itu perlu diubah, kecuali saya sekarang harus mengubahnya di dua tempat.

Jadi, jika Anda berpikir seperti saya, Anda pasti akan mengatakan bahwa Kucing dan Anjing itu dapat disetel. Ini adalah karakterisasi yang cocok dengan keduanya.

Bagian lain dari ini adalah apakah mereka harus memiliki kelas dasar yang sama? Pertanyaannya adalah apakah mereka perlu diperlakukan secara luas sebagai hal yang sama. Tentu saja mereka berdua Hewan, tetapi apakah itu sesuai dengan bagaimana kita akan menggunakannya bersama-sama.

Katakanlah saya ingin mengumpulkan semua kelas hewan dan menaruhnya di wadah bahtera saya.

Atau apakah mereka perlu mamalia? Mungkin kita membutuhkan semacam pabrik susu hewan silang?

Apakah mereka bahkan perlu dihubungkan bersama sama sekali? Apakah cukup dengan hanya mengetahui bahwa keduanya dapat disetel?

Saya sering merasakan keinginan untuk mendapatkan seluruh hirarki kelas ketika saya benar-benar hanya membutuhkan satu kelas. Saya melakukannya untuk mengantisipasi suatu hari nanti saya mungkin membutuhkannya dan biasanya saya tidak pernah melakukannya. Bahkan ketika saya melakukannya, saya biasanya menemukan saya harus melakukan banyak hal untuk memperbaikinya. Itu karena kelas pertama yang saya buat bukanlah Anjing, saya tidak seberuntung itu, melainkan Platypus. Sekarang seluruh hierarki kelas saya didasarkan pada kasus aneh dan saya memiliki banyak kode yang terbuang.

Anda mungkin juga menemukan di beberapa titik bahwa tidak semua Kucing dapat IP (seperti yang tidak berambut). Sekarang Anda bisa memindahkan Antarmuka itu ke semua kelas turunan yang cocok. Anda akan menemukan bahwa perubahan yang jauh lebih sedikit melanggar yaitu Kucing yang tiba-tiba tidak lagi berasal dari PettableBase.

Flory
sumber
18

Berikut adalah definisi dasar dan kelas antarmuka dan dasar:

  • Kelas dasar = pewarisan objek.
  • Antarmuka = ​​warisan fungsional.

Bersulang

RWendi
sumber
12

Saya sarankan menggunakan komposisi alih-alih pewarisan bila memungkinkan. Gunakan antarmuka tetapi gunakan objek anggota untuk implementasi basis. Dengan begitu, Anda dapat mendefinisikan pabrik yang membuat objek Anda agar berperilaku dengan cara tertentu. Jika Anda ingin mengubah perilaku maka Anda membuat metode pabrik baru (atau pabrik abstrak) yang menciptakan berbagai jenis sub-objek.

Dalam beberapa kasus, Anda mungkin menemukan bahwa objek utama Anda tidak memerlukan antarmuka sama sekali, jika semua perilaku yang dapat diubah didefinisikan dalam objek pembantu.

Jadi, alih-alih IPet atau PetBase, Anda mungkin berakhir dengan Pet yang memiliki parameter IFurBehavior. Parameter IFurBehavior diatur oleh metode CreateDog () dari PetFactory. Parameter inilah yang dipanggil untuk metode shed ().

Jika Anda melakukan ini, Anda akan menemukan kode Anda jauh lebih fleksibel dan sebagian besar objek sederhana Anda berurusan dengan perilaku seluruh sistem yang sangat dasar.

Saya merekomendasikan pola ini bahkan dalam berbagai bahasa warisan.

Joe
sumber
12

Ini dijelaskan dengan baik di artikel Java World ini .

Secara pribadi, saya cenderung menggunakan antarmuka untuk mendefinisikan antarmuka - yaitu bagian dari desain sistem yang menentukan bagaimana sesuatu harus diakses.

Bukan hal yang aneh bahwa saya akan memiliki kelas yang mengimplementasikan satu atau lebih antarmuka.

Kelas abstrak yang saya gunakan sebagai dasar untuk sesuatu yang lain.

Berikut ini adalah kutipan dari artikel yang disebutkan di atas artikel JavaWorld.com, penulis Tony Sintes, 04/20/01


Antarmuka vs kelas abstrak

Memilih antarmuka dan kelas abstrak bukanlah proposisi salah satu / atau satu. Jika Anda perlu mengubah desain, buatlah antarmuka. Namun, Anda mungkin memiliki kelas abstrak yang menyediakan beberapa perilaku default. Kelas abstrak adalah kandidat yang sangat baik di dalam kerangka kerja aplikasi.

Kelas abstrak memungkinkan Anda mendefinisikan beberapa perilaku; mereka memaksa subclass Anda untuk menyediakan yang lain. Misalnya, jika Anda memiliki kerangka kerja aplikasi, kelas abstrak dapat memberikan layanan default seperti penanganan acara dan pesan. Layanan-layanan itu memungkinkan aplikasi Anda terhubung ke kerangka kerja aplikasi Anda. Namun, ada beberapa fungsi khusus aplikasi yang hanya dapat dilakukan oleh aplikasi Anda. Fungsionalitas tersebut dapat mencakup tugas-tugas startup dan shutdown, yang seringkali tergantung pada aplikasi. Jadi alih-alih mencoba mendefinisikan perilaku itu sendiri, kelas dasar abstrak dapat mendeklarasikan metode shutdown dan startup abstrak. Kelas dasar tahu bahwa itu membutuhkan metode-metode itu, tetapi kelas abstrak memungkinkan kelas Anda mengakui bahwa ia tidak tahu bagaimana melakukan tindakan-tindakan itu; ia hanya tahu bahwa ia harus memulai tindakan. Ketika saatnya untuk memulai, kelas abstrak dapat memanggil metode startup. Ketika kelas dasar memanggil metode ini, Java memanggil metode yang didefinisikan oleh kelas anak.

Banyak pengembang lupa bahwa kelas yang mendefinisikan metode abstrak dapat memanggil metode itu juga. Kelas abstrak adalah cara terbaik untuk membuat hierarki warisan yang direncanakan. Mereka juga merupakan pilihan yang baik untuk kelas tidak bertingkat di hierarki kelas.

Antarmuka kelas vs.

Beberapa mengatakan Anda harus mendefinisikan semua kelas dalam hal antarmuka, tetapi saya pikir rekomendasi tampaknya agak ekstrem. Saya menggunakan antarmuka ketika saya melihat bahwa sesuatu dalam desain saya akan sering berubah.

Misalnya, pola Strategi memungkinkan Anda menukar algoritma dan proses baru ke dalam program Anda tanpa mengubah objek yang menggunakannya. Media player mungkin tahu cara memutar file CD, MP3, dan wav. Tentu saja, Anda tidak ingin mengubah kode algoritma pemutaran ke pemutar; itu akan membuat sulit untuk menambahkan format baru seperti AVI. Selanjutnya, kode Anda akan dikotori dengan pernyataan kasus yang tidak berguna. Dan untuk menambahkan penghinaan pada cedera, Anda perlu memperbarui pernyataan kasus tersebut setiap kali Anda menambahkan algoritma baru. Secara keseluruhan, ini bukan cara yang berorientasi objek untuk memprogram.

Dengan pola Strategi, Anda cukup merangkum algoritma di belakang suatu objek. Jika Anda melakukannya, Anda dapat menyediakan plug-in media baru kapan saja. Mari kita sebut kelas plug-in MediaStrategy. Objek itu akan memiliki satu metode: playStream (Stream s). Jadi untuk menambahkan algoritma baru, kami cukup memperluas kelas algoritma kami. Sekarang, ketika program menemukan jenis media baru, ia hanya mendelegasikan permainan aliran ke strategi media kami. Tentu saja, Anda akan memerlukan beberapa saluran air untuk membuat strategi algoritma yang dibutuhkan dengan benar.

Ini adalah tempat yang sangat baik untuk menggunakan antarmuka. Kami telah menggunakan pola Strategi, yang dengan jelas menunjukkan tempat dalam desain yang akan berubah. Dengan demikian, Anda harus mendefinisikan strategi sebagai antarmuka. Anda umumnya harus mendukung antarmuka daripada warisan ketika Anda ingin objek memiliki tipe tertentu; dalam hal ini, MediaStrategy. Mengandalkan pewarisan untuk identitas tipe adalah berbahaya; itu mengunci Anda ke dalam hierarki warisan tertentu. Java tidak mengizinkan banyak pewarisan, jadi Anda tidak bisa memperluas sesuatu yang memberi Anda implementasi yang bermanfaat atau lebih banyak tipe identitas.

Richard Harrison
sumber
2
+1. "Mengandalkan pewarisan untuk identitas tipe adalah berbahaya; itu mengunci Anda ke dalam hierarki warisan tertentu." Kalimat itu dengan sempurna menggambarkan alasan saya memilih antarmuka.
Insinyur
Dan lebih dari itu, alih-alih memperluas, buat implementasi di balik setiap metode antarmuka Anda.
Insinyur
10

Juga perlu diingat untuk tidak terhanyut dalam OO ( lihat blog ) dan selalu memodelkan objek berdasarkan perilaku yang diperlukan, jika Anda mendesain aplikasi di mana satu-satunya perilaku yang Anda butuhkan adalah nama generik dan spesies untuk hewan maka Anda hanya perlu satu hewan kelas dengan properti untuk nama, bukan jutaan kelas untuk setiap hewan yang mungkin ada di dunia.

Sijin
sumber
10

Saya memiliki aturan praktis yang kasar

Fungsi: kemungkinan berbeda di semua bagian: Antarmuka.

Data, dan fungsi, sebagian besar akan sama, bagian berbeda: kelas abstrak.

Data, dan fungsionalitas, benar-benar berfungsi, jika diperluas hanya dengan sedikit perubahan: kelas biasa (konkret)

Data dan fungsionalitas, tidak ada perubahan yang direncanakan: kelas biasa (konkret) dengan pengubah akhir.

Data, dan mungkin fungsionalitas: baca-saja: anggota enum.

Ini sangat kasar dan siap dan sama sekali tidak didefinisikan secara ketat, tetapi ada spektrum dari antarmuka di mana semuanya dimaksudkan untuk diubah menjadi enums di mana semuanya diperbaiki sedikit seperti file read-only.

Akhil Jain
sumber
7

Antarmuka harus kecil. Sangat kecil. Jika Anda benar-benar menghancurkan objek, maka antarmuka Anda mungkin hanya akan berisi beberapa metode dan properti yang sangat spesifik.

Kelas abstrak adalah jalan pintas. Adakah hal-hal yang semua turunan dari saham PetBase yang dapat Anda kode sekali dan dilakukan? Jika ya, maka sudah waktunya untuk kelas abstrak.

Kelas abstrak juga membatasi. Sementara mereka memberi Anda jalan pintas yang bagus untuk menghasilkan objek anak, objek apa pun yang diberikan hanya dapat menerapkan satu kelas abstrak. Sering kali, saya menemukan ini sebagai batasan kelas Abstrak, dan inilah mengapa saya menggunakan banyak antarmuka.

Kelas abstrak dapat berisi beberapa antarmuka. Kelas abstrak PetBase Anda dapat menerapkan IPet (hewan peliharaan memiliki pemilik) dan IDigestion (hewan peliharaan makan, atau setidaknya mereka seharusnya). Namun, PetBase mungkin tidak akan mengimplementasikan IMammal, karena tidak semua hewan peliharaan adalah mamalia dan tidak semua mamalia adalah hewan peliharaan. Anda dapat menambahkan MammalPetBase yang memperluas PetBase dan menambahkan IMammal. FishBase dapat memiliki PetBase dan menambahkan IFish. IFish akan memiliki ISwim dan IUnderwaterBreather sebagai antarmuka.

Ya, contoh saya terlalu rumit untuk contoh sederhana, tapi itu bagian dari hal hebat tentang bagaimana antarmuka dan kelas abstrak bekerja bersama.

Jarrett Meyer
sumber
7

Sumber : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C # adalah bahasa yang luar biasa yang telah matang dan berevolusi selama 14 tahun terakhir. Ini bagus untuk kami para pengembang karena bahasa yang matang memberi kami sejumlah besar fitur bahasa yang dapat kami gunakan.

Namun, dengan banyak kekuatan menjadi banyak tanggung jawab. Beberapa fitur ini dapat disalahgunakan, atau terkadang sulit untuk memahami mengapa Anda memilih untuk menggunakan satu fitur di atas yang lain. Selama bertahun-tahun, fitur yang telah saya lihat banyak pengembang geluti adalah ketika memilih untuk menggunakan antarmuka atau memilih untuk menggunakan kelas abstrak. Keduanya memiliki kelebihan dan kekurangan serta waktu dan tempat yang tepat untuk menggunakannya. Tapi bagaimana cara kita memutuskan ???

Keduanya menyediakan untuk penggunaan kembali fungsionalitas umum antara jenis. Perbedaan yang paling jelas adalah bahwa antarmuka tidak memberikan implementasi untuk fungsi mereka sedangkan kelas abstrak memungkinkan Anda untuk menerapkan beberapa perilaku "dasar" atau "default" dan kemudian memiliki kemampuan untuk "menimpa" perilaku default ini dengan tipe turunan kelas jika perlu .

Ini semua baik dan bagus dan menyediakan untuk penggunaan kembali kode dan mematuhi prinsip pengembangan perangkat lunak KERING (Jangan Ulangi Diri Sendiri). Kelas abstrak sangat bagus untuk digunakan ketika Anda memiliki hubungan “adalah”.

Misalnya: Anjing jenis golden retriever “adalah”. Begitu juga pudel. Mereka berdua bisa menggonggong, seperti semua anjing bisa. Namun, Anda mungkin ingin menyatakan bahwa taman pudel sangat berbeda dari kulit anjing "default". Oleh karena itu, masuk akal bagi Anda untuk mengimplementasikan sesuatu sebagai berikut:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Seperti yang Anda lihat, ini akan menjadi cara yang bagus untuk menjaga kode Anda KERING dan memungkinkan untuk implementasi kelas dasar dipanggil ketika salah satu jenis hanya dapat mengandalkan Bark default daripada implementasi kasus khusus. Kelas-kelas seperti GoldenRetriever, Boxer, Lab semua bisa mewarisi "default" (kelas bass) Bark tanpa biaya hanya karena mereka menerapkan kelas abstrak Dog.

Tapi saya yakin Anda sudah tahu itu.

Anda di sini karena Anda ingin memahami mengapa Anda mungkin ingin memilih antarmuka daripada kelas abstrak atau sebaliknya. Nah salah satu alasan Anda mungkin ingin memilih antarmuka daripada kelas abstrak adalah ketika Anda tidak memiliki atau ingin mencegah implementasi standar. Ini biasanya karena tipe yang mengimplementasikan antarmuka tidak terkait dalam hubungan "adalah". Sebenarnya, mereka tidak harus berhubungan sama sekali kecuali untuk fakta bahwa setiap jenis "mampu" atau memiliki "kemampuan" untuk melakukan sesuatu atau memiliki sesuatu.

Sekarang apa artinya itu? Misalnya, manusia bukan bebek ... dan bebek bukan manusia. Cukup jelas. Namun, baik bebek dan manusia memiliki "kemampuan" untuk berenang (mengingat bahwa manusia lulus pelajaran berenang di kelas 1 :)). Juga, karena bebek bukan manusia atau sebaliknya, ini bukan hubungan "adalah", tetapi sebaliknya hubungan "mampu" dan kita dapat menggunakan antarmuka untuk menggambarkan bahwa:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

Menggunakan antarmuka seperti kode di atas akan memungkinkan Anda untuk mengirimkan objek ke metode yang “mampu” melakukan sesuatu. Kode tidak peduli bagaimana melakukannya ... Yang ia tahu adalah ia bisa memanggil metode Swim pada objek itu dan objek itu akan tahu perilaku mana yang dijalankan pada saat run-time berdasarkan jenisnya.

Sekali lagi, ini membantu kode Anda tetap KERING sehingga Anda tidak perlu menulis beberapa metode yang memanggil objek untuk membentuk sebelumnya fungsi inti yang sama (ShowHowHumanSwims (manusia), ShowHowDuckSwims (bebek), dll.)

Menggunakan antarmuka di sini memungkinkan metode panggilan tidak perlu khawatir tentang jenis apa atau bagaimana perilaku itu diterapkan. Ia hanya tahu bahwa mengingat antarmuka, setiap objek harus menerapkan metode Swim sehingga aman untuk memanggilnya dalam kode sendiri dan memungkinkan perilaku metode Swim ditangani dalam kelasnya sendiri.

Ringkasan:

Jadi aturan utama saya adalah menggunakan kelas abstrak ketika Anda ingin menerapkan fungsionalitas "default" untuk hierarki kelas atau / dan kelas atau tipe yang Anda gunakan untuk berbagi hubungan "is a" (mis. Poodle "adalah "Jenis anjing).

Di sisi lain gunakan antarmuka ketika Anda tidak memiliki hubungan "adalah" tetapi memiliki tipe yang berbagi "kemampuan" untuk melakukan sesuatu atau memiliki sesuatu (mis. Bebek "bukan" manusia. Namun, bebek dan manusia berbagi "Kemampuan" untuk berenang).

Perbedaan lain yang perlu diperhatikan antara kelas abstrak dan antarmuka adalah bahwa kelas dapat mengimplementasikan satu ke banyak antarmuka tetapi kelas hanya bisa mewarisi dari SATU kelas abstrak (atau kelas apa pun dalam hal ini). Ya, Anda dapat membuat kelas sarang dan memiliki hierarki warisan (yang banyak dan harus dimiliki oleh banyak program) tetapi Anda tidak dapat mewarisi dua kelas dalam satu definisi kelas turunan (aturan ini berlaku untuk C #. Dalam beberapa bahasa lain Anda dapat melakukan ini, biasanya hanya karena kurangnya antarmuka dalam bahasa ini).

Ingat juga saat menggunakan antarmuka untuk mematuhi Prinsip Segregasi Antarmuka (ISP). ISP menyatakan bahwa tidak ada klien yang harus dipaksa untuk bergantung pada metode yang tidak digunakannya. Untuk alasan ini antarmuka harus difokuskan pada tugas-tugas tertentu dan biasanya sangat kecil (mis. IDisposable, IComparable).

Kiat lain adalah jika Anda mengembangkan fungsionalitas kecil dan ringkas, gunakan antarmuka. Jika Anda mendesain unit fungsional besar, gunakan kelas abstrak.

Semoga ini jelas bagi sebagian orang!

Juga jika Anda dapat memikirkan contoh yang lebih baik atau ingin menunjukkan sesuatu, silakan lakukan di komentar di bawah!

Jason Roell
sumber
6

Kasus untuk Kelas Dasar atas Antarmuka dijelaskan dengan baik dalam Pedoman Pengkodean .NET Submain:

Kelas Dasar vs. Antarmuka Jenis antarmuka adalah deskripsi sebagian dari suatu nilai, yang berpotensi didukung oleh banyak jenis objek. Gunakan kelas dasar alih-alih antarmuka bila memungkinkan. Dari perspektif versi, kelas lebih fleksibel daripada antarmuka. Dengan sebuah kelas, Anda bisa mengirimkan Versi 1.0 dan kemudian di Versi 2.0 menambahkan metode baru ke kelas. Selama metode ini tidak abstrak, setiap kelas turunan yang ada terus berfungsi tidak berubah.

Karena antarmuka tidak mendukung warisan implementasi, pola yang berlaku untuk kelas tidak berlaku untuk antarmuka. Menambahkan metode ke antarmuka sama dengan menambahkan metode abstrak ke kelas dasar; kelas apa pun yang mengimplementasikan antarmuka akan rusak karena kelas tidak menerapkan metode baru. Antarmuka sesuai untuk situasi berikut:

  1. Beberapa kelas yang tidak terkait ingin mendukung protokol.
  2. Kelas-kelas ini telah memiliki kelas dasar yang telah ditetapkan (misalnya, beberapa kontrol antarmuka pengguna (UI), dan beberapa layanan XML Web).
  3. Agregasi tidak tepat atau praktis. Dalam semua situasi lain, pewarisan kelas adalah model yang lebih baik.
YeahStu
sumber
Saya merasa jawaban ini harus mendapat perhatian lebih. Ini bertentangan dengan banyak jawaban, di sini. Saya tidak akan mengatakan saya sepenuhnya setuju, tetapi ada poin bagus di sini.
kayleeFrye_onDeck
5

Satu perbedaan penting adalah bahwa Anda hanya dapat mewarisi satu kelas dasar, tetapi Anda dapat mengimplementasikan banyak antarmuka. Jadi Anda hanya ingin menggunakan kelas dasar jika Anda benar-benar yakin bahwa Anda tidak perlu juga mewarisi kelas dasar yang berbeda. Selain itu, jika Anda menemukan antarmuka Anda semakin besar maka Anda harus mulai mencari untuk memecahnya menjadi beberapa bagian logis yang mendefinisikan fungsi independen, karena tidak ada aturan bahwa kelas Anda tidak dapat mengimplementasikan semuanya (atau bahwa Anda dapat mendefinisikan yang berbeda antarmuka yang hanya mewarisi mereka semua untuk mengelompokkan mereka).

Joel Coehoorn
sumber
4

Ketika saya pertama kali mulai belajar tentang pemrograman berorientasi objek, saya membuat kesalahan yang mudah dan mungkin umum menggunakan pewarisan untuk berbagi perilaku umum - bahkan di mana perilaku itu tidak penting bagi sifat objek.

Untuk lebih lanjut membangun contoh yang banyak digunakan dalam pertanyaan khusus ini, ada banyak hal yang dapat dipelihara - pacar, mobil, selimut fuzzy ... - jadi saya mungkin memiliki kelas Petable yang menyediakan perilaku umum ini, dan berbagai kelas mewarisi dari itu.

Namun, menjadi petable bukan bagian dari sifat dari objek-objek ini. Ada jauh konsep yang lebih penting yang sangat penting untuk sifat mereka - pacar adalah orang, mobil adalah kendaraan darat, kucing adalah mamalia ...

Perilaku harus ditetapkan terlebih dahulu ke antarmuka (termasuk antarmuka standar kelas), dan dipromosikan ke kelas dasar hanya jika mereka (a) umum untuk kelompok kelas besar yang merupakan himpunan bagian dari kelas yang lebih besar - dalam arti yang sama seperti "cat" dan "person" adalah himpunan bagian dari "mamalia".

Tangkapannya adalah, setelah Anda memahami desain berorientasi objek cukup lebih baik daripada yang saya lakukan pada awalnya, Anda biasanya akan melakukan ini secara otomatis tanpa memikirkannya. Jadi kebenaran sederhana dari pernyataan "kode ke antarmuka, bukan kelas abstrak" menjadi sangat jelas sehingga Anda sulit percaya ada orang yang mau repot-repot mengatakannya - dan mulai mencoba membaca makna lain di dalamnya.

Hal lain yang saya tambahkan adalah bahwa jika suatu kelas adalah murni abstrak - tanpa anggota yang tidak abstrak, tidak diwariskan atau metode yang terpapar pada anak, orang tua, atau klien - maka mengapa kelas itu? Ini bisa diganti, dalam beberapa kasus dengan antarmuka dan dalam kasus lain oleh Null.

Don Edwards
sumber
Kelas yang murni abstrak dapat memberikan perilaku default untuk metode. Ini berguna ketika kelas konkret Anda semua akan berbagi metode umum yang akan berlebihan untuk diterapkan kembali berulang kali.
Adam Hughes
4

Lebih suka antarmuka daripada kelas abstrak

Dasar pemikiran, poin utama untuk dipertimbangkan [dua sudah disebutkan di sini] adalah:

  • Antarmuka lebih fleksibel, karena suatu kelas dapat mengimplementasikan banyak antarmuka. Karena Java tidak memiliki banyak pewarisan, menggunakan kelas abstrak mencegah pengguna Anda menggunakan hierarki kelas lainnya. Secara umum, lebih suka antarmuka ketika tidak ada implementasi atau keadaan default. Koleksi Java menawarkan contoh-contoh bagus dari ini (Peta, Set, dll.).
  • Kelas abstrak memiliki keunggulan memungkinkan kompatibilitas maju yang lebih baik. Setelah klien menggunakan antarmuka, Anda tidak dapat mengubahnya; jika mereka menggunakan kelas abstrak, Anda masih bisa menambahkan perilaku tanpa merusak kode yang ada. Jika kompatibilitas menjadi perhatian, pertimbangkan untuk menggunakan kelas abstrak.
  • Bahkan jika Anda memiliki implementasi default atau keadaan internal, pertimbangkan untuk menawarkan antarmuka dan implementasi abstrak dari itu . Ini akan membantu klien, tetapi masih memberi mereka kebebasan yang lebih besar jika diinginkan [1].
    Tentu saja, subjek telah dibahas panjang lebar di tempat lain [2,3].

[1] Tentu saja menambahkan lebih banyak kode, tetapi jika singkatnya adalah perhatian utama Anda, Anda mungkin harus menghindari Jawa!

[2] Joshua Bloch, Java Efektif, item 16-18.

[3] http://www.codeproject.com/KB/ar ...

Jaimin Patel
sumber
3

Komentar sebelumnya tentang penggunaan kelas abstrak untuk implementasi umum sudah pasti. Salah satu manfaat yang belum saya lihat sebutkan adalah bahwa penggunaan antarmuka membuatnya lebih mudah untuk mengimplementasikan objek tiruan untuk tujuan pengujian unit. Mendefinisikan IPet dan PetBase seperti yang dijelaskan Jason Cohen memungkinkan Anda untuk mengejek kondisi data yang berbeda dengan mudah, tanpa overhead database fisik (hingga Anda memutuskan sudah waktunya untuk menguji hal yang sebenarnya).

Scott Lawrence
sumber
3

Jangan gunakan kelas dasar kecuali Anda tahu artinya, dan itu berlaku dalam kasus ini. Jika itu berlaku, gunakan, jika tidak, gunakan antarmuka. Tetapi perhatikan jawaban tentang antarmuka kecil.

Warisan Publik terlalu sering digunakan dalam OOD dan mengungkapkan lebih banyak daripada yang disadari oleh sebagian besar pengembang atau yang ingin dijalani. Lihat Prinsip Substitutablity Liskov

Singkatnya, jika A "adalah" B maka A membutuhkan tidak lebih dari B dan memberikan tidak kurang dari B, untuk setiap metode yang dipaparkan.

theschmitzer
sumber
3

Opsi lain yang perlu diingat adalah menggunakan hubungan "has-a", alias "diimplementasikan dalam istilah" atau "komposisi." Kadang-kadang ini adalah cara yang lebih bersih dan lebih fleksibel untuk menyusun berbagai hal daripada menggunakan warisan "is-a".

Mungkin tidak masuk akal secara logis untuk mengatakan bahwa Anjing dan Kucing sama-sama "memiliki" Hewan Peliharaan, tetapi ia menghindari jebakan warisan ganda yang umum:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Ya, contoh ini menunjukkan bahwa ada banyak duplikasi kode dan kurangnya keanggunan yang terlibat dalam melakukan hal-hal seperti ini. Tetapi orang juga harus menghargai bahwa ini membantu menjaga Anjing dan Kucing dipisahkan dari kelas Pet (dalam hal itu Anjing dan Kucing tidak memiliki akses ke anggota pribadi Pet), dan itu memberi ruang bagi Anjing dan Kucing untuk mewarisi dari sesuatu yang lain- -mungkin kelas mamalia.

Komposisi lebih disukai ketika tidak ada akses pribadi diperlukan dan Anda tidak perlu merujuk ke Dog and Cat menggunakan referensi / pointer Pet generik. Antarmuka memberi Anda kemampuan referensi umum dan dapat membantu mengurangi verbositas kode Anda, tetapi mereka juga dapat mengaburkan hal-hal ketika mereka tidak terorganisir dengan baik. Warisan berguna ketika Anda membutuhkan akses anggota pribadi, dan dalam menggunakannya Anda berkomitmen untuk sangat menggabungkan kelas Anjing dan Kucing Anda ke kelas Hewan Peliharaan Anda, yang merupakan biaya yang harus dibayar untuk membayar.

Antara warisan, komposisi, dan antarmuka tidak ada satu cara yang selalu benar, dan ada baiknya untuk mempertimbangkan bagaimana ketiga opsi dapat digunakan secara harmonis. Dari ketiganya, pewarisan biasanya merupakan opsi yang harus paling jarang digunakan.

Parappa
sumber
3

Secara konseptual, sebuah antarmuka digunakan untuk secara formal dan semi-formal mendefinisikan serangkaian metode yang akan disediakan oleh suatu objek. Secara formal berarti serangkaian nama metode dan tanda tangan, dan semi-formal berarti dokumentasi yang dapat dibaca manusia yang terkait dengan metode tersebut.

Antarmuka hanyalah deskripsi dari API (setelah semua, API adalah singkatan dari antarmuka pemrograman aplikasi ), mereka tidak dapat berisi implementasi apa pun, dan itu tidak mungkin untuk menggunakan atau menjalankan antarmuka. Mereka hanya membuat eksplisit kontrak tentang bagaimana Anda harus berinteraksi dengan suatu objek.

Kelas menyediakan implementasi, dan mereka dapat menyatakan bahwa mereka menerapkan nol, satu atau lebih Antarmuka. Jika kelas dimaksudkan untuk diwarisi, konvensi adalah untuk mengawali nama kelas dengan "Basis".

Ada perbedaan antara kelas dasar dan kelas dasar abstrak (ABC). ABC menggabungkan antarmuka dan implementasi bersama. Abstrak di luar pemrograman komputer berarti "ringkasan", yaitu "abstrak == antarmuka". Sebuah kelas dasar abstrak kemudian dapat menggambarkan kedua interface, serta implementasi kosong, sebagian atau lengkap yang dimaksudkan untuk diwariskan.

Pendapat tentang kapan harus menggunakan antarmuka vs kelas dasar abstrak versus hanya kelas akan bervariasi liar berdasarkan pada kedua apa yang Anda berkembang, dan bahasa yang Anda berkembang di. Antarmuka sering dikaitkan hanya dengan bahasa statis diketik seperti Java atau C #, tapi bahasa yang diketik secara dinamis juga dapat memiliki antarmuka dan kelas dasar abstrak . Dalam Python misalnya, perbedaan dibuat jelas antara Kelas, yang menyatakan itu mengimplementasikan sebuah antarmuka , dan objek, yang merupakan contoh dari kelas , dan dikatakan memberikan bahwa antarmuka. Mungkin dalam bahasa yang dinamis bahwa dua objek yang keduanya merupakan instance dari kelas yang sama , dapat menyatakan bahwa mereka menyediakan antarmuka yang sama sekali berbeda . Dalam Python ini hanya mungkin untuk atribut objek, sementara metode dibagi keadaan antara semua objek dari suatu kelas . Namun, di Ruby, objek dapat memiliki metode per-instance, jadi ada kemungkinan bahwa antarmuka antara dua objek dari kelas yang sama dapat bervariasi sebanyak keinginan programmer (namun, Ruby tidak memiliki cara eksplisit untuk menyatakan Antarmuka).

Dalam bahasa dinamis, antarmuka ke suatu objek sering diasumsikan secara implisit, baik dengan mengintrospeksi objek dan menanyakan metode apa yang disediakannya ( lihat sebelum Anda melompat ) atau lebih baik dengan hanya mencoba menggunakan antarmuka yang diinginkan pada suatu objek dan menangkap pengecualian jika objek tersebut tidak menyediakan antarmuka itu ( lebih mudah untuk meminta maaf daripada izin ). Ini dapat menyebabkan "false positive" di mana keduanya antarmuka memiliki nama metode yang sama, tetapi secara semantik berbeda. Namun, manfaatnya adalah kode Anda lebih fleksibel karena Anda tidak perlu menentukan di muka untuk mengantisipasi semua kemungkinan penggunaan kode Anda.

Gandum
sumber
2

Itu tergantung pada kebutuhan Anda. Jika IPet cukup sederhana, saya lebih suka mengimplementasikannya. Jika tidak, jika PetBase mengimplementasikan banyak fungsi yang tidak ingin Anda tiru, maka lakukanlah.

Kelemahan untuk menerapkan kelas dasar adalah persyaratan untuk override(ataunew ) metode yang ada. Ini membuat mereka metode virtual yang berarti Anda harus berhati-hati tentang bagaimana Anda menggunakan instance objek.

Terakhir, warisan tunggal .NET membunuh saya. Contoh naif: Katakanlah Anda membuat kontrol pengguna, jadi Anda mewarisi UserControl. Tapi, sekarang Anda terkunci dari warisan PetBase. Ini memaksa Anda untuk mengatur ulang, seperti membuat anggota PetBasekelas, sebagai gantinya.

spoulson
sumber
2

Saya biasanya tidak menerapkan baik sampai saya membutuhkannya. Saya lebih menyukai antarmuka daripada kelas abstrak karena itu memberi sedikit lebih banyak fleksibilitas. Jika ada perilaku umum di beberapa kelas yang mewarisi saya memindahkannya dan membuat kelas dasar abstrak. Saya tidak melihat perlunya keduanya, karena mereka pada dasarnya server tujuan yang sama, dan memiliki keduanya adalah bau kode yang buruk (imho) bahwa solusinya telah direkayasa berlebihan.

Bill the Lizard
sumber
2

Mengenai C #, dalam beberapa hal antarmuka dan kelas abstrak dapat dipertukarkan. Namun, perbedaannya adalah: i) antarmuka tidak dapat mengimplementasikan kode; ii) karena hal ini, antarmuka tidak dapat memanggil stack ke subclass; dan iii) hanya kelas abstrak yang dapat diwarisi pada suatu kelas, sedangkan beberapa antarmuka dapat diimplementasikan pada suatu kelas.

CarneyCode
sumber
2

Oleh def, antarmuka menyediakan lapisan untuk berkomunikasi dengan kode lain. Semua properti publik dan metode kelas secara default mengimplementasikan antarmuka implisit. Kita juga dapat mendefinisikan antarmuka sebagai peran, kapan pun kelas mana pun perlu memainkan peran itu, ia harus mengimplementasikannya sehingga memberikan berbagai bentuk implementasi tergantung pada kelas yang mengimplementasikannya. Oleh karena itu ketika Anda berbicara tentang antarmuka, Anda berbicara tentang polimorfisme dan ketika Anda berbicara tentang kelas dasar, Anda berbicara tentang warisan. Dua konsep oops !!!

Ganesh Kodiganti
sumber
2

Saya telah menemukan bahwa pola Interface> Abstract> Concrete bekerja pada use-case berikut:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

Kelas abstrak mendefinisikan atribut default bersama dari kelas beton, namun menegakkan antarmuka. Sebagai contoh:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Sekarang, karena semua mamalia memiliki rambut dan puting (AFAIK, saya bukan ahli zoologi), kita dapat memasukkan ini ke kelas dasar abstrak

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

Dan kemudian kelas konkret hanya mendefinisikan bahwa mereka berjalan tegak.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

Desain ini bagus ketika ada banyak kelas beton, dan Anda tidak ingin mempertahankan boilerplate hanya untuk memprogram ke antarmuka. Jika metode baru ditambahkan ke antarmuka, itu akan memecah semua kelas yang dihasilkan, jadi Anda masih mendapatkan keuntungan dari pendekatan antarmuka.

Dalam hal ini, abstraknya bisa juga konkret; Namun, penunjukan abstrak membantu untuk menekankan bahwa pola ini sedang digunakan.

Adam Hughes
sumber
1

Seorang pewaris kelas dasar harus memiliki hubungan "adalah". Antarmuka mewakili hubungan "mengimplementasikan a". Jadi hanya gunakan kelas dasar ketika pewaris Anda akan mempertahankan hubungan.

Aaron Fischer
sumber
1

Gunakan Antarmuka untuk menegakkan kontrak ACROSS keluarga dari kelas yang tidak terkait. Misalnya, Anda mungkin memiliki metode akses umum untuk kelas yang mewakili koleksi, tetapi berisi data yang sangat berbeda yaitu satu kelas mungkin mewakili hasil yang ditetapkan dari kueri, sementara yang lain mungkin mewakili gambar di galeri. Selain itu, Anda dapat mengimplementasikan banyak antarmuka, sehingga memungkinkan Anda untuk memadukan (dan menandakan) kemampuan kelas.

Gunakan Warisan ketika kelas memiliki hubungan yang sama dan oleh karena itu memiliki tanda tangan struktural dan perilaku yang sama, yaitu Mobil, Sepeda Motor, Truk dan SUV adalah semua jenis kendaraan jalan yang mungkin mengandung sejumlah roda, kecepatan tertinggi

sunwukung
sumber