Mendeklarasikan antarmuka dalam file yang sama dengan kelas dasar, apakah ini praktik yang baik?

29

Agar dapat dipertukarkan dan diuji, biasanya layanan dengan logika perlu memiliki antarmuka, mis

public class FooService: IFooService 
{ ... }

Dari segi desain, saya setuju dengan ini, tetapi salah satu hal yang mengganggu saya dengan pendekatan ini adalah bahwa untuk satu layanan Anda perlu mendeklarasikan dua hal (kelas dan antarmuka), dan di tim kami, biasanya dua file (satu untuk kelas dan satu untuk antarmuka). Ketidaknyamanan lain adalah kesulitan dalam navigasi karena menggunakan "Pergi ke definisi" di IDE (VS2010) akan mengarah ke antarmuka (karena kelas lain merujuk ke antarmuka), bukan kelas yang sebenarnya.

Saya berpikir bahwa menulis IFooService dalam file yang sama dengan FooService akan mengurangi keanehan di atas. Lagipula, IFooService dan FooService sangat terkait. Apakah ini praktik yang baik? Apakah ada alasan bagus bahwa IFooService harus terletak di file-nya sendiri?

Louis Rhys
sumber
3
Jika Anda hanya memiliki satu implementasi antarmuka tertentu, tidak ada kebutuhan logis nyata untuk antarmuka. Mengapa tidak menggunakan kelas secara langsung?
Joris Timmermans
11
@ MadKeithV untuk kopling longgar dan testability?
Louis Rhys
10
@MadKeithV: Anda benar. Tetapi ketika Anda menulis unit test untuk kode yang bergantung pada IFooService, Anda biasanya akan memberikan MockFooService, yang merupakan implementasi kedua dari antarmuka itu.
Doc Brown
5
@DocBrown - jika Anda memiliki beberapa implementasi maka jelas komentarnya hilang, tetapi begitu juga utilitas untuk dapat langsung menuju ke "implementasi tunggal" (karena setidaknya ada dua). Kemudian pertanyaannya menjadi: informasi apa yang ada dalam implementasi konkret yang seharusnya tidak ada dalam deskripsi antarmuka / dokumentasi pada titik di mana Anda menggunakan antarmuka?
Joris Timmermans
1
Iklan-sendiri terang-terangan: stackoverflow.com/questions/5840219/…
Marjan Venema

Jawaban:

24

Itu tidak harus di file sendiri, tetapi tim Anda harus memutuskan standar dan tetap menggunakannya.

Juga, Anda benar bahwa "Pergi ke definisi" akan membawa Anda ke antarmuka, tetapi jika Anda menginstal Resharper , hanya satu klik untuk membuka daftar kelas / antarmuka turunan dari antarmuka itu, jadi itu bukan masalah besar. Itu sebabnya saya menyimpan antarmuka dalam file terpisah.

 

Scott Whitlock
sumber
5
Saya setuju dengan jawaban ini, setelah semua IDE harus menyesuaikan dengan desain kami dan bukan sebaliknya, kan?
marktani
1
Juga, tanpa Resharper, F12 dan Shift + F12 cukup banyak melakukan hal yang sama. Bahkan tidak dengan sekali klik saja :) (untuk bersikap adil, itu hanya akan membawa Anda untuk langsung menggunakan antarmuka, tidak yakin apa yang dilakukan versi Resharper)
Daniel B
@DanielB: Di Resharper, Alt-End menuju implementasi (atau menyajikan daftar implementasi yang mungkin Anda tuju).
StriplingWarrior
@ TriplingWarrior maka sepertinya itulah yang dilakukan vanilla VS juga. F12 = pergi ke definisi, Shift + F12 = pergi ke implementasi (Saya cukup yakin ini bekerja tanpa Resharper, meskipun saya sudah menginstalnya, jadi sulit untuk mengkonfirmasi). Ini salah satu cara pintas navigasi yang paling berguna, saya hanya menyebarkan berita.
Daniel B
@DanielB: Default untuk Shift-F12 adalah "find usages." jetbrains.com/resharper/webhelp/…
StriplingWarrior
15

Saya pikir Anda harus menyimpannya file terpisah. Seperti yang Anda katakan, idenya adalah untuk tetap dapat diuji dan dipertukarkan. Dengan menempatkan antarmuka dalam file yang sama dengan implementasi Anda, Anda mengasosiasikan antarmuka dengan implementasi spesifik. Jika Anda memutuskan untuk membuat objek tiruan atau implementasi lain, antarmuka tidak akan secara logis terpisah dari FooService.

Brad S
sumber
10

Menurut SOLID, Anda tidak hanya harus membuat antarmuka, dan tidak hanya harus dalam file yang berbeda, tetapi juga harus dalam kumpulan yang berbeda.

