Apa tujuan dari antarmuka penanda?

Jawaban:

77

Ini adalah sedikit garis singgung berdasarkan respon dari "Mitch Wheat".

Secara umum, setiap kali saya melihat orang mengutip pedoman desain kerangka kerja, saya selalu ingin menyebutkan bahwa:

Biasanya Anda harus mengabaikan pedoman desain kerangka kerja.

Ini bukan karena masalah apa pun dengan pedoman desain kerangka kerja. Saya pikir .NET framework adalah perpustakaan kelas yang fantastis. Banyak dari kehebatan itu mengalir dari pedoman desain kerangka kerja.

Namun, pedoman desain tidak berlaku untuk sebagian besar kode yang ditulis oleh sebagian besar pemrogram. Tujuannya adalah memungkinkan pembuatan kerangka kerja besar yang digunakan oleh jutaan pengembang, bukan untuk membuat penulisan perpustakaan lebih efisien.

Banyak saran di dalamnya yang dapat memandu Anda untuk melakukan hal-hal yang:

  1. Mungkin bukan cara paling mudah untuk menerapkan sesuatu
  2. Dapat mengakibatkan duplikasi kode tambahan
  3. Mungkin memiliki overhead waktu proses tambahan

Kerangka .net besar, sangat besar. Ini sangat besar sehingga sangat tidak masuk akal untuk berasumsi bahwa ada orang yang memiliki pengetahuan terperinci tentang setiap aspeknya. Faktanya, jauh lebih aman untuk mengasumsikan bahwa sebagian besar programmer sering menemukan bagian kerangka kerja yang belum pernah mereka gunakan sebelumnya.

Dalam kasus tersebut, tujuan utama seorang desainer API adalah untuk:

  1. Jaga agar semuanya konsisten dengan kerangka kerja lainnya
  2. Kurangi kerumitan yang tidak dibutuhkan di area permukaan API

Pedoman desain kerangka kerja mendorong pengembang untuk membuat kode yang mencapai tujuan tersebut.

Itu berarti melakukan hal-hal seperti menghindari lapisan warisan, meskipun itu berarti menggandakan kode, atau mendorong semua pengecualian membuang kode ke "titik masuk" daripada menggunakan pembantu bersama (sehingga jejak tumpukan lebih masuk akal di debugger), dan banyak lagi hal serupa lainnya.

Alasan utama pedoman tersebut menyarankan penggunaan atribut daripada antarmuka marker adalah karena menghapus antarmuka marker membuat struktur warisan pustaka kelas jauh lebih mudah didekati. Diagram kelas dengan 30 jenis dan 6 lapisan hierarki pewarisan sangat menakutkan dibandingkan dengan 15 jenis dan 2 lapisan hierarki.

Jika benar-benar ada jutaan pengembang yang menggunakan API Anda, atau basis kode Anda sangat besar (katakanlah lebih dari 100K LOC), mengikuti pedoman tersebut dapat banyak membantu.

Jika 5 juta pengembang menghabiskan 15 menit mempelajari API daripada menghabiskan 60 menit mempelajarinya, hasilnya adalah penghematan bersih selama 428 tahun kerja. Itu waktu yang banyak.

Namun, sebagian besar proyek tidak melibatkan jutaan pengembang, atau 100K + LOC. Dalam proyek tipikal, dengan katakanlah 4 pengembang dan sekitar lokasi 50K, kumpulan asumsi sangat berbeda. Pengembang di tim akan memiliki pemahaman yang lebih baik tentang cara kerja kode. Artinya, jauh lebih masuk akal untuk mengoptimalkan untuk menghasilkan kode berkualitas tinggi dengan cepat, dan untuk mengurangi jumlah bug serta upaya yang diperlukan untuk membuat perubahan.

Menghabiskan 1 minggu untuk mengembangkan kode yang konsisten dengan framework .net, vs 8 jam untuk menulis kode yang mudah diubah dan memiliki lebih sedikit bug dapat mengakibatkan:

  1. Proyek terlambat
  2. Bonus lebih rendah
  3. Peningkatan jumlah bug
  4. Lebih banyak waktu dihabiskan di kantor, dan lebih sedikit waktu di pantai untuk minum margarita.

