Apa tujuan dari antarmuka penanda?
Apa tujuan dari antarmuka penanda?
Jawaban:
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:
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:
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:
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:
virtual protected
metode template DoSomethingCore
bukan DoSomething
pekerjaan 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.
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
{
...
}
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.
IConstructableFromString<T>
menentukan bahwa kelas T
hanya dapat diimplementasikan IConstructableFromString<T>
jika memiliki anggota statis ...
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.
T
dan kelas yang menggunakannya adalah IConstructableFromString<T>
tempat Anda memiliki metode di antarmuka yang menjelaskan beberapa perilaku sehingga ini bukan antarmuka penanda.
ProduceFromString
dalam 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.
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.
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 T
akan memeriksa pada waktu kompilator bahwa tipe yang disediakan setidaknya diklaim valid, meskipun itu tidak dapat memastikan bahwa itu sebenarnya.
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.
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,
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.
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.
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.
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.