Mengapa? Karena setiap perubahan pada file sumber yang dikompilasi menjadi sebuah perakitan membutuhkan kompilasi ulang dari perakitan tersebut, dan setiap perubahan pada suatu perakitan memerlukan kompilasi ulang dari setiap perakitan yang bergantung. Jadi, jika tujuan Anda, berdasarkan SOLID, adalah untuk dapat mengganti implementasi A dengan implementasi B, sedangkan kelas C bergantung pada antarmuka saya tidak harus mengetahui perbedaannya, Anda harus memastikan perakitan dengan I di dalamnya tidak berubah, sehingga melindungi penggunaan.

"Tapi itu hanya kompilasi ulang," aku mendengarmu protes. Mungkin memang begitu, tetapi di aplikasi ponsel cerdas Anda, yang lebih mudah pada bandwidth data pengguna Anda; mengunduh satu biner yang berubah, atau mengunduh biner itu dan lima lainnya dengan kode yang bergantung padanya? Tidak setiap program ditulis untuk dikonsumsi oleh komputer desktop pada LAN. Bahkan dalam kasus itu, di mana bandwidth dan memori murah, rilis patch yang lebih kecil dapat memiliki nilai karena mereka sepele untuk mendorong keluar ke seluruh LAN melalui Direktori Aktif atau lapisan manajemen domain serupa; pengguna Anda akan menunggu hanya beberapa detik untuk diterapkan pada waktu berikutnya mereka masuk, bukan beberapa menit untuk semuanya diinstal ulang. Belum lagi, semakin sedikit majelis yang harus dikompilasi ulang saat membangun proyek, semakin cepat akan dibangun,