Tanpa 4.999.999 pengembang lain untuk menyerap biaya biasanya tidak sepadan.

Misalnya, pengujian untuk antarmuka marker sampai pada satu ekspresi "adalah", dan menghasilkan lebih sedikit kode yang mencari atribut.

Jadi saran saya adalah:

  1. Ikuti pedoman framework secara religius jika Anda mengembangkan perpustakaan kelas (atau widget UI) yang dimaksudkan untuk konsumsi luas.
  2. Pertimbangkan untuk mengadopsi beberapa di antaranya jika Anda memiliki lebih dari 100 ribu LOC dalam proyek Anda
  3. Jika tidak, abaikan mereka sepenuhnya.
Scott Wisniewski
sumber
12
Saya, secara pribadi, melihat kode apa pun yang saya tulis sebagai perpustakaan yang akan saya gunakan nanti. Saya tidak terlalu peduli konsumsi tersebar luas atau tidak - mengikuti pedoman meningkatkan konsistensi, dan mengurangi kejutan ketika saya perlu melihat kode saya dan memahaminya bertahun-tahun kemudian ...
Reed Copsey
16
Saya tidak mengatakan pedoman itu buruk. Saya mengatakan mereka harus berbeda, tergantung pada ukuran basis kode Anda, dan jumlah pengguna yang Anda miliki. Banyak pedoman desain yang didasarkan pada hal-hal seperti menjaga komparasi biner, yang tidak penting untuk perpustakaan "internal" yang digunakan oleh beberapa proyek seperti halnya untuk sesuatu seperti BCL. Panduan lain, seperti yang terkait dengan kegunaan, hampir selalu penting. Moralnya adalah jangan terlalu religius tentang pedoman, terutama pada proyek-proyek kecil.
Scott Wisniewski
6
+1 - Tidak cukup menjawab pertanyaan OP - Tujuan MI - Tapi tetap saja sangat membantu.
bzarah
5
@ ScottWisniewski: Saya pikir Anda kehilangan poin serius. Panduan Kerangka tidak berlaku untuk proyek besar, mereka berlaku untuk proyek menengah dan beberapa proyek kecil. Mereka menjadi over-kill ketika Anda selalu mencoba menerapkannya ke program Hello-World. Misalnya, membatasi antarmuka menjadi 5 metode selalu merupakan aturan praktis yang baik terlepas dari ukuran aplikasinya. Hal lain yang Anda lewatkan, aplikasi kecil hari ini mungkin menjadi aplikasi besar di masa mendatang. Jadi, lebih baik Anda membangunnya dengan prinsip-prinsip baik yang berlaku untuk aplikasi besar dalam pikiran sehingga ketika tiba saatnya untuk meningkatkan, Anda tidak perlu menulis ulang banyak kode.
Phil
2
Saya tidak begitu mengerti bagaimana mengikuti (sebagian besar) pedoman desain akan menghasilkan proyek 8 jam yang tiba-tiba memakan waktu 1 minggu. ex: Penamaan virtual protectedmetode template DoSomethingCorebukan DoSomethingpekerjaan tambahan yang banyak dan Anda berkomunikasi dengan jelas bahwa itu adalah metode template ... IMNSHO, orang-orang yang menulis aplikasi tanpa mempertimbangkan API ( But.. I'm not a framework developer, I don't care about my API!) adalah orang-orang yang menulis banyak duplikat ( dan juga kode tidak berdokumen dan biasanya tidak dapat dibaca), bukan sebaliknya.
Laoujin
44

Marker Interfaces digunakan untuk menandai kapabilitas kelas sebagai implementasi antarmuka tertentu pada saat run-time.

The Desain Antarmuka dan NET Pedoman Desain Jenis - Interface Design mencegah penggunaan antarmuka penanda mendukung penggunaan atribut di C #, tapi seperti yang ditunjukkan @Jay Bazuzi keluar, lebih mudah untuk memeriksa antarmuka penanda daripada atribut:o is I

