Rekan kerja saya dan saya memiliki pendapat berbeda tentang hubungan antara kelas dasar dan antarmuka. Saya berkeyakinan bahwa suatu kelas tidak boleh mengimplementasikan antarmuka kecuali kelas itu dapat digunakan ketika implementasi antarmuka diperlukan. Dengan kata lain, saya suka melihat kode seperti ini:
interface IFooWorker { void Work(); }
abstract class BaseWorker {
... base class behaviors ...
public abstract void Work() { }
protected string CleanData(string data) { ... }
}
class DbWorker : BaseWorker, IFooWorker {
public void Work() {
Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
}
}
DbWorker adalah yang mendapat antarmuka IFooWorker, karena merupakan implementasi antarmuka instantiatable. Itu sepenuhnya memenuhi kontrak. Rekan kerja saya lebih suka yang hampir identik:
interface IFooWorker { void Work(); }
abstract class BaseWorker : IFooWorker {
... base class behaviors ...
public abstract void Work() { }
protected string CleanData(string data) { ... }
}
class DbWorker : BaseWorker {
public void Work() {
Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
}
}
Di mana kelas dasar mendapatkan antarmuka, dan berdasarkan ini semua pewaris kelas dasar adalah antarmuka itu juga. Ini mengganggu saya tetapi saya tidak dapat menemukan alasan konkret mengapa, di luar "kelas dasar tidak dapat berdiri sendiri sebagai implementasi dari antarmuka".
Apa pro & kontra dari metodenya vs. tambang, dan mengapa seseorang harus digunakan di atas yang lain?
sumber
Work
adalah masalah warisan berlian (dalam hal itu mereka berdua memenuhi kontrak atasWork
metode ini). Di Jawa Anda harus mengimplementasikan metode antarmuka sehingga masalahWork
metode mana yang harus digunakan oleh program dihindari dengan cara itu. Namun, bahasa seperti C ++ tidak membingungkan Anda.Jawaban:
BaseWorker
memenuhi persyaratan itu. Hanya karena Anda tidak dapat secara langsung membuat instanceBaseWorker
objek tidak berarti Anda tidak dapat memilikiBaseWorker
pointer yang memenuhi kontrak. Memang, itulah inti dari kelas abstrak.Juga, sulit untuk mengetahui dari contoh sederhana yang Anda posting, tetapi bagian dari masalahnya mungkin antarmuka dan kelas abstraknya berlebihan. Kecuali Anda memiliki implementasi kelas lain
IFooWorker
yang tidak berasalBaseWorker
, Anda tidak memerlukan antarmuka sama sekali. Antarmuka hanyalah kelas abstrak dengan beberapa batasan tambahan yang membuat pewarisan berganda lebih mudah.Sekali lagi sulit untuk mengetahui dari contoh sederhana, penggunaan metode yang dilindungi, secara eksplisit merujuk pada basis dari kelas turunan, dan kurangnya tempat yang tidak ambigu untuk mendeklarasikan implementasi antarmuka adalah tanda-tanda peringatan bahwa Anda secara tidak tepat menggunakan pewarisan alih-alih menggunakan komposisi. Tanpa warisan itu, seluruh pertanyaan Anda menjadi diperdebatkan.
sumber
BaseWorker
tidak dapat dipakai secara langsung, tidak berarti yang diberikanBaseWorker myWorker
tidak diterapkanIFooWorker
.Saya harus setuju dengan rekan kerja Anda.
Dalam kedua contoh yang Anda berikan, BaseWorker mendefinisikan metode abstrak Work (), yang berarti bahwa semua subclass mampu memenuhi kontrak IFooWorker. Dalam hal ini, saya pikir BaseWorker harus mengimplementasikan antarmuka, dan implementasi itu akan diwarisi oleh subkelasnya. Ini akan menyelamatkan Anda dari keharusan untuk secara eksplisit menunjukkan bahwa setiap subclass memang merupakan IFooWorker (prinsip KERING).
Jika Anda tidak mendefinisikan Work () sebagai metode BaseWorker, atau jika IFooWorker memiliki metode lain yang tidak diinginkan atau dibutuhkan subclass dari subclass dari BaseWorker, maka (jelas) Anda harus menunjukkan subclass mana yang benar-benar mengimplementasikan IFooWorker.
sumber
IFoo
danBaseFoo
kemudian mengimplikasikan bahwa keduanyaIFoo
bukan antarmuka yang tepat, atau yangBaseFoo
menggunakan warisan di mana komposisi mungkin merupakan pilihan yang lebih baik.MyDaoFoo
atauSqlFoo
atauHibernateFoo
atau apa pun, untuk menunjukkan bahwa itu hanya satu kemungkinan pohon kelas yang diimplementasikanIFoo
? Bagi saya lebih bau jika kelas dasar digabungkan ke perpustakaan atau platform tertentu dan tidak disebutkan namanya dalam nama ...Saya biasanya setuju dengan rekan kerja Anda.
Mari kita ambil model Anda: antarmuka hanya diterapkan oleh kelas anak, meskipun kelas dasar juga memberlakukan pemaparan metode IFooWorker. Pertama, itu berlebihan; apakah kelas anak mengimplementasikan antarmuka atau tidak, mereka diharuskan untuk mengganti metode yang diekspos dari BaseWorker, dan juga setiap implementasi IFooWorker harus mengekspos fungsi apakah mereka mendapat bantuan dari BaseWorker atau tidak.
Ini juga membuat hierarki Anda ambigu; "Semua IFooWorkers adalah BaseWorkers" tidak selalu merupakan pernyataan yang benar, dan tidak juga "Semua BaseWorkers adalah IFooWorkers". Jadi, jika Anda ingin mendefinisikan variabel instan, parameter, atau tipe pengembalian yang bisa berupa implementasi dari IFooWorker atau BaseWorker (memanfaatkan fungsionalitas umum yang terbuka yang merupakan salah satu alasan untuk mewarisi di tempat pertama), tidak satu pun dari ini dijamin mencakup semua; beberapa BaseWorkers tidak akan dapat ditugaskan ke variabel tipe IFooWorker, dan sebaliknya.
Model rekan kerja Anda lebih mudah digunakan dan diganti. "Semua BaseWorkers adalah IFooWorkers" sekarang merupakan pernyataan yang benar; Anda dapat memberikan instance BaseWorker untuk ketergantungan IFooWorker, tanpa masalah. Pernyataan sebaliknya "Semua IFooWorkers adalah BaseWorkers" tidak benar; yang memungkinkan Anda untuk mengganti BaseWorker dengan BetterBaseWorker dan kode konsumsi Anda (yang tergantung pada IFooWorker) tidak perlu memberi tahu perbedaannya.
sumber
Saya perlu menambahkan peringatan untuk jawaban ini. Menggabungkan kelas dasar ke antarmuka menciptakan kekuatan dalam struktur kelas itu. Dalam contoh dasar Anda, tidak ada keraguan bahwa keduanya harus digabungkan, tetapi itu mungkin tidak berlaku dalam semua kasus.
Ambil kelas framework koleksi Java:
Fakta bahwa kontrak Antrian dilaksanakan oleh LinkedList tidak mendorong kekhawatiran ke AbstractList .
Apa perbedaan antara kedua kasus itu? Tujuan BaseWorker selalu (seperti dikomunikasikan dengan nama dan antarmuka) untuk mengimplementasikan operasi di IWorker . Tujuan AbstractList dan Queue berbeda, tetapi keturunan dari yang lama masih dapat mengimplementasikan kontrak yang terakhir.
sumber
Saya akan bertanya, apa yang terjadi ketika Anda berubah
IFooWorker
, seperti menambahkan metode baru?Jika
BaseWorker
mengimplementasikan antarmuka, secara default ia harus mendeklarasikan metode baru, bahkan jika itu abstrak. Di sisi lain, jika tidak mengimplementasikan antarmuka, Anda hanya akan mendapatkan kesalahan kompilasi pada kelas turunan.Untuk alasan itu, saya akan membuat kelas dasar mengimplementasikan antarmuka , karena saya mungkin dapat menerapkan semua fungsi untuk metode baru di kelas dasar tanpa menyentuh kelas turunan.
sumber
Pertama-tama pikirkan apa itu kelas dasar abstrak, dan apa antarmuka itu. Pertimbangkan kapan Anda akan menggunakan satu atau yang lain dan kapan Anda tidak.
Sudah umum bagi orang untuk berpikir bahwa keduanya adalah konsep yang sangat mirip, bahkan itu adalah pertanyaan wawancara yang umum (perbedaan antara keduanya adalah ??)
Jadi kelas dasar abstrak memberi Anda sesuatu yang tidak bisa antarmuka yang merupakan implementasi metode standar. (Ada hal lain, di C # Anda dapat memiliki metode statis pada kelas abstrak, bukan pada antarmuka misalnya).
Sebagai contoh, salah satu penggunaan umum ini adalah dengan IDisposable. Kelas abstrak mengimplementasikan metode Buang IDisposable, yang berarti setiap versi turunan dari kelas dasar akan secara otomatis dibuang. Anda kemudian dapat bermain dengan beberapa opsi. Buat Buang abstrak, memaksa kelas turunan untuk mengimplementasikannya. Berikan implementasi default virtual, jadi mereka tidak melakukannya, atau membuatnya bukan virtual atau abstrak dan minta metode virtual yang disebut hal-hal seperti BeforeDispose, AfterDispose, OnDispose atau Disposing misalnya.
Jadi, setiap saat semua kelas turunan perlu mendukung antarmuka, itu akan berlaku pada kelas dasar. Jika hanya satu atau beberapa yang perlu antarmuka itu akan pergi pada kelas turunan.
Itu semua sebenarnya terlalu disederhanakan. Alternatif lain adalah memiliki kelas turunan yang bahkan tidak mengimplementasikan antarmuka, tetapi menyediakannya melalui pola adaptor. Contoh dari ini saya melihat baru-baru ini di IObjectContextAdapter.
sumber
Saya masih bertahun-tahun lagi untuk sepenuhnya memahami perbedaan antara kelas abstrak dan antarmuka. Setiap kali saya pikir saya memahami konsep dasar, saya mencari stackexchange dan saya mundur dua langkah. Tetapi beberapa pemikiran tentang topik dan pertanyaan OP:
Pertama:
Ada dua penjelasan umum antarmuka:
Antarmuka adalah daftar metode dan properti yang dapat diterapkan oleh setiap kelas, dan dengan mengimplementasikan antarmuka, kelas menjamin metode tersebut (dan tanda tangannya) dan properti tersebut (dan tipenya) akan tersedia ketika "berinteraksi" dengan kelas itu atau sebuah objek dari kelas itu. Antarmuka adalah kontrak.
Antarmuka adalah kelas abstrak yang tidak / tidak bisa melakukan apa pun. Mereka berguna karena Anda dapat menerapkan lebih dari satu, tidak seperti kelas induk yang berarti. Seperti, saya bisa menjadi objek kelas BananaBread, dan mewarisi dari BaseBread, tetapi itu tidak berarti saya tidak bisa juga mengimplementasikan antarmuka IWithNuts dan ITastesYummy. Saya bahkan bisa mengimplementasikan antarmuka IDoesTheDishes, karena saya bukan hanya roti, tahu?
Ada dua penjelasan umum tentang kelas abstrak:
Kelas abstrak, yah, adalah hal yang tidak bisa dilakukan oleh hal itu. Ini seperti, intinya, sama sekali bukan hal yang nyata. Tunggu, ini akan membantu. Kapal adalah kelas abstrak, tetapi Playboy Yacht yang seksi akan menjadi sub kelas dari BaseBoat.
Saya membaca buku tentang kelas abstrak, dan mungkin Anda harus membaca buku itu, karena Anda mungkin tidak mengerti dan akan melakukannya dengan salah jika Anda belum membaca buku itu.
Ngomong-ngomong, buku Citers selalu tampak mengesankan, walaupun aku masih berjalan pergi dengan bingung.
Kedua:
Pada SO, seseorang menanyakan versi sederhana dari pertanyaan ini, klasik, "mengapa menggunakan antarmuka? Apa bedanya? Apa yang saya lewatkan?" Dan satu jawaban menggunakan pilot angkatan udara sebagai contoh sederhana. Itu tidak cukup mendarat, tetapi memicu beberapa komentar yang bagus, salah satunya menyebutkan antarmuka IFlyable memiliki metode seperti takeOff, pilotEject, dll. Dan ini benar-benar diklik bagi saya sebagai contoh dunia nyata mengapa antarmuka tidak hanya berguna, tetapi sangat penting. Antarmuka membuat objek / kelas menjadi intuitif, atau paling tidak memberikan pengertian bahwa itu objek. Antarmuka bukan untuk kepentingan objek atau data, tetapi untuk sesuatu yang perlu berinteraksi dengan objek itu. Buah klasik-> Apple-> Fuji atau Shape-> Triangle-> Contoh-contoh pewarisan sama sisi adalah model yang bagus untuk memahami taksonomi objek yang diberikan berdasarkan keturunannya. Ini memberi tahu konsumen dan prosesor tentang kualitas umum, perilaku, apakah objek itu sekelompok hal, akan menyiram sistem Anda jika Anda meletakkannya di tempat yang salah, atau menjelaskan data sensorik, konektor ke toko data tertentu, atau data keuangan diperlukan untuk membuat daftar gaji.
Objek Plane tertentu mungkin atau mungkin tidak memiliki metode untuk pendaratan darurat, tapi saya akan marah jika saya menganggap daruratnya. karena mereka mengira semuanya sama saja. Sama seperti bagaimana saya akan frustrasi jika setiap produsen sekrup memiliki interpretasi yang benar tentang fasty tighty atau jika TV saya tidak memiliki tombol kenaikan volume di atas tombol penurunan volume.
Kelas abstrak adalah model yang menetapkan apa yang harus dimiliki objek turunan apa pun untuk dikualifikasikan sebagai kelas itu. Jika Anda tidak memiliki semua kualitas bebek, tidak masalah bahwa Anda memiliki antarmuka IQuack diimplementasikan, Anda hanya seekor penguin aneh. Antarmuka adalah hal-hal yang masuk akal bahkan ketika Anda tidak bisa memastikan hal lain. Jeff Goldblum dan Starbuck sama-sama dapat menerbangkan pesawat ruang angkasa alien karena antarmuka yang mirip.
Ketiga:
Saya setuju dengan rekan kerja Anda, karena kadang-kadang Anda perlu menerapkan metode tertentu di awal. Jika Anda membuat ORM Rekaman Aktif, ini membutuhkan metode penyimpanan. Ini tidak sampai ke subkelas yang bisa dipakai. Dan jika antarmuka ICRUD cukup portabel untuk tidak secara eksklusif digabungkan ke satu kelas abstrak, itu dapat diimplementasikan oleh kelas lain untuk membuatnya dapat diandalkan dan intuitif bagi siapa pun yang sudah akrab dengan salah satu kelas turunan dari kelas abstrak itu.
Di sisi lain, ada contoh yang bagus sebelumnya ketika tidak melompat untuk mengikat antarmuka ke kelas abstrak, karena tidak semua tipe daftar akan (atau seharusnya) mengimplementasikan antarmuka antrian. Anda mengatakan skenario ini terjadi separuh waktu, yang berarti Anda dan rekan kerja Anda sama-sama salah separuh waktu, dan dengan demikian hal terbaik untuk dilakukan adalah berdebat, berdebat, mempertimbangkan, dan jika ternyata benar, akui dan terima sambungan tersebut. . Tetapi jangan menjadi pengembang yang mengikuti filosofi bahkan ketika itu bukan yang terbaik untuk pekerjaan yang ada.
sumber
Ada aspek lain mengapa
DbWorker
harus menerapkanIFooWorker
, seperti yang Anda sarankan.Dalam hal gaya rekan kerja Anda, jika karena alasan tertentu terjadi refactoring kemudian dan
DbWorker
dianggap tidak memperpanjangBaseWorker
kelas abstrak ,DbWorker
kehilanganIFooWorker
antarmuka. Ini bisa, tetapi tidak harus, berdampak pada klien yang memakannya, jika mereka mengharapkanIFooWorker
antarmuka.sumber
Untuk memulai, saya ingin mendefinisikan antarmuka dan kelas abstrak secara berbeda:
Dalam kasus Anda, semua pekerja menerapkan
work()
karena itulah yang diwajibkan oleh kontrak bagi mereka. Oleh karena itu kelas dasar AndaBaseworker
harus mengimplementasikan metode itu baik secara eksplisit atau sebagai fungsi virtual. Saya menyarankan Anda untuk meletakkan metode ini di kelas dasar Anda karena fakta sederhana bahwa semua pekerja perluwork()
. Oleh karena itu logis bahwa baseclass Anda (meskipun Anda tidak dapat membuat pointer dari tipe itu) memasukkannya sebagai fungsi.Sekarang,
DbWorker
memang memenuhiIFooWorker
antarmuka, tetapi jika ada cara khusus yang kelas ini lakukanwork()
, maka itu benar-benar perlu membebani definisi yang diwarisiBaseWorker
. Alasan tidak seharusnya mengimplementasikan antarmukaIFooWorker
secara langsung adalah karena ini bukan sesuatu yang istimewaDbWorker
. Jika Anda melakukan itu setiap kali Anda menerapkan kelas yang samaDbWorker
maka Anda akan melanggar KERING .Jika dua kelas menerapkan fungsi yang sama dengan cara yang berbeda, Anda bisa mulai mencari superclass umum terbesar. Sebagian besar waktu Anda akan menemukan satu. Jika tidak, teruslah mencari atau menyerah dan menerima bahwa mereka tidak memiliki kesamaan dalam hal membuat kelas dasar.
sumber
Wow, semua jawaban ini dan tidak ada yang menunjukkan bahwa antarmuka dalam contoh ini tidak memiliki tujuan apa pun. Anda tidak menggunakan warisan dan antarmuka untuk metode yang sama, itu tidak ada gunanya. Anda menggunakan satu atau yang lain. Ada banyak pertanyaan di forum ini dengan jawaban yang menjelaskan manfaat masing-masing dan senarios yang membutuhkan satu atau yang lain.
sumber