Sekarang, penafian: Ini tidak selalu mungkin atau layak dilakukan. Cara termudah untuk melakukan ini adalah membuat proyek "antarmuka" terpusat. Ini memiliki kelemahan sendiri; kode menjadi kurang dapat digunakan kembali karena proyek antarmuka DAN proyek implementasi harus direferensikan di aplikasi lain menggunakan kembali lapisan ketekunan atau komponen kunci lainnya dari aplikasi Anda. Anda dapat mengatasi masalah itu dengan memisahkan antarmuka menjadi rakitan yang lebih erat, tetapi kemudian Anda memiliki lebih banyak proyek di aplikasi Anda yang membuat bangunan penuh sangat menyakitkan. Kuncinya adalah keseimbangan, dan mempertahankan desain yang digabungkan secara longgar; Anda biasanya dapat memindahkan file seperlunya, jadi ketika Anda melihat bahwa suatu kelas akan membutuhkan banyak perubahan, atau implementasi antarmuka yang baru diperlukan secara berkala (mungkin untuk berinteraksi dengan versi perangkat lunak lain yang baru saja didukung,

KeithS
sumber
8

Mengapa memiliki file terpisah tidak nyaman? Bagi saya, ini jauh lebih rapi dan bersih. Adalah umum untuk membuat subfolder bernama "Antarmuka" dan menempelkan file IFooServer.cs Anda di sana, jika Anda ingin melihat lebih sedikit file di Solution Explorer Anda.

Alasan antarmuka didefinisikan dalam file sendiri adalah untuk alasan yang sama bahwa kelas biasanya didefinisikan dalam file mereka sendiri: manajemen proyek lebih sederhana ketika struktur logis dan struktur file Anda identik, sehingga Anda selalu tahu file mana kelas yang ditentukan didefinisikan in. Ini dapat membuat hidup Anda lebih mudah ketika debugging (pengecualian stack stack biasanya memberi Anda nomor file dan baris) atau ketika menggabungkan kode sumber dalam repositori kontrol sumber.

Avner Shahar-Kashtan
sumber
6

Sering kali merupakan praktik yang baik untuk membiarkan file kode Anda hanya berisi satu kelas, atau satu antarmuka tunggal. Tetapi praktik pengkodean ini merupakan sarana untuk mencapai tujuan - untuk menyusun struktur kode Anda dengan lebih baik sehingga memudahkan untuk dikerjakan. Jika Anda, dan tim Anda, merasa lebih mudah untuk bekerja dengan jika kelas disimpan bersama dengan antarmuka mereka, dengan segala cara melakukannya.

Secara pribadi saya lebih suka memiliki antarmuka dan kelas dalam file yang sama ketika hanya ada satu kelas yang mengimplementasikan antarmuka, seperti dalam kasus Anda.

Mengenai masalah yang Anda miliki dengan navigasi, saya bisa sangat merekomendasikan ReSharper . Ini berisi beberapa cara pintas yang sangat berguna untuk melompat langsung ke metode yang menerapkan metode antarmuka tertentu.

Pete
sumber
3
+1 untuk menunjukkan bahwa praktik tim harus mendikte struktur file dan karena bertentangan dengan pendekatan norma.
3

Ada banyak waktu di mana Anda ingin antarmuka Anda tidak hanya di file terpisah ke kelas, tetapi bahkan di majelis terpisah sama sekali.

Misalnya, antarmuka Kontrak Layanan WCF dapat dibagikan oleh klien dan layanan jika Anda memiliki kendali atas kedua ujung kabel. Dengan memindahkan antarmuka ke rakitannya sendiri, ia akan memiliki lebih sedikit ketergantungan rakitan sendiri. Ini membuatnya lebih mudah bagi klien untuk mengkonsumsi, melonggarkan kopling dengan implementasinya.

StuartLC
sumber
2

Jarang, jika pernah, masuk akal untuk memiliki implementasi tunggal dari antarmuka 1 . Jika Anda meletakkan antarmuka publik dan kelas publik yang mengimplementasikan antarmuka itu ke file yang sama, kemungkinan besar Anda tidak perlu antarmuka.

Ketika kelas yang Anda tempatkan bersama dengan antarmuka adalah abstrak , dan Anda tahu bahwa semua implementasi antarmuka Anda harus mewarisi kelas abstrak itu, menempatkan keduanya di file yang sama masuk akal. Anda harus tetap meneliti keputusan Anda untuk menggunakan antarmuka: tanyakan pada diri sendiri apakah kelas abstrak dengan sendirinya akan baik-baik saja, dan lepaskan antarmuka jika jawabannya setuju.

Namun, secara umum, Anda harus tetap berpegang pada strategi "satu kelas publik / antarmuka sesuai dengan satu file": strategi ini mudah diikuti, dan itu membuat pohon sumber Anda lebih mudah dinavigasi.


1 Satu pengecualian penting adalah ketika Anda perlu memiliki antarmuka untuk tujuan pengujian, karena pilihan kerangka kerja mengejek Anda menempatkan persyaratan tambahan ini pada kode Anda.

dasblinkenlight
sumber
3
Ini sebenarnya pola yang cukup normal untuk memiliki satu antarmuka untuk satu kelas di. NET, karena memungkinkan unit test untuk menggantikan dependensi dengan mengejek, bertopik, mata-mata, atau tes ganda lainnya.
Pete
2
@Pete Saya mengedit jawaban saya untuk memasukkan ini sebagai catatan. Saya tidak akan menyebut pola ini "normal", karena hal ini memungkinkan masalah testabilitas Anda "bocor" ke basis kode utama Anda. Kerangka kerja mengejek modern membantu Anda mengatasi masalah ini sebagian besar, tetapi memiliki antarmuka di sana pasti menyederhanakan banyak hal.
dasblinkenlight
2
@KeithS Sebuah kelas tanpa antarmuka harus dalam desain Anda hanya ketika Anda mati-yakin bahwa itu tidak masuk akal untuk subkelasnya, dan Anda membuat kelas itu sealed. Jika Anda mencurigai bahwa suatu saat nanti Anda mungkin menambahkan subkelas kedua, Anda langsung masuk ke antarmuka. PS Asisten refactoring berkualitas bagus dapat menjadi milik Anda dengan harga sederhana dari satu lisensi ReSharper :)
dasblinkenlight
1
@KeithS Dan ya, semua kelas Anda yang tidak dirancang khusus untuk subkelas harus disegel. Bahkan, kelas harapan disegel secara default, memaksa Anda untuk menetapkan kelas untuk warisan secara eksplisit, dengan cara yang sama dengan bahasa yang saat ini memaksa Anda untuk menunjuk fungsi untuk menimpa dengan menandai mereka virtual.
dasblinkenlight
2
@KeithS: Apa yang terjadi ketika satu implementasi Anda menjadi dua? Sama seperti ketika implementasi Anda berubah; Anda mengubah kode untuk mencerminkan perubahan itu. YAGNI berlaku di sini. Anda tidak akan pernah tahu apa yang akan berubah di masa depan, jadi lakukan hal sesederhana mungkin. (Sayangnya menguji kekacauan dengan pepatah ini)
Groky
2

Antarmuka milik klien mereka bukan untuk implementasi mereka, sebagaimana didefinisikan dalam Prinsip Agile, Praktik dan Pola oleh Robert C. Martin. Jadi, menggabungkan antarmuka dan implementasi di tempat yang sama bertentangan dengan prinsipnya.

Kode klien tergantung pada antarmuka. Ini memungkinkan kemungkinan kompilasi dan penyebaran kode klien dan antarmuka tanpa implementasi. Daripada Anda dapat memiliki implementasi yang berbeda bekerja seperti plugin ke kode klien.

