Jenerik mi ortak arayüz mü?


20

Geçen sefer jenerik dersi ne zaman yazdığımı hatırlamıyorum. Her düşündüğümden sonra ihtiyacım olduğunu her düşündüğümde bir sonuca varmam.

İkinci cevap bu soruya beni (ben henüz yorum yapamam çünkü ben yeni bir soru yapılan) açıklama istemek için yapılmıştır.

Şimdi, jeneriklere ihtiyaç duyduğu bir duruma örnek olarak verilen kodu alalım:

public class Repository<T> where T : class, IBusinessOBject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

Tür kısıtlamaları vardır: IBusinessObject

Her zamanki düşünce tarzım: sınıfın kullanımı kısıtlanmış IBusinessObject, bunu kullanan sınıflar da Repositoryöyle. Depo bu IBusinessObjects depolar , büyük olasılıkla bu istemcileri arayüzü Repositoryaracılığıyla nesneleri almak ve kullanmak isteyeceksiniz IBusinessObject. Öyleyse neden sadece

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

Tho, örnek iyi değil, çünkü sadece başka bir koleksiyon türü ve genel koleksiyon klasikler. Bu durumda tür kısıtlaması da tuhaf görünür.

Aslında bu örnek bana class Repository<T> where T : class, IBusinessbBjectçok benziyor class BusinessObjectRepository. Jeneriklerin düzeltmesi için yapılan şey budur.

Bütün mesele şudur: Jenerikler, koleksiyonlar dışında herhangi bir şey için iyi midir ve sınıf kısıtlamaları, sınıf içindeki genel tür parametresi yerine bu tür kısıtlamasının kullanımı gibi, genel olarak uzmanlaştırılmaz mı?

Yanıtlar:


24

Önce saf parametrik polimorfizm hakkında konuşalım ve daha sonra sınırlı polimorfizme geçelim.

Parametrik Polimorfizm

Parametrik polimorfizm ne anlama geliyor? Bu, bir tür veya daha çok tür yapıcısının bir tür tarafından parametreleştirildiği anlamına gelir . Tür bir parametre olarak iletildiğinden, ne olabileceğini önceden bilemezsiniz. Buna dayanarak herhangi bir varsayımda bulunamazsınız. Şimdi, ne olabileceğini bilmiyorsanız, ne işe yarar? Bununla ne yapabilirsiniz?

Örneğin, saklayabilir ve alabilirsiniz. Daha önce bahsettiğiniz durum budur: koleksiyonlar. Bir öğeyi bir listede veya dizide saklamak için, öğe hakkında hiçbir şey bilmem gerekiyor. Liste veya dizi, türden tamamen habersiz olabilir.

Peki ya Maybetürü? Bunu bilmiyorsanız, Maybebelki bir değeri olan ve belki de olmayan bir türdür. Nerede kullanırdın? Örneğin, bir öğeyi sözlükten çıkarırken: bir öğenin sözlükte bulunmaması istisnai bir durum değildir, bu nedenle öğe yoksa gerçekten bir istisna atmamalısınız. Bunun yerine, Maybe<T>tam olarak iki alt tipi olan bir alt türünün örneğini döndürürsünüz : Noneve Some<T>. bir istisna veya tüm dans atmak yerine int.Parsegerçekten geri dönmesi gereken bir şeyin başka bir adayıdır .Maybe<int>int.TryParse(out bla)

Şimdi, Maybebunun sadece sıfır veya bir unsuru olabilen bir liste gibi olduğunu iddia edebilirsiniz. Ve bu yüzden biraz bir koleksiyon.

Sonra ne olacak Task<T>? Gelecekte bir noktada bir değer döndürmeyi vaat eden, ancak şu anda bir değeri olması gerekmeyen bir tür.

Yoksa ne olacak Func<T, …>? Tipler üzerinde soyutlama yapamazsanız, bir fonksiyondan bir tipten diğerine bir fonksiyon kavramını nasıl temsil edersiniz?

Ya da, daha genel olarak: İki yeniden o soyutlama dikkate ve olan temel yazılım mühendisliği işlemleri, neden olmaz sen türleri üzerinde soyut edebilmek istiyor?

