Bir işaretleyici arayüzünün amacı nedir?
Yanıtlar:
Bu biraz teğet, "Mitch Wheat" in cevabına dayanıyor.
Genel olarak, insanların çerçeve tasarım yönergelerine atıfta bulunduğunu her gördüğümde, şunu belirtmek isterim:
Genel olarak çerçeve tasarım yönergelerini çoğu zaman göz ardı etmelisiniz.
Bunun nedeni çerçeve tasarım yönergelerindeki herhangi bir sorun değildir. NET çerçevesinin harika bir sınıf kitaplığı olduğunu düşünüyorum. Bu fantastikliğin çoğu çerçeve tasarım yönergelerinden kaynaklanıyor.
Ancak tasarım yönergeleri çoğu programcı tarafından yazılan çoğu kod için geçerli değildir. Amaçları, kütüphane yazımını daha verimli hale getirmek değil, milyonlarca geliştirici tarafından kullanılan geniş bir çerçevenin oluşturulmasını sağlamaktır.
İçindeki birçok öneri size şu şeyleri yapmanız için rehberlik edebilir:
.Net çerçevesi büyük, gerçekten büyük. O kadar büyük ki, herhangi birinin her yönüyle ilgili ayrıntılı bilgiye sahip olduğunu varsaymak kesinlikle mantıksız olurdu. Aslında, çoğu programcının daha önce hiç kullanmadıkları çerçeve bölümleriyle sık sık karşılaştığını varsaymak çok daha güvenlidir.
Bu durumda, bir API tasarımcısının birincil hedefleri şunlardır:
Çerçeve tasarım yönergeleri, geliştiricileri bu hedefleri gerçekleştiren kod oluşturmaya zorlar.
Bu, kodun kopyalanması anlamına gelse bile kalıtım katmanlarından kaçınmak veya paylaşılan yardımcıları kullanmak yerine tüm istisna atma kodunu "giriş noktalarına" itmek (böylece yığın izlemeleri hata ayıklayıcıda daha mantıklıdır) gibi şeyler yapmak anlamına gelir ve çok diğer benzer şeyler.
Bu yönergelerin işaretçi arabirimleri yerine özniteliklerin kullanılmasını önermesinin birincil nedeni, işaretleyici arabirimlerinin kaldırılmasının sınıf kitaplığının kalıtım yapısını çok daha erişilebilir hale getirmesidir. 30 tür ve 6 kat miras hiyerarşisine sahip bir sınıf diyagramı, 15 tür ve 2 katman hiyerarşi içeren bir sınıf şemasına kıyasla çok yıldırıcıdır.
API'lerinizi kullanan milyonlarca geliştirici gerçekten varsa veya kod tabanınız gerçekten büyükse (örneğin 100K LOC'nin üzerinde), bu yönergeleri takip etmek çok yardımcı olabilir.
5 milyon geliştirici bir API öğrenmek için 60 dakika harcamak yerine 15 dakika harcarsa, sonuç 428 adam yılı net tasarruftur. Bu çok zaman.
Ancak çoğu proje milyonlarca geliştiriciyi veya 100K + LOC'yi içermiyor. Tipik bir projede, örneğin 4 geliştirici ve yaklaşık 50 bin lokasyonda, varsayımlar çok farklıdır. Takımdaki geliştiriciler, kodun nasıl çalıştığını çok daha iyi anlayacaktır. Bu, hızlı bir şekilde yüksek kaliteli kod üretmek için optimize etmenin ve hata miktarını ve değişiklik yapmak için gereken çabayı azaltmanın çok daha mantıklı olduğu anlamına gelir.
.Net çerçevesi ile tutarlı bir kod geliştirmek için 1 hafta harcamak, değiştirilmesi kolay ve daha az hataya sahip kod yazmak için 8 saat harcamak:
4.999.999 diğer geliştiricilerin maliyetleri karşılamaması genellikle buna değmez.
Örneğin, işaretçi arabirimlerinin test edilmesi tek bir "is" ifadesine indirgenir ve öznitelikleri arayan daha az kodla sonuçlanır.
Benim tavsiyem şu:
virtual protected
şablon yöntemi DoSomethingCore
yerine DoSomething
açıkça iletişim çok ek iş ve API (düşünmeden yazma uygulamaları bir şablon yöntemi ... IMNSHO, insanlar olduğunu o değil But.. I'm not a framework developer, I don't care about my API!
) tam olarak bu insanlar olduklarını çoğaltılamaz yazma sürü ( ve ayrıca belgesiz ve genellikle okunamayan) kod, tam tersi değil.
İşaretçi Arabirimleri, bir sınıfın yeteneğini çalışma zamanında belirli bir arabirim uyguluyor olarak işaretlemek için kullanılır.
Arayüz Tasarımı ve .NET Tipi Tasarım Kuralları - Arayüz Tasarımı C # özelliklerini kullanarak lehine işaretleyici arayüzleri kullanımı vazgeçirmek, ancak @Jay Bazuzi işaret ettiği gibi, özellikler için daha işaretleyici arayüzleri kontrol etmek daha kolaydır:o is I
Yani bunun yerine:
public interface IFooAssignable {}
public class FooAssignableAttribute : IFooAssignable
{
...
}
.NET yönergeleri şunu yapmanızı önerir:
public class FooAssignableAttribute : Attribute
{
...
}
[FooAssignable]
public class Foo
{
...
}
Diğer her cevap “kaçınılmalıdır” dediğinden, nedeninin bir açıklamasının olması yararlı olacaktır.
İlk olarak, neden işaretleme arabirimleri kullanılır: Kendisini uygulayan nesneyi kullanan kodun, söz konusu arabirimi uygulayıp uygulamadıklarını kontrol etmesine ve varsa nesneye farklı şekilde davranmasına izin vermek için varlar.
Bu yaklaşımla ilgili sorun, kapsüllemeyi bozmasıdır. Artık nesnenin kendisi, harici olarak nasıl kullanılacağı üzerinde dolaylı kontrole sahiptir. Dahası, kullanılacağı sistem hakkında bilgi sahibidir. Markör arayüzünü uygulayarak, sınıf tanımı, markörün varlığını kontrol eden bir yerde kullanılmasını beklediğini önermektedir. İçinde kullanıldığı çevre hakkında örtük bir bilgiye sahip ve nasıl kullanılması gerektiğini tanımlamaya çalışıyor. Bu, kapsülleme fikrine aykırıdır çünkü sistemin tamamen kendi kapsamı dışında var olan bir kısmının uygulanmasına ilişkin bilgiye sahiptir.
Pratik düzeyde bu, taşınabilirliği ve yeniden kullanılabilirliği azaltır. Sınıf farklı bir uygulamada yeniden kullanılırsa, arayüzün de kopyalanması gerekir ve yeni ortamda herhangi bir anlamı olmayabilir, bu da onu tamamen gereksiz kılar.
Bu nedenle, "işaretçi" sınıfla ilgili meta verilerdir. Bu meta veriler sınıfın kendisi tarafından kullanılmaz ve yalnızca (bazıları!) Harici istemci kodu için anlamlıdır, böylece nesneyi belirli bir şekilde ele alabilir. Yalnızca istemci kodu için anlamı olduğundan, meta veriler API sınıfında değil, istemci kodunda olmalıdır.
Bir "işaretleyici arayüzüne" ve normal bir arayüz arasındaki fark yöntemleri ile bir arayüz nasıl dış dünyayı anlatıyor olmasıdır edebilirsiniz boş bir arayüzü nasıl dış dünyayı anlatıyor ima oysa kullanılmalıdır gerektiğini kullanılabilir.
IConstructableFromString<T>
bir sınıfın T
yalnızca IConstructableFromString<T>
statik bir üyesi varsa uygulayabileceğini
public static T ProduceFromString(String params);
, arayüze eşlik eden bir sınıf bir yöntem sunabilir public static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>
; eğer müşteri kodunun bir metodu varsa T[] MakeManyThings<T>() where T:IConstructableFromString<T>
, müşteri kodunu değiştirmek zorunda kalmadan müşteri kodu ile çalışabilecek yeni tipler tanımlanabilir. Meta veriler istemci kodunda olsaydı, mevcut istemci tarafından kullanılmak üzere yeni türler oluşturmak mümkün olmazdı.
T
onu kullanan sınıf ile arasındaki sözleşme IConstructableFromString<T>
, arayüzde bazı davranışları tanımlayan bir yöntemin olduğu yerdir, bu nedenle bu bir işaret arayüzü değildir.
ProduceFromString
yukarıdaki örnekte statik yöntemi arama ve yürütmenin gerçek süreci şunları içermeyecektir. arabirim, gerekli işlevi uygulamak için hangi sınıfların beklenmesi gerektiğini belirtmek için bir işaretçi olarak kullanılması dışında herhangi bir şekilde.
Bir dil ayrımcılığa tabi birleşim türlerini desteklemediğinde, işaretleme arabirimleri bazen gerekli bir kötülük olabilir .
Türü tam olarak A, B veya C'den biri olması gereken bir bağımsız değişken bekleyen bir yöntem tanımlamak istediğinizi varsayalım. İşlevsel öncelikli birçok dilde ( F # gibi ), böyle bir tür açıkça şu şekilde tanımlanabilir:
type Arg =
| AArg of A
| BArg of B
| CArg of C
Ancak, C # gibi OO öncelikli dillerde bu mümkün değildir. Burada benzer bir şey elde etmenin tek yolu IArg arayüzünü tanımlamak ve onunla A, B ve C'yi "işaretlemektir".
Elbette, "nesne" türünü bağımsız değişken olarak kabul ederek işaretleme arabirimini kullanmaktan kaçınabilirsiniz, ancak bu durumda ifade gücünü ve bir dereceye kadar tür güvenliğini kaybedersiniz.
Ayrımcı birleşim türleri son derece faydalıdır ve işlevsel dillerde en az 30 yıldır mevcuttur. Garip bir şekilde, bugüne kadar tüm ana akım OO dilleri bu özelliği görmezden geldi - aslında işlevsel programlamayla hiçbir ilgisi olmasa da, tip sistemine aittir.
Foo<T>
her tür için ayrı bir statik alan kümesine sahip olacağından, T
genel bir sınıfın a'yı işlemek için temsilciler içeren statik alanlar içermesinin T
ve bu alanları, sınıfın olduğu her türü işleyecek işlevlerle önceden doldurmasının zor olmadığını belirtmek gerekir. ile çalışması gerekiyordu. Tür üzerine genel bir arabirim kısıtlaması kullanmak T
, derleyici zamanında sağlanan türün en azından geçerli olduğunu iddia edip etmediğini kontrol eder, ancak gerçekte olduğundan emin olamazdı.
Bir işaretleyici arayüzü sadece boş olan bir arayüzdür. Bir sınıf, bu arabirimi herhangi bir nedenle kullanılacak meta veri olarak uygular. C # 'da, diğer dillerde bir işaretleyici arabirimi kullandığınız nedenlerle bir sınıfı işaretlemek için daha yaygın olarak öznitelikler kullanırsınız.
Bir işaretleyici arabirimi, bir sınıfın tüm alt sınıflara uygulanacak şekilde etiketlenmesine izin verir. "Saf" bir işaret arayüzü hiçbir şeyi tanımlamaz veya miras almaz; daha kullanışlı bir işaretleme arabirimi türü, başka bir arabirimi "miras alan" ancak yeni üye tanımlamayan bir arabirim olabilir. Örneğin, bir "IReadableFoo" arabirimi varsa, bir "IImmutableFoo" arabirimi de tanımlanabilir, bu arabirim "Foo" gibi davranır, ancak onu kullanan herkese hiçbir şeyin değerini değiştirmeyeceğine söz verir. Bir IImmutableFoo'yu kabul eden bir rutin, onu bir IReadableFoo gibi kullanabilir, ancak rutin yalnızca IImmutableFoo uygulaması olarak bildirilen sınıfları kabul eder.
"Saf" işaret arayüzleri için pek çok kullanım düşünemiyorum. Aklıma gelen tek şey, EqualityComparer (of T) .Default'un, IDoNotUseEqualityComparer'ı uygulayan herhangi bir tür için Object.Equals döndürmesi olurdu, bu tür de IEqualityComparer'ı uygulamış olsa bile. Bu, bir kişinin Liskov İkame İlkesini ihlal etmeden mühürsüz değişmez bir türe sahip olmasına izin verir: eğer tür, eşitlik testiyle ilgili tüm yöntemleri kapatırsa, türetilmiş bir tür, ek alanlar ekleyebilir ve bunların değiştirilebilir olmasını sağlayabilir, ancak bu alanların mutasyonu olmaz Herhangi bir temel yöntem kullanılarak görünür olmalıdır. Mühürsüz bir değişmez sınıfa sahip olmak ve EqualityComparer.Default'un herhangi bir kullanımından kaçınmak veya IEqualityComparer'ı uygulamamak için türetilmiş sınıflara güvenmek korkunç olmayabilir.
Bu iki uzantı yöntemi, Scott'ın öne sürdüğü sorunların çoğunu öznitelikler yerine işaretleyici arayüzlerini çözecektir:
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>()
}
Şimdi sahipsin:
if (o.HasAttribute<FooAssignableAttribute>())
{
//...
}
karşı:
if (o is IFooAssignable)
{
//...
}
Scott'ın iddia ettiği gibi, bir API oluşturmanın ilk modelle ikinciye kıyasla 5 kat daha uzun süreceğini göremiyorum.
İşaretçiler boş arayüzlerdir. Bir işaretçi ya vardır ya da yoktur.
sınıf Foo: IConfidential
Burada Foo'yu gizli olarak işaretliyoruz. Gerçek ek özellik veya öznitelik gerekmez.
İşaretleyici arayüzü, gövdesi / veri üyeleri / uygulaması olmayan tamamen boş bir arayüzdür.
Bir sınıf , gerektiğinde işaretleme arabirimini uygular , sadece " işaretlemek " içindir; JVM'ye, belirli bir sınıfın klonlama amaçlı olduğunu söylemesi anlamına gelir, bu nedenle klonlanmasına izin verin. Bu belirli sınıf, nesnelerini Seri hale getirmektir, bu nedenle lütfen nesnelerinin serileştirilmesine izin verin.
Markör arayüzü, bir OO dilinde gerçekten sadece prosedürel bir programlamadır. Bir arayüz, bir markör arayüzü dışında, uygulayıcılar ile tüketiciler arasındaki bir sözleşmeyi tanımlar, çünkü bir markör arayüzü kendisinden başka bir şey tanımlamaz. Yani, geçidin hemen dışında, işaretleyici arayüzü, bir arayüz olmanın temel amacında başarısız olur.