Bir işaretleyici arayüzünün amacı nedir?


Yanıtlar:


77

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:

  1. Bir şeyi uygulamanın en basit yolu olmayabilir
  2. Ekstra kod çoğaltmasına neden olabilir
  3. Ekstra çalışma süresi ek yükü olabilir

.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:

  1. Çerçevenin geri kalanıyla işleri tutarlı tutun
  2. API yüzey alanında gereksiz karmaşıklığı ortadan kaldırın

Ç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:

  1. Geç projeler
  2. Daha düşük bonuslar
  3. Artan hata sayıları
  4. Ofiste daha çok zaman, sahilde daha az margarita içmek.

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:

  1. Geniş çapta kullanıma yönelik sınıf kitaplıkları (veya UI pencere öğeleri) geliştiriyorsanız, çerçeve yönergelerini dini olarak izleyin.
  2. Projenizde 100.000'den fazla LOC'ye sahipseniz bunlardan bazılarını benimsemeyi düşünün
  3. Aksi takdirde onları tamamen görmezden gelin.

12
Şahsen ben kütüphane olarak yazdığım her kodu daha sonra kullanmam gerekecek. Tüketimin yaygın olup olmadığını gerçekten umursamıyorum - yönergelere uymak tutarlılığı artırır ve koduma bakmam ve yıllar sonra anlamam gerektiğinde şaşkınlığı azaltır ...
Reed Copsey

16
Kuralların kötü olduğunu söylemiyorum. Kod tabanınızın boyutuna ve sahip olduğunuz kullanıcı sayısına bağlı olarak farklı olmaları gerektiğini söylüyorum. Tasarım yönergelerinin çoğu, ikili karşılaştırılabilirliği sürdürmek gibi şeylere dayanmaktadır; bu, BCL gibi bir şey için olduğu kadar, bir dizi proje tarafından kullanılan "dahili" kütüphaneler için de önemli değildir. Kullanılabilirlikle ilgili olanlar gibi diğer yönergeler neredeyse her zaman önemlidir. Ahlaki, özellikle küçük projelerde kılavuzlar konusunda aşırı dindar olmamaktır.
Scott Wisniewski

6
+1 - OP'nin sorusuna tam olarak cevap vermedi - MI'nın Amacı - Ama yine de çok yardımcı oldu.
bzarah

5
@ScottWisniewski: Ciddi noktaları kaçırdığınızı düşünüyorum. Çerçeve yönergeleri sadece büyük projeler için geçerli değildir, orta ve bazı küçük projeler için geçerlidir. Onları her zaman Merhaba Dünya programına uygulamaya çalıştığınızda aşırı öldürürler. Örneğin, arayüzleri 5 yöntemle sınırlamak, uygulama boyutuna bakılmaksızın her zaman iyi bir temel kuraldır. Kaçırdığınız başka bir şey, bugünkü küçük uygulama yarının büyük uygulaması olabilir. Bu nedenle, büyük uygulamalar için geçerli olan iyi prensipleri göz önünde bulundurarak geliştirmeniz daha iyi olur, böylece ölçek büyütme zamanı geldiğinde çok fazla kod yazmanıza gerek kalmaz.
Phil

2
Tasarım yönergelerine uymanın (çoğu) aniden 1 hafta süren 8 saatlik bir projeyle nasıl sonuçlanacağını tam olarak anlamıyorum. örnek: Adlandırma virtual protectedşablon yöntemi DoSomethingCoreyerine DoSomethingaçı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.
Laoujin

46

İş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 
{    
   ...
} 

28
Ayrıca, jenerikleri işaret arabirimleriyle tam olarak kullanabiliriz, ancak özniteliklerle değil.
Jordão

18
Nitelikleri ve bildirimsel bir bakış açısından nasıl göründüklerini sevsem de, çalışma zamanında birinci sınıf vatandaşlar değiller ve çalışmak için önemli miktarda nispeten düşük düzeyli tesisat gerektiriyorlar.
Jesse C. Slicer

4
@ Jordão - Bu tam olarak benim düşüncemdi. Örnek olarak, eğer veritabanı erişim kodunu soyutlamak istersem (Linq to Sql diyelim), ortak bir arayüze sahip olmak bunu A LOT kolaylaştırır. Aslında, bir niteliğe atama yapamayacağınız ve onları jenerikte kullanamayacağınız için niteliklerle bu tür bir soyutlama yazmanın mümkün olacağını sanmıyorum. Sanırım diğer sınıfların hepsinin türetildiği boş bir temel sınıf kullanabilirsiniz, ancak bu aşağı yukarı boş bir arayüze sahip olmakla aynıdır. Ayrıca, daha sonra paylaşılan işlevselliğe ihtiyacınız olduğunu fark ederseniz, mekanizma zaten mevcuttur.
tandrewnichols

23

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.


1
Herhangi bir arayüzün birincil amacı, o arayüzle ilişkili sözleşmeye uymayı vaat eden ve olmayan sınıfları ayırt etmektir. Bir arayüz, sözleşmeyi yerine getirmek için gerekli olan üyelerin çağrı imzalarını sağlamaktan da sorumlu olsa da, belirli bir arayüzün belirli bir sınıf tarafından uygulanmasının gerekip gerekmediğini belirleyen, üyelerden ziyade sözleşmedir. İçin sözleşme IConstructableFromString<T>bir sınıfın Tyalnızca IConstructableFromString<T>statik bir üyesi varsa uygulayabileceğini
belirtiyorsa

... 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ı.
supercat

Ancak Tonu 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.
Tom B

Sınıfın sahip olması gereken statik yöntem, arabirimin bir parçası değildir. Arayüzlerdeki statik üyeler, arayüzlerin kendileri tarafından gerçekleştirilir; bir arabirimin uygulama sınıfındaki statik bir üyeye başvurmasının bir yolu yoktur.
supercat

Bir yöntemin, Yansıma kullanarak, genel bir türün belirli bir statik yönteme sahip olup olmadığını belirlemesi ve varsa bu yöntemi çalıştırması mümkündür, ancak ProduceFromStringyukarı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.
süper kedi

8

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.


A'nın Foo<T>her tür için ayrı bir statik alan kümesine sahip olacağından, Tgenel bir sınıfın a'yı işlemek için temsilciler içeren statik alanlar içermesinin Tve 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ı.
supercat

6

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.


4

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.


4

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.


1
Hala jenerik yok.
Ian Kemp

1

İş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.


0

İş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.


0

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.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.