Jadi, alih-alih ini:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

Panduan .NET menyarankan Anda melakukan ini:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 
Mitch Wheat
sumber
27
Selain itu, kita dapat sepenuhnya menggunakan obat generik dengan antarmuka marker, tetapi tidak dengan atribut.
Jordão
18
Meskipun saya menyukai atribut dan tampilannya dari sudut pandang deklaratif, mereka bukan warga negara kelas satu pada saat runtime dan memerlukan sejumlah besar pipa ledeng yang relatif rendah untuk digunakan.
Jesse C. Slicer
4
@ Jordão - Ini persis seperti yang saya pikirkan. Sebagai contoh, jika saya ingin mengabstraksi kode akses database (katakanlah Linq ke Sql), memiliki antarmuka yang sama membuatnya BANYAK lebih mudah. Nyatanya, saya rasa tidak mungkin menulis abstraksi semacam itu dengan atribut karena Anda tidak dapat mentransmisikan ke atribut dan tidak dapat menggunakannya dalam obat generik. Saya kira Anda bisa menggunakan kelas dasar kosong dari mana semua kelas lain berasal, tapi rasanya kurang lebih sama dengan memiliki antarmuka kosong. Plus, jika Anda kemudian menyadari bahwa Anda membutuhkan fungsionalitas bersama, mekanismenya sudah ada.
tandrewnichols
23

Karena setiap jawaban lain menyatakan "mereka harus dihindari", akan berguna untuk memiliki penjelasan mengapa.

Pertama, mengapa antarmuka marker digunakan: Antarmuka marker ada untuk memungkinkan kode yang menggunakan objek yang mengimplementasikannya untuk memeriksa apakah mereka mengimplementasikan antarmuka tersebut dan memperlakukan objek secara berbeda jika memang demikian.

Masalah dengan pendekatan ini adalah ia merusak enkapsulasi. Objek itu sendiri sekarang memiliki kendali tidak langsung atas bagaimana ia akan digunakan secara eksternal. Selain itu, ia memiliki pengetahuan tentang sistem tempat ia akan digunakan. Dengan menerapkan antarmuka marker, definisi kelas menyarankan ia mengharapkan untuk digunakan di suatu tempat yang memeriksa keberadaan marker. Ia memiliki pengetahuan implisit tentang lingkungan tempat ia digunakan dan sedang mencoba untuk menentukan bagaimana ia harus digunakan. Ini bertentangan dengan gagasan enkapsulasi karena ia memiliki pengetahuan tentang implementasi bagian dari sistem yang ada sepenuhnya di luar ruang lingkupnya sendiri.

Pada tingkat praktis ini mengurangi portabilitas dan penggunaan kembali. Jika kelas digunakan kembali dalam aplikasi yang berbeda, antarmuka perlu disalin juga, dan itu mungkin tidak memiliki arti di lingkungan baru, membuatnya sepenuhnya redundan.

Dengan demikian, "penanda" adalah metadata tentang kelas. Metadata ini tidak digunakan oleh kelas itu sendiri dan hanya bermakna untuk (beberapa!) Kode klien eksternal sehingga dapat memperlakukan objek dengan cara tertentu. Karena hanya memiliki arti untuk kode klien, metadata harus dalam kode klien, bukan API kelas.

Perbedaan antara "antarmuka penanda" dan antarmuka normal adalah bahwa antarmuka dengan metode memberi tahu dunia luar bagaimana ia dapat digunakan sedangkan antarmuka kosong menyiratkan bahwa ia memberi tahu dunia luar bagaimana ia harus digunakan.

