Bukankah seluruh titik dari antarmuka yang banyak kelas mematuhi seperangkat aturan dan implementasi?
Bukankah seluruh titik dari antarmuka yang banyak kelas mematuhi seperangkat aturan dan implementasi?
Jawaban:
Sebenarnya, tidak, YAGNI berlaku. Yang mengatakan, waktu yang Anda habiskan untuk membuat antarmuka sangat minim, terutama jika Anda memiliki alat pembuat kode praktis melakukan sebagian besar pekerjaan untuk Anda. Jika Anda tidak yakin apakah Anda akan memerlukan antarmuka atau tidak, saya akan mengatakan lebih baik untuk berbuat salah dalam mendukung definisi antarmuka.
Selain itu, menggunakan antarmuka bahkan untuk satu kelas akan memberi Anda implementasi tiruan lain untuk unit test, yang tidak diproduksi. Jawaban Avner Shahar-Kashtan diperluas pada titik ini.
Saya akan menjawab bahwa apakah Anda memerlukan antarmuka atau tidak tidak tergantung pada berapa banyak kelas yang akan mengimplementasikannya. Antarmuka adalah alat untuk mendefinisikan kontrak antara beberapa subsistem aplikasi Anda; jadi yang terpenting adalah bagaimana aplikasi Anda dibagi menjadi beberapa subsistem. Seharusnya ada antarmuka sebagai front-end untuk subsistem yang dienkapsulasi, tidak peduli berapa banyak kelas yang mengimplementasikannya.
Inilah salah satu aturan praktis yang sangat berguna:
Foo
merujuk langsung ke kelas BarImpl
, Anda sangat berkomitmen untuk berubah Foo
setiap kali Anda berubah BarImpl
. Anda pada dasarnya memperlakukan mereka sebagai satu unit kode yang dibagi menjadi dua kelas.Foo
merujuk ke antarmuka Bar
, Anda malah mengkomit diri sendiri untuk menghindari perubahan Foo
saat Anda berubah BarImpl
.Jika Anda mendefinisikan antarmuka pada titik-titik kunci aplikasi Anda, Anda memikirkan metode yang harus mereka dukung dan mana yang seharusnya tidak didukung, dan Anda mengomentari antarmuka dengan jelas untuk menggambarkan bagaimana suatu implementasi seharusnya berperilaku (dan bagaimana tidak), aplikasi Anda akan jauh lebih mudah untuk dipahami karena antarmuka yang dikomentari ini akan memberikan semacam spesifikasi aplikasi — deskripsi tentang bagaimana ia seharusnya berperilaku. Ini membuatnya lebih mudah untuk membaca kode (daripada bertanya "apa yang seharusnya dilakukan kode ini" Anda bisa bertanya "bagaimana kode ini melakukan apa yang seharusnya dilakukan").
Selain semua ini (atau sebenarnya karena itu), antarmuka mempromosikan kompilasi terpisah. Karena antarmuka sepele untuk dikompilasi dan memiliki lebih sedikit ketergantungan daripada implementasinya, itu berarti bahwa jika Anda menulis kelas Foo
untuk menggunakan antarmuka Bar
, Anda biasanya dapat mengkompilasi ulang BarImpl
tanpa perlu mengkompilasi ulang Foo
. Dalam aplikasi besar ini dapat menghemat banyak waktu.
Foo
tergantung pada antarmuka Bar
maka BarImpl
dapat dimodifikasi tanpa harus mengkompilasi ulang Foo
. 4. Kontrol akses berbutir halus daripada penawaran publik / pribadi (mengekspos kelas yang sama untuk dua klien melalui antarmuka yang berbeda).
If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
Saat mengubah BarImpl, apa saja perubahan yang dapat dihindari di Foo dengan menggunakan interface Bar
? Karena selama tanda tangan & fungsionalitas metode tidak berubah di BarImpl, Foo tidak akan memerlukan perubahan bahkan tanpa antarmuka (seluruh tujuan antarmuka). Saya berbicara tentang skenario di mana hanya satu kelas yaitu BarImpl
mengimplementasikan Bar. Untuk skenario multi-kelas, saya memahami Prinsip Ketergantungan Pembalikan dan bagaimana antarmuka sangat membantu.
Antarmuka ditunjuk untuk mendefinisikan perilaku, yaitu seperangkat prototipe fungsi / metode. Tipe yang mengimplementasikan antarmuka akan mengimplementasikan perilaku itu, jadi ketika Anda berurusan dengan tipe seperti itu Anda tahu (sebagian) perilaku apa yang dimilikinya.
Tidak perlu mendefinisikan antarmuka jika Anda tahu bahwa perilaku yang ditentukan oleh itu akan digunakan hanya sekali. Kiss (tetap simpel, bodoh)
Sementara secara teori Anda tidak harus memiliki antarmuka hanya untuk memiliki-an-antarmuka, jawaban Yannis Rizos mengisyaratkan komplikasi lebih lanjut:
Saat Anda menulis tes unit dan menggunakan kerangka kerja tiruan seperti Moq atau FakeItEasy (untuk menyebutkan dua yang paling baru saya gunakan), Anda secara implisit membuat kelas lain yang mengimplementasikan antarmuka. Mencari kode atau analisis statis mungkin mengklaim bahwa hanya ada satu implementasi, tetapi sebenarnya ada implementasi tiruan internal. Setiap kali Anda mulai menulis ejekan, Anda akan menemukan bahwa mengekstraksi antarmuka masuk akal.
Tapi tunggu, masih ada lagi. Ada lebih banyak skenario di mana ada implementasi antarmuka implisit. Menggunakan tumpukan komunikasi .NET WCF, misalnya, menghasilkan proxy ke layanan jarak jauh, yang, sekali lagi, mengimplementasikan antarmuka.
Dalam lingkungan kode bersih, saya setuju dengan sisa jawaban di sini. Namun, perhatikan semua kerangka kerja, pola atau dependensi yang Anda miliki yang mungkin menggunakan antarmuka.
Tidak, Anda tidak membutuhkannya, dan saya menganggapnya sebagai anti-pola untuk secara otomatis membuat antarmuka untuk setiap referensi kelas.
Ada biaya nyata untuk membuat Foo / FooImpl untuk semuanya. IDE dapat membuat antarmuka / implementasi gratis, tetapi ketika Anda menavigasi kode, Anda memiliki beban kognitif tambahan dari F3 / F12 untuk foo.doSomething()
membawa Anda ke antarmuka tanda tangan dan bukan implementasi sebenarnya yang Anda inginkan. Plus Anda memiliki kekacauan dua file, bukan satu untuk semuanya.
Jadi Anda hanya harus melakukannya ketika Anda benar-benar membutuhkannya untuk sesuatu.
Sekarang menangani counterarguments:
Saya membutuhkan antarmuka untuk kerangka kerja injeksi Ketergantungan
Antarmuka untuk mendukung kerangka kerja adalah warisan. Di Jawa, antarmuka yang digunakan menjadi persyaratan untuk proxy dinamis, pra-CGLIB. Hari ini, Anda biasanya tidak membutuhkannya. Ini dianggap sebagai kemajuan dan keuntungan bagi produktivitas pengembang yang tidak Anda perlukan lagi di EJB3, Spring dll.
Saya perlu mengejek untuk pengujian unit
Jika Anda menulis tiruan Anda sendiri dan memiliki dua implementasi aktual, maka antarmuka sesuai. Kami mungkin tidak akan melakukan diskusi ini di tempat pertama jika basis kode Anda memiliki FooImpl dan TestFoo.
Tetapi jika Anda menggunakan kerangka mengejek seperti Moq, EasyMock, atau Mockito, Anda bisa mengejek kelas dan Anda tidak perlu antarmuka. Ini analog dengan pengaturan foo.method = mockImplementation
dalam bahasa dinamis tempat metode dapat ditetapkan.
Kami membutuhkan antarmuka untuk mengikuti Prinsip Ketergantungan Inversi (DIP)
DIP mengatakan Anda membangun bergantung pada kontrak (antarmuka) dan bukan implementasi. Tapi kelas sudah menjadi kontrak dan abstraksi. Untuk itulah kata kunci publik / pribadi diperuntukkan. Di universitas, contoh kanonik adalah sesuatu seperti kelas Matriks atau Polinomial - konsumen memiliki API publik untuk membuat matriks, menambahkannya, dll. Tetapi tidak diizinkan untuk peduli jika matriks diterapkan dalam bentuk yang jarang atau padat. Tidak ada IMatrix atau MatrixImpl yang diperlukan untuk membuktikan hal itu.
Selain itu, DIP sering diterapkan berlebihan pada setiap tingkat panggilan kelas / metode, tidak hanya pada batas modul utama. Tanda bahwa Anda terlalu menerapkan DIP adalah bahwa antarmuka dan implementasi Anda berubah pada langkah-kunci sehingga Anda harus menyentuh dua file untuk membuat perubahan, bukan satu. Jika DIP diterapkan dengan tepat, itu berarti bahwa antarmuka Anda tidak harus sering berubah. Juga, tanda lain adalah bahwa antarmuka Anda hanya memiliki satu konsumen nyata (aplikasi sendiri). Cerita berbeda jika Anda membangun perpustakaan kelas untuk konsumsi di banyak aplikasi yang berbeda.
Ini adalah akibat wajar dari pendapat Paman Bob Martin tentang mengejek - Anda hanya perlu mengejek batas-batas arsitektur utama. Dalam webapp, akses HTTP dan DB adalah batasan utama. Semua panggilan kelas / metode di antaranya tidak. Hal yang sama berlaku untuk DIP.
Lihat juga:
new Mockup<YourClass>() {}
dan seluruh kelas, termasuk konstruktornya, diejek, apakah itu antarmuka, kelas abstrak atau kelas beton. Anda juga dapat "mengganti" perilaku konstruktor jika Anda ingin melakukannya. Saya kira ada cara yang setara di Mockito atau Powermock.
Sepertinya jawaban di kedua sisi pagar dapat disimpulkan sebagai berikut:
Desain dengan baik, dan letakkan antarmuka di mana antarmuka dibutuhkan.
Seperti yang saya perhatikan dalam jawaban saya untuk jawaban Yanni , saya tidak berpikir Anda bisa memiliki aturan yang keras dan cepat tentang antarmuka. Menurut definisi, aturan tersebut harus fleksibel. Aturan saya tentang antarmuka adalah bahwa antarmuka harus digunakan di mana pun Anda membuat API. Dan API harus dibuat di mana pun Anda melewati batas dari satu domain tanggung jawab ke domain lainnya.
Misalnya (dibuat-buat mengerikan), katakanlah Anda sedang membangun Car
kelas. Di kelas Anda, Anda tentu membutuhkan lapisan UI. Dalam contoh khusus ini, dibutuhkan bentuk IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
, dan BrakePedal
. Karena mobil ini mengandung AutomaticTransmission
, Anda tidak perlu ClutchPedal
. (Dan karena ini adalah mobil yang mengerikan, tidak ada a / C, radio, atau kursi. Faktanya, papan lantai juga hilang - Anda hanya perlu berpegang pada setir itu dan berharap yang terbaik!)
Jadi, kelas mana yang membutuhkan antarmuka? Jawabannya bisa semuanya, atau tidak sama sekali - tergantung desain Anda.
Anda dapat memiliki antarmuka yang terlihat seperti ini:
Interface ICabin
Event IgnitionSwitchTurnedOn()
Event IgnitionSwitchTurnedOff()
Event BrakePedalPositionChanged(int percent)
Event GasPedalPositionChanged(int percent)
Event GearShiftGearChanged(int gearNum)
Event SteeringWheelTurned(float degree)
End Interface
Pada saat itu, perilaku kelas-kelas tersebut menjadi bagian dari ICabin Interface / API. Dalam contoh ini kelas-kelas (jika ada beberapa) mungkin sederhana, dengan beberapa properti, dan satu atau dua fungsi. Dan apa yang secara implisit Anda nyatakan dengan desain Anda adalah bahwa kelas-kelas ini ada hanya untuk mendukung implementasi konkret ICabin apa pun yang Anda miliki, dan mereka tidak dapat eksis sendiri, atau mereka tidak ada artinya di luar konteks ICabin.
Itu alasan yang sama bahwa Anda tidak menguji anggota pribadi - mereka hanya ada untuk mendukung API publik, dan dengan demikian perilaku mereka harus diuji dengan menguji API.
Jadi jika kelas Anda ada hanya untuk mendukung kelas lain, dan secara konseptual Anda melihatnya tidak benar - benar memiliki domain sendiri, maka tidak masalah untuk melewati antarmuka. Tetapi jika kelas Anda cukup penting sehingga Anda menganggapnya cukup dewasa untuk memiliki domain sendiri, silakan maju dan berikan antarmuka.
SUNTING:
Sering (termasuk dalam jawaban ini) Anda akan membaca hal-hal seperti 'domain', 'ketergantungan' (sering digabungkan dengan 'injeksi') yang tidak berarti apa-apa bagi Anda ketika Anda mulai memprogram (mereka yakin tidak berarti bagi saya). Untuk domain, artinya persis seperti apa itu:
Wilayah di mana kekuasaan atau kekuasaan diberikan; milik kedaulatan atau persemakmuran, atau sejenisnya. Juga digunakan secara kiasan. [WordNet sense 2] [1913 Webster]
Dalam contoh saya - mari kita pertimbangkan IgnitionSwitch
. Di mobil meatspace, kunci kontak bertanggung jawab untuk:
Properti-properti tersebut membentuk domain dari IgnitionSwitch
, atau dengan kata lain, apa yang diketahui dan bertanggung jawab atas hal itu.
Tidak IgnitionSwitch
bertanggung jawab atas GasPedal
. Sakelar penyalaan sama sekali tidak mengetahui pedal gas dalam segala hal. Mereka berdua beroperasi sepenuhnya independen satu sama lain (meskipun mobil akan cukup berharga tanpa keduanya!).
Seperti yang saya katakan sebelumnya, itu tergantung pada desain Anda. Anda bisa mendesain IgnitionSwitch
yang memiliki dua nilai: Aktif ( True
) dan Nonaktif ( False
). Atau Anda bisa mendesainnya untuk mengotentikasi kunci yang disediakan untuknya, dan sejumlah tindakan lainnya. Itulah bagian sulit dari menjadi seorang pengembang memutuskan di mana harus menarik garis di pasir - dan jujur sebagian besar waktu itu benar-benar relatif. Garis-garis di pasir itu penting - di situlah API Anda berada, dan dengan demikian di mana antarmuka Anda seharusnya.
Dari MSDN :
Antarmuka lebih cocok untuk situasi di mana aplikasi Anda membutuhkan banyak jenis objek yang mungkin tidak terkait untuk menyediakan fungsionalitas tertentu.
Antarmuka lebih fleksibel daripada kelas dasar karena Anda dapat menentukan implementasi tunggal yang dapat mengimplementasikan banyak antarmuka.
Antarmuka lebih baik dalam situasi di mana Anda tidak perlu mewarisi implementasi dari kelas dasar.
Antarmuka berguna dalam kasus di mana Anda tidak dapat menggunakan warisan kelas. Sebagai contoh, struktur tidak dapat mewarisi dari kelas, tetapi mereka dapat mengimplementasikan antarmuka.
Secara umum dalam kasus kelas tunggal, tidak perlu mengimplementasikan antarmuka, tetapi mempertimbangkan masa depan proyek Anda, mungkin berguna untuk secara formal mendefinisikan perilaku kelas yang diperlukan.
Untuk menjawab pertanyaan: Ada lebih dari itu.
Salah satu aspek penting dari sebuah antarmuka adalah tujuannya.
Antarmuka adalah "tipe abstrak yang tidak berisi data, tetapi memperlihatkan perilaku" - Antarmuka (komputasi) Jadi jika ini adalah perilaku, atau seperangkat perilaku, yang didukung oleh kelas, dari antarmuka kemungkinan pola yang benar. Namun, jika perilaku tersebut intrinsik dengan konsep yang terkandung oleh kelas, maka Anda mungkin tidak ingin antarmuka sama sekali.
Pertanyaan pertama yang diajukan adalah apa sifat dari hal atau proses yang Anda coba wakili. Kemudian ikuti dengan alasan praktis untuk menerapkan sifat itu dengan cara tertentu.
Karena Anda mengajukan pertanyaan ini, saya kira Anda telah melihat minat memiliki antarmuka yang menyembunyikan banyak implementasi. Ini dapat dimanifestasikan oleh prinsip inversi ketergantungan.
Namun, keharusan untuk memiliki antarmuka atau tidak tidak tergantung pada jumlah implementasinya. Peran sebenarnya dari sebuah antarmuka adalah bahwa ia mendefinisikan kontrak yang menyatakan layanan apa yang harus disediakan alih-alih bagaimana seharusnya dilaksanakan.
Setelah kontrak ditetapkan, dua tim atau lebih dapat bekerja secara independen. Katakanlah Anda bekerja pada modul A dan itu tergantung pada modul B, fakta untuk membuat antarmuka pada B memungkinkan untuk melanjutkan pekerjaan Anda tanpa khawatir tentang implementasi B karena semua detail disembunyikan oleh antarmuka. Dengan demikian, pemrograman terdistribusi menjadi mungkin.
Meskipun modul B hanya memiliki satu implementasi antarmuka, antarmuka masih diperlukan.
Sebagai kesimpulan, sebuah antarmuka menyembunyikan detail implementasi dari penggunanya. Pemrograman ke antarmuka membantu untuk menulis lebih banyak dokumen karena kontrak harus ditentukan, untuk menulis lebih banyak perangkat lunak modular, untuk mempromosikan pengujian unit dan untuk mempercepat kecepatan pengembangan.
Semua jawaban di sini sangat bagus. Memang sebagian besar waktu Anda tidak perlu mengimplementasikan antarmuka yang berbeda. Tetapi ada kasus di mana Anda mungkin ingin tetap melakukannya. Berikut adalah beberapa kasus di mana saya melakukannya:
Kelas mengimplementasikan antarmuka lain yang saya tidak ingin
sering terjadi dengan kelas adaptor yang menjembatani kode pihak ketiga.
interface NameChangeListener { // Implemented by a lot of people
void nameChanged(String name);
}
interface NameChangeCount { // Only implemented by my class
int getCount();
}
class NameChangeCounter implements NameChangeListener, NameChangeCount {
...
}
class SomeUserInterface {
private NameChangeCount currentCount; // Will never know that you can change the counter
}
Kelas menggunakan teknologi spesifik yang seharusnya tidak bocor
Sebagian besar saat berinteraksi dengan perpustakaan eksternal. Bahkan jika hanya ada satu implementasi, saya menggunakan antarmuka untuk memastikan bahwa saya tidak memperkenalkan penggabungan yang tidak perlu dengan perpustakaan eksternal.
interface SomeRepository { // Guarantee that the external library details won't leak trough
...
}
class OracleSomeRepository implements SomeRepository {
... // Oracle prefix allow us to quickly know what is going on in this class
}
Komunikasi lintas lapisan
Bahkan jika hanya satu kelas UI yang pernah mengimplementasikan salah satu kelas domain, itu memungkinkan pemisahan yang lebih baik antara lapisan itu dan yang paling penting itu menghindari ketergantungan siklik.
package project.domain;
interface UserRequestSource {
public UserRequest getLastRequest();
}
class UserBehaviorAnalyser {
private UserRequestSource requestSource;
}
package project.ui;
class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
// UI concern, no need for my domain object to know about this method.
public void displayLabelInErrorMode();
// They most certainly need to know about *that* though
public UserRequest getLastRequest();
}
Hanya sebagian dari metode yang harus tersedia untuk sebagian
besar objek. Sebagian besar terjadi ketika saya memiliki beberapa metode konfigurasi pada kelas beton
interface Sender {
void sendMessage(Message message)
}
class PacketSender implements Sender {
void sendMessage(Message message);
void setPacketSize(int sizeInByte);
}
class Throttler { // This class need to have full access to the object
private PacketSender sender;
public useLowNetworkUsageMode() {
sender.setPacketSize(LOW_PACKET_SIZE);
sender.sendMessage(new NotifyLowNetworkUsageMessage());
... // Other details
}
}
class MailOrder { // Not this one though
private Sender sender;
}
Jadi pada akhirnya saya menggunakan antarmuka untuk alasan yang sama seperti saya menggunakan bidang pribadi: objek lain seharusnya tidak memiliki akses ke hal-hal yang seharusnya tidak mereka akses. Jika saya memiliki case seperti itu, saya memperkenalkan antarmuka walaupun hanya satu kelas yang mengimplementasikannya.
Antarmuka sangat penting tetapi cobalah untuk mengontrol jumlah mereka yang Anda miliki.
Setelah membuat antarmuka untuk hampir semua hal, mudah untuk berakhir dengan kode 'spaghetti cincang'. Saya tunduk pada kebijaksanaan Ayende Rahien yang lebih besar yang telah memposting beberapa kata yang sangat bijak tentang masalah ini:
http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-aplikasi
Ini adalah posting pertamanya dari seluruh seri jadi teruslah membaca!
Salah satu alasan Anda mungkin masih ingin memperkenalkan antarmuka dalam hal ini adalah untuk mengikuti Prinsip Ketergantungan Inversi . Yaitu, modul yang menggunakan kelas akan bergantung pada abstraksi itu (yaitu antarmuka) bukan tergantung pada implementasi konkret. Ini memisahkan komponen tingkat tinggi dari komponen tingkat rendah.
Tidak ada alasan nyata untuk melakukan apa pun. Antarmuka adalah untuk membantu Anda dan bukan program keluaran. Jadi meskipun Antarmuka diimplementasikan oleh sejuta kelas, tidak ada aturan yang mengatakan Anda harus membuatnya. Anda membuatnya sehingga ketika Anda, atau siapa pun yang menggunakan kode Anda, ingin mengubah sesuatu itu meresap ke semua implementasi. Membuat antarmuka akan membantu Anda dalam semua kasus di masa mendatang di mana Anda mungkin ingin membuat kelas lain yang mengimplementasikannya.
Tidak selalu ada kebutuhan untuk mendefinisikan antarmuka untuk kelas.
Objek sederhana seperti objek nilai tidak memiliki banyak implementasi. Mereka tidak perlu dipermainkan juga. Implementasi dapat diuji sendiri dan ketika kelas lain diuji yang bergantung padanya, objek nilai aktual dapat digunakan.
Ingatlah bahwa membuat antarmuka memiliki biaya. Perlu diperbarui sepanjang implementasi, perlu file tambahan, dan beberapa IDE akan kesulitan memperbesar dalam implementasi, bukan antarmuka.
Jadi saya akan mendefinisikan antarmuka hanya untuk kelas tingkat yang lebih tinggi, di mana Anda ingin abstraksi dari implementasi.
Perhatikan bahwa dengan kelas Anda mendapatkan antarmuka gratis. Selain implementasi, kelas mendefinisikan antarmuka dari set metode publik. Antarmuka itu diimplementasikan oleh semua kelas turunan. Ini bukan sepenuhnya antarmuka, tetapi dapat digunakan persis dengan cara yang sama. Jadi saya tidak berpikir perlu untuk membuat ulang antarmuka yang sudah ada dengan nama kelas.