Sınırlı Polimorfizm

Şimdi sınırlı polimorfizm hakkında konuşalım. Sınırlı polimorfizm temel olarak parametrik polimorfizm ve alt tip polimorfizminin buluştuğu yerdir: bir tip yapıcısının tür parametresinden tamamen habersiz olması yerine, türü belirli bir türün alt türü olarak bağlayabilir (veya sınırlayabilirsiniz).

Koleksiyonlara geri dönelim. Bir hashtable alın. Yukarıda bir listenin elemanları hakkında hiçbir şey bilmesine gerek olmadığını söyledik. Bir hashtable yapar: onları hash edebileceğini bilmelidir. (Not: C # 'da, tüm nesneler eşitlik için karşılaştırılabildiği gibi tüm nesneler yıkanabilir. Yine de bu, tüm diller için geçerli değildir ve bazen C #' da bile tasarım hatası olarak kabul edilir.)

Bu nedenle, hashtable içindeki anahtar türü için type parametrenizi bir örnek olarak sınırlamak istiyorsunuz IHashable:

class HashTable<K, V> where K : IHashable
{
  Maybe<V> Get(K key);
  bool Add(K key, V value);
}

Bunun yerine bunun olup olmadığını düşünün:

class HashTable
{
    object Get(IHashable key);
    bool Add(IHashable key, object value);
}

valueOradan çıkacak biriyle ne yapardın ? Onunla hiçbir şey yapamazsın, sadece bunun bir nesne olduğunu biliyorsun. Ve eğer onu tekrarlarsanız, elde ettiğiniz tek şey bildiğiniz bir şeydir IHashable(bu sadece bir mülke sahip olduğu için size çok fazla yardımcı olmaz Hash) ve bildiğiniz bir şey object(size daha az yardımcı olur).

Veya örneğinize dayanan bir şey:

class Repository<T> where T : ISerializable
{
    T Get(int id);
    void Save(T obj);
    void Delete(T obj);
}

Öğe, diskte depolanacağı için serileştirilebilir olmalıdır. Peki ya bunun yerine sahipseniz:

class Repository
{
    ISerializable Get(int id);
    void Save(ISerializable obj);
    void Delete(ISerializable obj);
}

Bir koyarsanız jenerik durumda ile, BankAccountiçinde, bir olsun BankAccountyöntemleri ve özellikleri gibi olan, arka Owner, AccountNumber, Balance, Deposit, Withdraw, vb Bir şey çalışabilirsiniz. Şimdi, diğer dava? Bir koymak BankAccountama geri bir olsun Serializablesadece bir özelliği vardır ki,: AsString. Bununla ne yapacaksın?

Sınırlı polimorfizmle yapabileceğiniz bazı düzgün hileler de vardır:

F-sınırlı polimorfizm

F-sınırlı niceleme temel olarak tip değişkeninin kısıtlamada tekrar göründüğü yerdir. Bu bazı durumlarda yararlı olabilir. Bir ICloneablearayüzü nasıl yazıyorsunuz ? Dönüş türünün uygulayıcı sınıfın türü olduğu bir yöntemi nasıl yazarsınız? MyType özelliğine sahip bir dilde , bu kolay:

interface ICloneable
{
    public this Clone(); // syntax I invented for a MyType feature
}

Sınırlı polimorfizmi olan bir dilde, bunun gibi bir şey yapabilirsiniz:

interface ICloneable<T> where T : ICloneable<T>
{
    public T Clone();
}

class Foo : ICloneable<Foo>
{
    public Foo Clone()
    {
        // …
    }
}

Bunun MyType sürümü kadar güvenli olmadığını unutmayın, çünkü birisinin sadece "yanlış" sınıfını tür yapıcısına geçirmesini engelleyen hiçbir şey yoktur:

class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
    public SomethingTotallyUnrelatedToBar Clone()
    {
        // …
    }
}

Özet Türü Üyeler