Tom B
sumber
1
Tujuan utama dari setiap antarmuka adalah untuk membedakan antara kelas yang berjanji untuk mematuhi kontrak yang terkait dengan antarmuka tersebut, dan yang tidak. Sementara sebuah antarmuka juga bertanggung jawab untuk menyediakan tanda tangan panggilan dari setiap anggota yang diperlukan untuk memenuhi kontrak, itu adalah kontrak, bukan anggota, yang menentukan apakah antarmuka tertentu harus diimplementasikan oleh kelas tertentu. Jika kontrak untuk IConstructableFromString<T>menentukan bahwa kelas Thanya dapat diimplementasikan IConstructableFromString<T>jika memiliki anggota statis ...
supercat
... public static T ProduceFromString(String params);, kelas pendamping ke antarmuka dapat menawarkan metode public static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>; jika kode klien memiliki metode seperti itu T[] MakeManyThings<T>() where T:IConstructableFromString<T>, seseorang dapat menentukan tipe baru yang dapat bekerja dengan kode klien tanpa harus mengubah kode klien untuk menanganinya. Jika metadata berada dalam kode klien, tidak mungkin membuat tipe baru untuk digunakan oleh klien yang ada.
supercat
Tetapi kontrak antara Tdan kelas yang menggunakannya adalah IConstructableFromString<T>tempat Anda memiliki metode di antarmuka yang menjelaskan beberapa perilaku sehingga ini bukan antarmuka penanda.
Tom B
Metode statis yang harus dimiliki kelas bukanlah bagian dari antarmuka. Anggota statis dalam antarmuka diimplementasikan oleh antarmuka itu sendiri; tidak ada cara bagi sebuah antarmuka untuk merujuk ke anggota statis dalam kelas implementasi.
supercat
Ada kemungkinan metode untuk menentukan, menggunakan Refleksi, apakah tipe generik kebetulan memiliki metode statis tertentu, dan mengeksekusi metode itu jika ada, tetapi proses sebenarnya untuk mencari dan menjalankan metode statis ProduceFromStringdalam contoh di atas tidak akan melibatkan antarmuka dengan cara apa pun, kecuali bahwa antarmuka akan digunakan sebagai penanda untuk menunjukkan kelas apa yang diharapkan untuk mengimplementasikan fungsi yang diperlukan.
supercat
8

Antarmuka penanda terkadang menjadi kejahatan yang diperlukan jika bahasa tidak mendukung jenis serikat yang terdiskriminasi .

