Genel olarak kısıtlanmış bir türe karşı bir arayüz kullanmanın nedeni nedir?


15

Genel tip parametrelerini (sınıf şablonları ve parametrik polimorfizm olarak da bilinir) destekleyen nesne yönelimli dillerde, elbette her isim farklı çağrışımlara sahip olsa da, genellikle tür parametresinde bir tür kısıtlaması belirtmek mümkündür, böylece aşağı doğru başka bir türden. Örneğin, bu C # sözdizimidir:

//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}

Gerçek arabirim türlerini, bu arabirimler tarafından kısıtlanan türler üzerinde kullanmanın nedenleri nelerdir? Örneğin, yöntemi imzalamanın nedenleri I2 ExampleMethod(I2 value)nelerdir?


4
sınıf şablonları (C ++), ölçülü jeneriklerden tamamen farklı ve daha güçlü bir şeydir. Her ne kadar jenerik olan diller onlar için şablon sözdizimi ödünç aldı.
Tekilleştirici

Arayüz yöntemleri dolaylı çağrılardır, oysa tip yöntemleri doğrudan çağrı olabilir. Bu nedenle, ikincisi öncekinden daha hızlı olabilir ve refdeğer türü parametreleri durumunda, değer türünü gerçekten değiştirebilir.
user541686

@Deduplicator: Jeneriklerin şablonlardan daha eski olduğu düşünüldüğünde, jeneriklerin şablonlardan, sözdiziminde veya başka bir şekilde nasıl bir şey ödünç alabileceğini göremiyorum.
Jörg W Mittag

3
@ JörgWMittag: Deduplicator "jenerikleri destekleyen nesne yönelimli diller" ile "ML ve Ada" yerine "Java ve C #" anlamış olabilir. Daha sonra C ++ 'dan öncekine etkisi açıktır, ancak jenerik veya parametrik polimorfizme sahip tüm diller C ++' dan ödünç alınmamıştır .
Steve Jessop

2
@SteveJessop: ML, Ada, Eiffel, Haskell C ++ şablonlarından önce gelir, Scala, F #, OCaml sonra gelir ve hiçbiri C ++ 'ın sözdizimini paylaşmaz. (İlginçtir ki, C + 'dan özellikle ödünç alan D, hatta şablonlar C ++' ın sözdizimini paylaşmaz.) "Java ve C #" "jeneriklere sahip diller" in oldukça dar bir görünümü olduğunu düşünüyorum.
Jörg W Mittag

Yanıtlar:


21

Parametrik sürümün kullanılması

  1. Fonksiyonun kullanıcılarına daha fazla bilgi
  2. Yazabileceğiniz program sayısını kısıtlar (ücretsiz hata kontrolü)

Rastgele bir örnek olarak, kuadratik denklemin köklerini hesaplayan bir yöntemimiz olduğunu varsayalım

int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}

Ve sonra bunun gibi şeyler gibi başka sayılarda da çalışmasını istiyorsunuz int. Gibi bir şey yazabilirsiniz

Num solve(Num a, Num b, Num c){
  ...
}

Sorun, bunun ne istediğini söylememesi. Diyor ki

Bana sayı gibi 3 şey verin (mutlaka aynı şekilde değil) ve size bir tür sayı vereceğim

Biz böyle bir şey yapamaz int sol = solve(a, b, c)eğer a, bve cvardır intbiz yöntemi bir geri gidiyor bilmiyorum çünkü s intsonunda! Bu, çözümü daha büyük bir ifadede kullanmak istiyorsak, iniş ve dua ile garip bir dansa yol açar.

Fonksiyonun içinde, birisi bize bir şamandıra, bir bigint ve derece verebilir ve bunları birlikte eklememiz ve çoğalmamız gerekir. Bunu statik olarak reddetmek istiyoruz, çünkü bu 3 sınıf arasındaki operasyonlar anlamsız olacak. Dereceler mod a.plus(b) = b.plus(a)360'tır, bu yüzden benzer komiklikler ortaya çıkacaktır.

Parametrik polimorfizmi alt tiplemeyle kullanırsak, tüm bunları göz ardı edebiliriz çünkü tipimiz aslında ne demek istediğimizi söyler

<T : Num> T solve(T a, T b, T c)

Veya "Bana bir sayı olan bir tür verirseniz, bu katsayılarla denklemleri çözebilirim" sözcükleriyle.

Bu başka yerlerde de ortaya çıkıyor. Örnekler iyi bir kaynaktır fonksiyonları olan soyut konteyner, ala çeşit bitti reverse, sort, mapvb


8
Özet olarak, genel sürüm her üç girişin (ve çıktının) aynı tipte sayı olmasını garanti eder .
Matematiksel