Sonuç olarak, soyut tip üyeleriniz ve alt tipleriniz varsa, parametrik polimorfizm olmadan tamamen başarabilir ve yine de aynı şeyleri yapabilirsiniz. Scala başladı dair ilk önemli dil olan bu yönde ilerliyor ile tam olarak örneğin Java ve C # tersi olan bunları kaldırmaya çalışırken sonra jenerik ve.

Temel olarak, Scala'da, tıpkı üye olarak alanlara, özelliklere ve yöntemlere sahip olabileceğiniz gibi, türleriniz de olabilir. Ve tıpkı alanlar, özellikler ve yöntemler bir alt sınıfta daha sonra uygulanacak şekilde soyut bırakılabilir gibi, yazım üyeleri de soyut bırakılabilir. ListC # 'da destekleniyorsa, böyle bir şeye benzeyen koleksiyonlara geri dönelim :

class List
{
    T; // syntax I invented for an abstract type member
    T Get(int index) { /* … */ }
    void Add(T obj) { /* … */ }
}

class IntList : List
{
    T = int;
}
// this is equivalent to saying `List<int>` with generics

anladığım kadarıyla, türler üzerindeki soyutlamanın yararlı olduğunu anlıyorum. sadece "gerçek hayatta" kullanımını görmüyorum. İşlev <> ve Görev <> ve Eylem <> kitaplık türleridir. ve teşekkürler interface IFoo<T> where T : IFoo<T>ayrıca hatırladım . Açıkçası bu gerçek hayat uygulaması. örnek harika. ama nedense tatmin olmuyorum. uygun olduğunda ve uygun olmadığında fikrimi almak istiyorum. cevaplar bu sürece bazı katkı var, ama hala kendimi tüm bu konuda rahatsız hissediyorum. garip çünkü dil seviyesi problemleri beni çok fazla rahatsız etmiyor.
jungle_mole

Harika bir örnek. Sınıf odasına geri döndüğümü hissettim. +1
Chef_Code

1
@Chef_Code: Umarım bu bir iltifattır :-P
Jörg W Mittag

Evet öyle. Daha sonra yorum yaptıktan sonra nasıl algılanabileceğini düşündüm. Samimiyeti onaylamak için ... Evet, iltifattan başka bir şey değil.
Chef_Code

14

Bütün mesele şudur: Jenerikler, koleksiyonlar dışında herhangi bir şey için iyi midir ve sınıf kısıtlamaları, sınıf içindeki genel tür parametresi yerine bu tür kısıtlamasının kullanımı gibi, genel olarak uzmanlaştırılmaz mı?

Hayır. RepositoryNerede aynı olduğunu çok fazla düşünüyorsun . Ama jenerikler bunun için değil. Onlar oradaKullanıcılar .

Burada kilit nokta, Deponun kendisinin daha genel olması değildir. Kullanıcıların olduğunu var daha specialized- yani o Repository<BusinessObject1>ve Repository<BusinessObject2>farklı türleri ve daha da ötesi, bir alırsam o Repository<BusinessObject1>, ben biliyorum ben alacak BusinessObject1dışarı geriGet .

Bu güçlü yazmayı basit mirastan sunamazsınız. Önerilen Depo sınıfınız, insanların farklı iş nesneleri için depoları karıştırmasını veya doğru iş nesnesi türünün geri gelmesini garanti etmesini önlemek için hiçbir şey yapmaz.


Teşekkürler, bu mantıklı. Ancak bu övgüyle karşılanan dil özelliğini kullanmanın tüm amacı, IntelliSense'i kapatan kullanıcılara yardımcı olmak kadar basit mi? (Biraz abartıyorum, ama eminim
anladınız

@zloidooraque: IntelliSense, bir depoda hangi Nesnelerin saklandığını da bilmez. Ancak evet, bunun yerine yayınlar yapmak istiyorsanız, jeneriksiz herhangi bir şey yapabilirsiniz.
gexicide

@gexicide asıl nokta: Ortak arayüz kullanırsam dökümleri nerede kullanmam gerektiğini görmüyorum. Asla "kullan Object" demedim . Ayrıca koleksiyon yazarken neden jenerik ilaç kullanıldığını da anlıyorum (KURU prensibi). Muhtemelen, ilk sorum koleksiyon bağlamı dışında jenerikler kullanmak hakkında bir şey olmalı ..
jungle_mole