Misalkan Anda ingin mendefinisikan metode yang mengharapkan argumen yang tipenya harus persis salah satu dari A, B, atau C. Dalam banyak bahasa fungsional-pertama (seperti F # ), tipe seperti itu dapat didefinisikan dengan jelas sebagai:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

Namun, dalam bahasa OO-first seperti C #, hal ini tidak dimungkinkan. Satu-satunya cara untuk mencapai sesuatu yang serupa di sini adalah dengan mendefinisikan antarmuka IArg dan "menandai" A, B dan C dengannya.

Tentu saja, Anda dapat menghindari penggunaan antarmuka marker hanya dengan menerima jenis "objek" sebagai argumen, tetapi kemudian Anda akan kehilangan ekspresi dan beberapa tingkat keamanan jenis.

Jenis serikat yang terdiskriminasi sangat berguna dan telah ada dalam bahasa fungsional setidaknya selama 30 tahun. Anehnya, sampai hari ini, semua bahasa OO arus utama telah mengabaikan fitur ini - meskipun sebenarnya tidak ada hubungannya dengan pemrograman fungsional itu sendiri, tetapi termasuk dalam sistem tipe.

Marc Sigrist
sumber
Perlu dicatat bahwa karena a Foo<T>akan memiliki sekumpulan bidang statis terpisah untuk setiap jenis T, tidak sulit untuk memiliki kelas generik berisi bidang statis yang berisi delegasi untuk memproses T, dan mengisi bidang tersebut dengan fungsi untuk menangani setiap jenis kelas tersebut. seharusnya bekerja dengan. Menggunakan batasan antarmuka generik pada tipe Takan memeriksa pada waktu kompilator bahwa tipe yang disediakan setidaknya diklaim valid, meskipun itu tidak dapat memastikan bahwa itu sebenarnya.
supercat
6

Antarmuka penanda hanyalah antarmuka yang kosong. Sebuah kelas akan mengimplementasikan antarmuka ini sebagai metadata yang akan digunakan untuk beberapa alasan. Dalam C # Anda akan lebih sering menggunakan atribut untuk menandai kelas untuk alasan yang sama Anda menggunakan antarmuka penanda dalam bahasa lain.

Richard Anthony Hein
sumber
4

Antarmuka marker memungkinkan sebuah kelas untuk diberi tag dengan cara yang akan diterapkan ke semua kelas turunan. Antarmuka marker "murni" tidak akan mendefinisikan atau mewarisi apa pun; jenis antarmuka penanda yang lebih berguna mungkin adalah salah satu yang "mewarisi" antarmuka lain tetapi tidak mendefinisikan anggota baru. Misalnya, jika ada antarmuka "IReadableFoo", orang juga dapat mendefinisikan antarmuka "IImmutableFoo", yang akan berperilaku seperti "Foo" tetapi akan menjanjikan siapa pun yang menggunakannya bahwa tidak ada yang akan mengubah nilainya. Rutinitas yang menerima IImmutableFoo akan dapat menggunakannya seperti halnya IReadableFoo, tetapi rutin hanya akan menerima kelas yang dideklarasikan sebagai implementasi IImmutableFoo.

Saya tidak bisa memikirkan banyak kegunaan untuk antarmuka penanda "murni". Satu-satunya yang dapat saya pikirkan adalah jika EqualityComparer (dari T) .Default akan mengembalikan Object.Equals untuk semua jenis yang menerapkan IDoNotUseEqualityComparer, bahkan jika jenis tersebut juga menerapkan IEqualityComparer. Ini akan memungkinkan seseorang untuk memiliki tipe tak tersegel yang tidak dapat diubah tanpa melanggar Prinsip Substitusi Liskov: jika tipe menyegel semua metode yang terkait dengan pengujian kesetaraan, tipe turunan dapat menambahkan bidang tambahan dan membuatnya dapat berubah, tetapi mutasi bidang tersebut tidak akan ' t terlihat menggunakan metode tipe dasar apa pun. Mungkin tidak mengerikan untuk memiliki kelas yang tidak dapat diubah dan menghindari penggunaan EqualityComparer.Default atau mempercayai kelas turunan untuk tidak mengimplementasikan IEqualityComparer,

supercat
sumber
4

Kedua metode ekstensi ini akan menyelesaikan sebagian besar masalah yang Scott nyatakan lebih menyukai antarmuka penanda daripada atribut:

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

Sekarang kamu punya:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

melawan:

if (o is IFooAssignable)
{
    //...
}

Saya gagal untuk melihat bagaimana membangun API akan memakan waktu 5 kali lebih lama dengan pola pertama dibandingkan dengan yang kedua, seperti yang diklaim Scott.

Tuan Anderson
sumber
1
Masih belum ada obat generik.
Ian Kemp
1

Penanda adalah antarmuka kosong. Penanda ada atau tidak.

kelas Foo: Rahasia

Di sini kami menandai Foo sebagai rahasia. Tidak ada properti atau atribut tambahan yang sebenarnya diperlukan.

Rick O'Shea
sumber
0

Antarmuka penanda adalah antarmuka kosong total yang tidak memiliki badan / data-anggota / implementasi.
Sebuah kelas mengimplementasikan antarmuka marker jika diperlukan, itu hanya untuk " menandai "; artinya ia memberi tahu JVM bahwa kelas tertentu adalah untuk tujuan kloning, jadi izinkan untuk menggandakan. Kelas khusus ini adalah untuk menyusun objeknya jadi mohon biarkan objeknya diserialkan.

Arun Raaj
sumber
0

Antarmuka penanda sebenarnya hanyalah pemrograman prosedural dalam bahasa OO. Antarmuka mendefinisikan kontrak antara pelaksana dan konsumen, kecuali antarmuka marker, karena antarmuka marker hanya mendefinisikan dirinya sendiri. Jadi, langsung dari gerbang, antarmuka penanda gagal pada tujuan dasar menjadi antarmuka.

Ali Bayat
sumber