PEMBARUAN: Ini bukan prinsip yang hanya gesit di sini. Geng Empat dalam Pola Desain, kembali pada '94, sudah berbicara tentang klien yang mengikuti antarmuka dan pemrograman ke antarmuka. Pandangan mereka serupa.

Patkos Csaba
sumber
1
Penggunaan Antarmuka melampaui domain yang dimiliki Agile. Agile adalah metodologi pengembangan yang menggunakan konstruksi bahasa dengan cara tertentu. Antarmuka adalah konstruksi bahasa.
1
Ya, tetapi antarmuka masih milik klien dan bukan implementasinya, terlepas dari kenyataan bahwa kita berbicara tentang lincah atau tidak.
Patkos Csaba
Saya dengan Anda dalam hal ini.
Marjan Venema
0

Saya pribadi tidak suka "Saya" di depan sebuah antarmuka. Sebagai pengguna tipe seperti itu saya tidak ingin melihat bahwa ini adalah antarmuka atau bukan. Jenisnya adalah hal yang menarik. Dalam hal dependensi, FooService Anda adalah SATU kemungkinan implementasi IFooService. Antarmuka harus dekat dengan tempat digunakannya. Di sisi lain implementasi harus di tempat di mana ia dapat dengan mudah diubah tanpa mempengaruhi klien. Jadi, saya lebih suka dua file - biasanya.

ollins
sumber
2
Sebagai pengguna, saya ingin tahu apakah suatu jenis adalah antarmuka, karena misalnya itu berarti saya tidak dapat menggunakannya new, tetapi di sisi lain, saya dapat menggunakannya bersama atau berlawanan. Dan Iini adalah konvensi penamaan yang mapan di .Net, jadi itu adalah alasan yang bagus untuk mematuhinya, jika hanya untuk konsistensi dengan jenis lainnya.
svick
Dimulai dengan Iantarmuka hanya pengantar argumen saya tentang pemisahan dalam dua file. Kode lebih mudah dibaca dan itu membuat inversi dari dependensi lebih jelas.
ollins
4
Suka atau tidak, menggunakan 'I' di depan nama antarmuka adalah standar de-facto di .NET. Tidak mengikuti standar dalam proyek .NET menurut saya akan menjadi pelanggaran 'prinsip paling tidak mengejutkan'.
Pete
Poin utama saya adalah: pisahkan dalam dua file. Satu untuk abstraksi dan satu untuk implementasi. Jika penggunaan Inormal di lingkungan Anda: Gunakan! (Tapi saya tidak suka :))
ollins
Dan dalam kasus P.SE, awalan I di depan antarmuka membuatnya lebih jelas bahwa poster tersebut berbicara tentang antarmuka, bukan warisan dari kelas lain.
0

Pengkodean ke antarmuka bisa buruk, sebenarnya diperlakukan sebagai anti-pola untuk konteks tertentu, dan bukan hukum. Biasanya contoh pengkodean untuk antarmuka anti-pola yang baik adalah ketika Anda memiliki antarmuka yang hanya memiliki satu implementasi dalam aplikasi Anda. Lain akan jika file antarmuka menjadi begitu merepotkan bahwa Anda harus menyembunyikan deklarasi di file kelas yang diimplementasikan.

Shivan Dragon
sumber
memiliki hanya satu implementasi dalam aplikasi antarmuka tidak selalu merupakan hal yang buruk; seseorang bisa melindungi aplikasi terhadap perubahan teknologi. Misalnya, antarmuka untuk teknologi data-persistence. Anda hanya akan memiliki satu implementasi untuk teknologi data-persistensi saat ini sambil melindungi aplikasi jika itu berubah. Jadi hanya akan ada satu imp pada saat itu.
TSmith
0

Menempatkan kelas dan antarmuka ke file yang sama tidak membatasi penggunaan keduanya. Antarmuka masih dapat digunakan untuk mengolok-olok dll dengan cara yang sama, bahkan jika itu dalam file yang sama dengan kelas yang mengimplementasikannya. Pertanyaan itu dapat dianggap murni sebagai salah satu kenyamanan organisasi, dalam hal ini saya akan mengatakan melakukan apa pun yang membuat hidup Anda lebih mudah!

Tentu saja, orang dapat berargumentasi bahwa memiliki keduanya dalam file yang sama dapat menyebabkan mereka yang tidak waspada menganggap mereka lebih diprogram daripada mereka yang sebenarnya, yang merupakan risiko.

Secara pribadi, jika saya menemukan basis kode yang menggunakan satu konvensi lebih dari yang lain, saya tidak akan terlalu peduli.

Serigala
sumber
Ini bukan hanya "kenyamanan organisasi" ... itu juga masalah manajemen risiko, penerapan mgmt, penelusuran kode, kontrol perubahan, kebisingan kontrol sumber, keamanan mgmt. Ada banyak sudut untuk pertanyaan ini.
TSmith