@zloidooraque: Çevre ile hiçbir ilgisi yok. Intellisense size IBusinessObjecta BusinessObject1veya a olup olmadığını söyleyemez BusinessObject2. Aşırı yükleri, bilmediği türetilmiş türe göre çözemez. Yanlış türde geçen kodu reddedemez. Intellisense'nin kesinlikle hiçbir şey yapamayacağı daha güçlü yazmanın bir milyon biti var. Daha iyi takım desteği güzel bir faydadır, ancak temel nedenlerle hiçbir ilgisi yoktur.
DeadMG

@DeadMG ve bu benim amacım: intellisense yapamaz: jenerik kullanın, öyleyse yapabilir mi? önemli mi? nesneyi arabiriminden aldığınızda neden sersemletiyorsunuz? yaparsan, kötü tasarım, değil mi? ve neden ve "aşırı yüklenmeleri çöz" nedir? kullanıcı, doğru yöntem çağrısını sisteme (hangi polimorfizm) devrediyorsa, çağrı yönteminin veya türetilmiş türe dayalı olup olmadığına karar vermemelidir. Bu da beni bir soruya götürüyor: jenerikler kapların dışında faydalı mı? seninle tartışmıyorum, bunu gerçekten anlamam gerekiyor.
jungle_mole

13

"Bu Deponun büyük olasılıkla müşterileri IBusinessObject arabirimi üzerinden nesne almak ve kullanmak isteyecektir".

Hayır.

IBusinessObject öğesinin aşağıdaki tanıma sahip olduğunu düşünelim:

public interface IBusinessObject
{
  public int Id { get; }
}

Yalnızca kimliği tanımlar, çünkü tüm iş nesneleri arasında paylaşılan tek işlevdir. Ve iki gerçek iş nesneniz var: Kişi ve Adres (Kişilerin sokakları olmadığı ve Adreslerin isimleri olmadığı için, ikisini de her ikisinin işlevselliği olan bir commom arayüzüyle sınırlandıramazsınız. Bu, Arayüz Segragation Prensibi , "I" KATI )

public class Person : IBusinessObject
{
  public int Id { get; private set; }
  public string Name { get; private set; }
}

public class Address : IBusinessObject
{
  public int Id { get; private set; }
  public string City { get; private set; }
  public string StreetName { get; private set; }
  public int Number { get; private set; }
}

Şimdi, deponun genel sürümünü kullandığınızda ne olur:

public class Repository<T> where T : class, IBusinessObject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

Genel havuzda Get yöntemini çağırdığınızda, döndürülen nesne tüm sınıf üyelerine erişmenizi sağlayacak şekilde güçlü bir şekilde yazılır.

Person p = new Repository<Person>().Get(1);
int id = p.Id;
string name = p.Name;

Address a = new Repository<Address>().Get(1);
int id = a.Id;
string cityName = a.City;
int houseNumber = a.Number;

Öte yandan, genel olmayan Depoyu kullandığınızda:

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

Üyelere yalnızca IBusinessObject arayüzünden erişebileceksiniz:

IBussinesObject p = new Repository().Get(1);
int id = p.Id; //OK
string name = p.Name; //Oooops, you dont have "Name" defined on the IBussinesObject interface.

Bu nedenle, önceki kod aşağıdaki satırlardan dolayı derlenmeyecektir:

string name = p.Name;
string cityName = a.City;
int houseNumber = a.Number;

Tabii, IBussinesObject öğesini gerçek sınıfa atabilirsiniz, ancak jeneriklerin izin verdiği tüm derleme süresi sihrini kaybedersiniz (InvalidCastExceptions'a yol açar), gereksiz yere ek yükten muzdarip olur ... ne performansı (gerekir) kontrol derleme zaman bakım, sonra döküm kesinlikle ilk etapta jenerik kullanımı üzerinde herhangi bir fayda vermeyecektir.

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.