Ancak, söz konusu türü denetlemediğinizde (ve bu nedenle bir arabirim ekleyemediğinizde) bu azalır. Maksimum genellik için, bağımsız değişken türü (örn. Num<int>) Tarafından parametreleştirilmiş bir arabirimi ek bir bağımsız değişken olarak kabul etmeniz gerekir . Temsilci aracılığıyla her tür arabirimi her zaman uygulayabilirsiniz. Arakın etrafından açıkça geçmeniz gerektiğinden, kullanımı çok daha sıkıcı olmak dışında, Haskell'in tip sınıfları budur.
Doval

16

Gerçek arayüz türlerini, bu arayüzler tarafından kısıtlanan türler üzerinde kullanmanın nedenleri nelerdir?

Çünkü ihtiyacın olan bu ...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

kesinlikle iki farklı imza. Birincisi , arabirimi uygulayan herhangi bir türü alır ve yaptığı tek garanti, dönüş değerinin arabirimi karşılamasıdır.

İkincisi , arabirimi uygulayan herhangi bir türü alır ve en azından bu türü geri döndüreceğini garanti eder (daha az kısıtlayıcı arayüzü karşılayan bir şey yerine).

Bazen, daha zayıf bir garanti istersiniz. Bazen daha güçlü olanı istersiniz.


Daha zayıf garanti versiyonunu nasıl kullanacağınıza bir örnek verebilir misiniz?
GregRos

4
@GregRos - Örneğin, bazı ayrıştırıcı kodunda yazdım. Ben Oriki Parsernesne (soyut bir temel sınıf, ama prensip tutar) alır ve yeni Parser(ama farklı bir tür ile) döndüren bir işlevi var . Son kullanıcı, beton tipinin ne olduğunu bilmemeli veya önemsememelidir.
Telastyn

C # 'da, yeni bir kısıtlama olmaksızın, geçirilenin dışında bir T döndürmenin neredeyse imkansız olduğunu (yansıma ağrısı olmadan) ve güçlü garantinizi kendi başına oldukça işe yaramaz hale getirdiğini hayal ediyorum.
NtscCobalt

1
@NtscCobalt: Hem parametrik hem de arayüz genel programlamasını birleştirdiğinizde daha kullanışlıdır. Örneğin, LINQ'nun her zaman yaptığı şey (kabul eder, bir IEnumerable<T>başka döndürür, IEnumerable<T>yani aslında bir OrderedEnumerable<T>)
Ben Voigt

2

Kısıtlı jeneriklerin yöntem parametreleri için kullanılması, bir yöntemin iletilen öğeye bağlı olarak çok geri dönüş türüne izin verebilir. .NET'te ek avantajları da olabilir. Onların arasında:

  1. Kısıtlı bir jeneratörü bir refveya outparametre olarak kabul eden bir metoda , kısıtlamayı karşılayan bir değişken geçirilebilir; aksine, bir arabirim tipi parametresine sahip jenerik olmayan bir yöntem, o kesin arabirim tipinin değişkenlerini kabul etmekle sınırlı olacaktır.

  2. Genel tip T parametresine sahip bir yöntem, T'nin genel koleksiyonlarını kabul edebilir. A yöntemini kabul IList<T> where T:IAnimaledebilecek List<SiameseCat>bir yöntem, ancak bunu isteyen bir yöntem IList<Animal>bunu yapamaz.

  3. Bir kısıtlama bazen genel tür açısından bir arayüz belirtebilir, örn where T:IComparable<T>.

  4. Bir arabirimi uygulayan bir yapı, kısıtlanmış bir genel parametreyi kabul eden bir yönteme iletildiğinde bir değer türü olarak tutulabilir, ancak bir arabirim türü olarak iletildiğinde kutulu olmalıdır. Bunun hız üzerinde büyük bir etkisi olabilir.

  5. Genel bir parametrenin birden fazla kısıtlaması olabilirken, "hem IFoo hem de IBar'ı uygulayan bir tür" parametresini belirtmenin başka bir yolu yoktur. Bazen bu tür iki uçlu bir kılıç olabilir, çünkü bir tür parametre alan kod IFoo, söz konusu örnek tüm kısıtlamaları karşılasa bile, çift kısıtlı bir jenerik bekleyen böyle bir yönteme geçirmeyi çok zor bulacaktır.

Belirli bir durumda jenerik kullanmanın bir avantajı yoksa, arayüz tipinde bir parametreyi kabul edin. Bir jenerik kullanımı, tip sistemi ve JITter'ı ekstra iş yapmaya zorlayacaktır, bu yüzden faydası yoksa, bunu yapmamalıdır. Öte yandan, yukarıdaki avantajlardan en az birinin geçerli olacağı çok yaygındır.

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.