Tip testi ne zaman tamamdır?


53

Bazı doğal güvenlik türlerine sahip bir dil varsayarak (örneğin, JavaScript değil):

A'yı kabul eden bir yöntem göz önüne alındığında SuperType, çoğu durumda bir eylem seçmek için tip sınaması yapmak için cazip olabileceğimizi biliyoruz:

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    o.doSomethingA()
  } else {
    o.doSomethingB();
  }
}

Genellikle, her zaman değilse, üzerinde tek ve geçersiz kılınabilir bir yöntem oluşturmalı SuperTypeve bunu yapmalıyız:

public void DoSomethingTo(SuperType o) {
  o.doSomething();
}

... burada her alt tipe kendi doSomething()uygulaması verilmiştir . Başvurumuzun geri kalanı daha sonra verilenlerin SuperTypegerçekten a SubTypeAveya a olup olmadıklarını görmezden gelebilir SubTypeB.

Olağanüstü.

Ancak, hala is ahepsi güvenli olmasa da çoğu dilde benzer işlemlere sahibiz . Bu da açık tip testler için potansiyel bir ihtiyaç olduğunu göstermektedir.

Peki, hangi durumlarda, varsa, açık tip testler yapmalı mıyız yoksa yapmalı mıyız?

Yokluğum veya yaratıcı olmayışımı affet. Daha önce yaptığımı biliyorum; ama, çok uzun zaman önce dürüst olmuştu, yaptığım şeyin iyi olup olmadığını hatırlayamıyorum! Ve son hafızada, kovboy JavaScript'in dışındaki türleri test etme gereği duyduğumu sanmıyorum .



4
Ne Java ne de C # 'in ilk sürümlerinde jenerik olduğu belirtilmemiştir. Konteynır kullanmak için Object'e ve dan döküm yapmanız gerekiyordu.
Doval,

6
"Tip kontrolü" neredeyse her zaman kodun dilin statik yazma kurallarına uyup uymadığını kontrol etmek anlamına gelir. Ne demek istediğine genellikle tür testi denir .


3
Bu yüzden C ++ yazmayı ve RTTI'yi kapatmayı seviyorum. Bir kişi tam anlamıyla çalışma zamanında nesne türlerini test edemediğinde, geliştiriciyi burada sorulan soruya ilişkin iyi OO tasarımına uymaya zorlar.

Yanıtlar:


48

"Asla", kanuni cevap "tür sınaması ne zaman olur?" Bunu kanıtlamanın veya ispat etmenin bir yolu yok; “iyi tasarım” veya “iyi nesne yönelimli tasarım” yapan şeylerin bir inanç sisteminin bir parçasıdır. Aynı zamanda hokum.

Emin olmak için, bütünleşik bir sınıf grubunuz ve ayrıca bu tür doğrudan tip testine ihtiyaç duyan birden fazla fonksiyonunuz varsa, muhtemelen YANLIŞ YAPMAKtasınız. Gerçekten ihtiyacınız olan şey SuperType, alt tipleri ve içinde farklı şekilde uygulanan bir yöntemdir . Bu, nesne yönelimli programlamanın bir parçasıdır ve bir parçasıdır ve bütün sebep sınıfları ve kalıtım vardır.

Bu durumda, açıkça tür sınaması yanlıştır, çünkü tür sınaması doğal olarak yanlıştır, ancak dilin zaten tür ayrımcılığını sağlamanın temiz, genişletilebilir, deyimsel bir yoluna sahip olduğu ve onu kullanmadığınız için yanlış. Bunun yerine, ilkel, kırılgan, genişletilemeyen bir deyime geri döndünüz.

Çözüm: Deyimi kullanın. Önerdiğiniz gibi, sınıfların her birine bir yöntem ekleyin, sonra standart kalıtımın ve yöntem seçim algoritmalarının hangi durumun uygulanacağını belirlemesine izin verin. Veya temel türleri değiştiremiyorsanız, alt sınıfı ekleyin ve yönteminizi buraya ekleyin.

Geleneksel akıl için çok fazla ve bazı cevaplarda. Açık tip testlerin mantıklı olduğu bazı durumlar:

  1. Bu bir kerelik. Yapacak çok fazla tür ayrımcılığınız varsa, türleri veya alt sınıfı genişletebilirsiniz. Ama yapmazsın. Açık testlere ihtiyaç duyacağınız sadece bir veya iki yeriniz var, bu yüzden geri dönüp işlevleri sıraya sokmak için sınıf hiyerarşisi içinde çalışmak için zaman ayırmaya değmez. Veya bu kadar basit, sınırlı bir kullanım için genel sınıflandırma, test etme, tasarım incelemeleri, dokümantasyon veya temel sınıfların diğer özelliklerini ekleme pratik çabasına değmez. Bu durumda, doğrudan test yapan bir fonksiyon eklemek rasyoneldir.

  2. Sınıfları ayarlayamazsın. Alt sınıflamayı düşünürsün - ama yapamazsın. Örneğin, Java’daki birçok sınıf belirlenmiştir final. Bir atmaya çalışıyorsunuz public class ExtendedSubTypeA extends SubTypeA {...} ve derleyici, belirsiz bir şekilde, yaptığınız şeyin mümkün olmadığını söylüyor. Nesne yönelimli modelin özür dilerim, zarafeti ve karmaşıklığı! Birisi türlerini uzatamayacağınıza karar verdi! Ne yazık ki, standart kütüphanelerin çoğu vardır finalve sınıf finalyapmak ortak tasarım rehberliğidir. Bir fonksiyon son çalışması, sizin için uygun olan şeydir.

    BTW, bu statik olarak yazılmış dillerle sınırlı değildir. Dinamik dil Python, C'de uygulanan kapaklar altında gerçekten değiştirilemeyen bir dizi temel sınıfa sahiptir. Java gibi, standart türlerin çoğunu içerir.

  3. Kodunuz harici. Bir dizi veritabanı sunucusundan, ara katman yazılım motorundan ve kontrol edemediğiniz veya ayarlayamadığınız diğer kod tabanlarından gelen sınıflar ve nesnelerle gelişiyorsunuz. Kodunuz, başka bir yerde üretilen nesnelerin yalnızca az miktarda tüketicisidir. Alt sınıfı bile olsa alt sınıflarınızda SuperTypenesneler oluşturmak için bağımlı olduğunuz kütüphaneleri elde edemezsiniz. Varyantlarınızda değil, bildikleri türlerin örneklerini size verecekler. Bu her zaman böyle değildir ... bazen esneklik için üretilirler ve onları beslediğiniz sınıfların örneklerini dinamik olarak başlatırlar. Veya fabrikalarının inşa etmesini istediğiniz alt sınıfları kaydetmek için bir mekanizma sağlarlar. XML ayrıştırıcıları bu tür giriş noktaları sağlamada özellikle iyi görünüyor; bakınız örneğin veya Python'da lxml . Ancak çoğu kod tabanları yok değil böyle uzantıları sağlamak. Seni inşa ettikleri ve bildikleri sınıfları geri verecekler. Genelde, sonuçlarını özel sonuçlarınıza proxy olarak bildirmek mantıklı olmayacaktır, böylece tamamen nesne yönelimli bir tip seçici kullanabilirsiniz. Eğer tür ayrımcılığı yapacaksanız, bunu nispeten kabaca yapmak zorunda kalacaksınız. Tip sınama kodu sonra oldukça uygun görünüyor.

  4. Zavallı kişinin jenerik / çoklu gönderme. Kodunuza çeşitli farklı türler kabul etmek istiyorsunuz ve bir dizi çok özel yönteme sahip olmanın zarif olmadığını düşünün. public void add(Object x)mantıklı görünüyor, ama değil bir dizi addByte, addShort, addInt, addLong, addFloat, addDouble, addBoolean, addChar, ve addStringvaryant (bir kaç isim). Üstün bir süper tip alan işlevlere veya yöntemlere sahip olmak ve daha sonra türlere göre ne yapılacağına karar vermek - size yıllık Booch-Liskov Tasarım Sempozyumu'nda Saflık Ödülü'nü kazanamayacaklar, ancak Macarca adlandırma size daha basit bir API sağlayacaktır. Bir anlamda, sizin is-aveyais-instance-of sınama, genel olarak desteklemeyen bir dil bağlamında genel veya çoklu gönderimi simüle ediyor.

    Dahili ikisi için dil desteği jenerikler ve ördek yazarak daha muhtemel "zarif ve uygun bir şey yapmak" yaparak tür denetleme ihtiyacını azaltır. Çoklu gönderim / arayüz seçimi Julia gibi dillerde görülen ve benzer türü tabanlı seçim için dahili mekanizmaları ile doğrudan tipi test yerini git "ne yapacağını." Ancak bütün diller bunu desteklemiyor. Java, örneğin genellikle tek bir gönderimdir ve deyimleri ördek yazma konusunda süper kolay değildir.

    Ancak tüm bu tür ayrımcılık özellikleriyle - kalıtım, jenerikler, ördek yazma ve çoklu gönderme - bile, nesnenin türüne göre bir şey yaptığınızı gerçeğe dönüştüren tek bir konsolide rutinin olması bazen uygundur. açık ve acil. Gelen metaprogramlama Ben aslında kaçınılmaz bulduk. Doğrudan tip sorgulamalara geri dönüp düşmeme, "eylemdeki pragmatizm" veya "kirli kodlama" anlamına mı gelir, tasarım felsefenize ve inançlarınıza bağlı olacaktır.


Bir işlem belirli bir tür üzerinde gerçekleştirilemiyorsa, ancak üstü kapalı olarak dönüştürülebileceği iki farklı tür üzerinde gerçekleştirilebiliyorsa, aşırı yükleme yalnızca her iki dönüşümün de aynı sonuçları vermesi durumunda uygundur. Aksi halde biri .NET'teki gibi ufak davranışlarla biter: (1.0).Equals(1.0f)true [argüman teşvik eder double] verir, ancak (1.0f).Equals(1.0)false [argüman teşvik eder object] verir; Java’da Math.round(123456789*1.0)123456789, ancak Math.round(123456789*1.0)verim 123456792 [argüman floatyerine double].
supercat

Bu otomatik tip döküm / eskalasyonuna karşı klasik argümandır. En kötü durumlarda, kötü ve paradoksal sonuçlar. Katılıyorum, ancak cevabımla nasıl ilişki kurmayı düşündüğünden emin değilim.
Jonathan Eunice

Farklı türlerde farklı adlandırılmış yöntemler kullanmak yerine aşırı yüklenmeyi savunan görünen 4. numaralı konuya cevap veriyordum.
supercat

2
@supercat Zavallı görüşümü suçla, ancak iki ifadesi Math.roundbana aynı görünüyor. Fark ne?
Lily Chung

2
@IstvanChung: Hata ... ikincisi olmalıydı Math.round(123456789)[birileri yeniden yazar takdirde neler olabileceğini işaret Math.round(thing.getPosition() * COUNTS_PER_MIL)hayata değil ölçeklendirilmemiş pozisyon değerini döndürmek için getPositionbir döner intOr long.]
SuperCat

25

Şimdiye dek ihtiyaç duyduğum en temel durum equals(other), tam türüne bağlı olarak farklı algoritmalar gerektirebilecek bir yöntemde olduğu gibi iki nesneyi karşılaştırırken oldu other. O zaman bile, oldukça nadirdir.

Karşılaştığım diğer durum, yine çok nadiren, bazen daha güvenli bir şekilde daha spesifik bir tipe güvenli bir şekilde döküm yapmanıza ihtiyaç duyduğunuz seri kaldırma veya ayrıştırma işleminden sonra.

Ayrıca, bazen kontrol etmediğiniz üçüncü taraf kodları etrafında çalışmak için bir kesmeye ihtiyacınız vardır. Gerçekten düzenli olarak kullanmak istemediğiniz şeylerden biri, ancak gerçekten ihtiyacınız olduğunda orada olmasından memnun.


1
Bir anda, onu kullanmayı kesinlikle hatırlamamış olan seri kaldırma vakası beni çok mutlu etti; ama şimdi nasıl olacağını hayal edemiyorum! Bunun için bazı garip tip aramalar yaptığımı biliyorum; ancak bunun tip testi oluşturup oluşturmadığından emin değilim . Belki seri hale getirme daha yakın bir paraleldir: Nesneleri somut türleri için sorgulamak zorunda kalmak.
svidgen

1
Serileştirme genellikle polimorfizm kullanılarak yapılabilir. Seriyi kaldırırken, çoğu zaman benzer bir şey yaparsınız BaseClass base = deserialize(input), çünkü henüz türü bilmezsiniz, o zaman if (base instanceof Derived) derived = (Derived)basetam türetilmiş türü olarak saklarsınız.
Karl Bielefeldt

1
Eşitlik mantıklı. Tecrübelerime göre, bu yöntemler genellikle “bu iki nesne aynı somut tipe sahipse, tüm alanların eşit olup olmadığını döndürür; Aksi takdirde, false döndürün (veya karşılaştırılamaz) ”.
Jon Purdy

2
Özellikle, kendinizi test türü olarak bulmanız, polimorfik eşitlik karşılaştırmasının bir engerek yuvası olduğunu gösteren iyi bir göstergedir.
Steve Jessop,

12

Standart (ancak umarım nadirdir) durumu şöyle görünür: aşağıdaki durumda ise

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    DoSomethingA((SubTypeA) o )
  } else {
    DoSomethingB((SubTypeB) o );
  }
}

işlevler DoSomethingAveya / / DoSomethingBmiras ağacının üye işlevleri olarak kolayca uygulanamaz . Örneğin, eğerSuperTypeSubTypeASubTypeB

  • alt türler, değiştiremeyeceğiniz bir kütüphanenin parçasıdır veya
  • DoSomethingXXXBu kütüphaneye kod eklemek, yasaklı bir bağımlılık getirmek anlamına gelirse.

(Örneğin, bir sargı veya adaptör oluşturarak bu sorunu aşmak için durumlar sıklıkla vardır Not SubTypeAve SubTypeBya da yeniden uygulamaya çalışırken DoSomethingtemel işlemleri açısından tamamen SuperType), fakat bazen bu çözümler güçlük değmez ya yapmak Açık tip test yapmaktan daha karmaşık ve daha az genişletilebilir şeyler.

Dünkü çalışmamdan bir örnek: Bir nesne listesinin işlenmesini paralelleştireceğim ( SuperTypetam olarak iki farklı alt tipte türden , daha fazla olamayacak kadar düşük olması muhtemel). Eşsiz sürümde iki döngü bulunur: A alt tipindeki nesneler için bir döngü, çağrı DoSomethingAve B alt tipindeki nesneler için ikinci bir döngü, arama DoSomethingB.

"DoSomethingA" ve "DoSomethingB" yöntemleri, hem A, hem de B alt tipleri kapsamında bulunmayan bağlam bilgisini kullanan zaman yoğun hesaplamalardır (bu yüzden bunları alt tiplerin üye işlevleri olarak uygulamak anlamsızdır). Yeni "paralel döngü" bakış açısından, onlarla eşit bir şekilde başa çıkarak işleri çok daha kolaylaştırıyor, bu yüzden DoSomethingToyukarıdan benzer bir fonksiyon uyguladım . Bununla birlikte, "DoSomethingA" ve "DoSomethingB" uygulamalarına bakmak, içten çok farklı çalıştıklarını göstermektedir. Bu yüzden SuperType, çok sayıda soyut yöntemle genişleterek genel bir "DoSomething" uygulaması yapmaya çalışmak gerçekten işe yaramadı, ya da işleri fazla abartmak anlamına geliyordu.


2
Sisli beynimi ikna etmek için küçük, somut bir örnek ekleyebilme şansınız var mı?
svidgen

Açık olmak gerekirse, ben demiyorum olduğunu yapmacık. Sanırım bugün fevkalade sisli beynim var.
svidgen

@svidgen: Bu, arı yetiştiriciliğinin uzağında olmaktan uzak, aslında bugün bu durumla karşılaştım (bu örneği burada gönderemiyorum çünkü işletme içindekiler içeriyor). Ve "is" işlecini kullanmanın bir istisna olması ve sadece nadir durumlarda yapılması gerektiğini söyleyen diğer cevapları kabul ediyorum.
Doktor Brown

Sanırım gözden kaçırdığım düzenlemenin iyi bir örnek olduğunu düşünüyorum. ... örneklerini bile bu sorun yok burada var kontrol SuperTypeve alt sınıfları var?
svidgen

6
İşte somut bir örnek: Bir web servisinden gelen bir JSON yanıtındaki en üst düzey konteyner bir sözlük veya bir dizi olabilir. Genellikle, JSON'u gerçek nesnelere dönüştüren bir araca sahipsiniz (örneğin NSJSONSerialization, Obj-C'de), ancak yanıtın beklediğiniz türü içerdiğine güvenmek istemezsiniz, bu yüzden kullanmadan önce kontrol edin (örn. if ([theResponse isKindOfClass:[NSArray class]])...) .
Caleb

5

Bob Amca'nın dediği gibi:

When your compiler forgets about the type.

Clean Coder bölümlerinden birinde, Employees döndürmek için kullanılan bir işlev çağrısı örneği verdi . Managerbir alt türüdür Employee. Bir Managerkimliğini kabul eden ve onu ofise çağıran bir uygulama hizmetimiz olduğunu varsayalım :) İşlev getEmployeeById()bir süper tür döndürür Employee, ancak bu kullanım durumunda bir yöneticinin iade edilip edilmediğini kontrol etmek istiyorum.

Örneğin:

var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
   throw new Exception("Invalid Id specified.");
manager.summon();

Burada sorguyla döndürülen çalışanın gerçekten bir yönetici olup olmadığını kontrol ediyorum (yani bir yönetici olmasını bekliyorum, aksi takdirde hızlı başarısız olur).

En iyi örnek değil, ama sonuçta Bob Amca.

Güncelleme

Örneği bellekten hatırlayabildiğim kadar çok güncelledim.


1
Neden gelmez Manager's uygulaması summon()sadece bu örnekte istisna?
svidgen

@svidgen belki CEOcan çağırabilir Manager.
kullanıcı253751,

@svidgen, O zaman, çalışanRepository.getEmployeeById'in (empId) bir menajer getirmesi beklendiği kadar açık olmazdı
Ian

@Bunu problem olarak görmüyorum. Çağıran kod bir soruyorsa Employee, sadece bir gibi davranması için özen göstermesi gerekir Employee. Farklı alt sınıfların Employeefarklı izinleri, sorumlulukları, vb. Varsa , tip testini gerçek bir izin sisteminden daha iyi bir seçenek yapan şey nedir?
svidgen

3

Tip kontrolü ne zaman tamamdır?

Asla.

  1. Bu türle ilişkili davranışı başka bir işlevde bulundurarak, Açık Kapalı Prensibi ihlal ediyorsunuz , çünkü ismaddeyi değiştirerek veya (bazı dillerde senaryo) Çünkü iskontrolü yapan işlevin içindekileri değiştirmeden yazıyı genişletemezsiniz .
  2. Daha da önemlisi, iskontroller Liskov Değişim İlkesini ihlal ettiğinizin güçlü bir işaretidir . İle çalışır şey SuperTypeolmalıdır tamamen cahil olabilir hangi alt türleri.
  3. Sen ediyoruz örtülü türünün adı ile bazı davranış ilişkilendirerek. Bu, kodunuzu korumayı zorlaştırır, çünkü bu örtülü sözleşmeler kod boyunca yayılır ve gerçek sınıf üyeleri gibi evrensel ve tutarlı bir şekilde uygulanmaları garanti edilmez.

Tüm bunlar, isçekler diğer alternatiflerden daha az kötü olabilir . Tüm yaygın işlevselliği temel sınıfa sokmak ağır elle yapılır ve genellikle daha kötü sorunlara neden olur. Örneğin "tip" in ne olduğuna dair bir bayrağı veya numaralandırması olan tek bir sınıf kullanmak ... korkunç bir şeyden daha zordur, çünkü şu anda tüm sistem çevresini tüm tüketicilere yayıyorsunuz .

Kısacası, her zaman tür denetimlerini güçlü bir kod kokusu olarak düşünmelisiniz. Ancak tüm kurallarda olduğu gibi, hangi kural ihlalinin en az saldırgan olduğunu seçmek zorunda kaldığınız zamanlar olacaktır.


3
Küçük bir uyarı var: cebirsel veri türlerini , onları desteklemeyen dillerde uygulamak . Bununla birlikte, kalıtımın ve tip kontrolünün kullanımı orada tamamen sahte bir uygulama detayıdır; amaç alt tipleri tanıtmak değil, değerleri sınıflandırmaktır. Bunu yalnızca ADT'ler yararlı olduğu ve hiçbir zaman çok güçlü bir niteleyici olmadığı için ortaya çıkardım, aksi halde tamamen katılıyorum; instanceofuygulama detaylarını sızdırıyor ve soyutlamayı kırıyor.
Doval

17
“Asla”, böyle bir bağlamda gerçekten sevmediğim bir kelime, özellikle de aşağıda yazdıklarınızla çelişiyorsa.
Doktor Brown

10
"Asla" derken aslında "bazen" demek istiyorsan haklısın demektir.
Caleb

2
Tamam , izin verilebilir, kabul edilebilir vb. Anlamına gelir, ancak mutlaka ideal veya en uygun değil. Tamam değil kontrast : bir şey yolunda değilse o zaman hiç yapmamalısın. Eğer işaret gibi, bir şeyin türünü kontrol etmek ihtiyacı kodunda derin sorunların bir göstergesi olabilir, ancak en uygun, en az kötü bir seçenek olduğunu ve bu tür durumlarda açıkça olduğunda zamanlar vardır Tamam de araçları kullanmak senin bertaraf. (Her durumda bunlardan kaçınmak kolay olsaydı, muhtemelen ilk etapta orada olmazlardı.) Soru, bu durumları tanımlamaktan kaynaklanıyor ve hiçbir zaman yardımcı olmuyor.
Caleb,

2
@supercat: A yöntemleri mümkün olan en az belirli türde talep etmesi gerektiğini onun gereklerini karşılar . IEnumerable<T>"son" bir elementin varolduğunu vaat etmiyor. Yöntemin böyle bir öğeye ihtiyacı varsa, birinin varlığını garanti eden bir tip gerektiriyor olması gerekir. Ve bu tipin alt tipleri daha sonra "Son" yönteminin verimli bir şekilde uygulanmasını sağlayabilir.
cHao

2

Büyük bir kod tabanınız varsa (100K kod satırından fazlaysa) ve sevkıyatınıza yakınsanız veya daha sonra birleştirme yapmanız gereken bir dalda çalışıyorsanız, bu nedenle çok fazla başa çıkmanın maliyeti / riski vardır.

Daha sonra, bazen sistemin büyük bir refrakteri veya basit bir yerelleştirilmiş “tip testi” seçeneğiniz vardır. Bu, mümkün olan en kısa sürede geri ödenmesi gereken ancak çoğu zaman ödenmeyen bir teknik borç yaratır.

(Örnek olarak kullanılabilecek kadar küçük olan herhangi bir kod, daha iyi tasarımın açıkça görülebilmesi için yeterince küçük olduğundan bir örnek bulmak mümkün değildir.)

Başka bir deyişle, amaç, tasarımınızın temizliği için “oy kullanma” yapmak yerine ücretinize ödeme yapılması olduğunda.


Diğer yaygın durum, örneğin bazı çalışan türleri için farklı bir kullanıcı arayüzü gösterdiğiniz zaman, ancak kullanıcı arayüzü kavramlarının tüm “etki alanı” sınıflarınıza girmesini istemiyorsanız, kullanıcı arabirimi kodu.

Kullanıcı arayüzünün hangi sürümünün gösterileceğine karar vermek için “tür testi” kullanabilirsiniz veya “etki alanı sınıflarından” “UI sınıflarına” dönüştüren bazı süslü arama tablolarına sahip olabilirsiniz. Arama tablosu sadece “tip testi” ni bir yerde saklamanın bir yoludur.

(Veritabanı güncelleme kodu, UI kodu ile aynı sorunlara sahip olabilir, ancak yalnızca bir veritabanı güncelleme kodu kümesine sahip olabilirsiniz, ancak gösterilen nesne türüne uyarlanması gereken birçok farklı ekrana sahip olabilirsiniz.)


Ziyaretçi Deseni genellikle UI durumunuzu çözmenin iyi bir yoludur.
Ian Goldby

@IanGoldby, olabilecekleri zaman kabul etti, ancak hala "tip testi" yapıyorsunuz, sadece biraz gizlenmişsiniz.
Ian

Normal bir sanal yöntem çağırdığınızda gizlendiği gibi gizli mi? Yoksa başka bir şey mi demek istiyorsun? Kullandığım Ziyaretçi Şablonunun türüne bağlı koşullu ifadeleri yok. Her şey dil tarafından yapılır.
Ian Goldby

@IanGoldby, WPF veya WinForms kodunun anlaşılmasını zorlaştırabilir anlamında gizli demek istedim. Bazı web tabanı kullanıcı arayüzleri için çok iyi çalışacağını umuyorum.
Ian,

2

LINQ uygulaması, olası performans optimizasyonları için çok sayıda tip kontrolü ve ardından IEnumerable için bir geri dönüş kullanır.

En belirgin örnek muhtemelen ElementAt yöntemidir (.NET 4.5 kaynağının küçük bir kısmı):

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) { 
    IList<TSource> list = source as IList<TSource>;

    if (list != null) return list[index];
    // ... and then an enumerator is created and MoveNext is called index times

Ancak, Numaralandırılabilir sınıfında benzer bir desenin kullanıldığı birçok yer var.

Bu yüzden belki de yaygın olarak kullanılan bir alt tip için performansı optimize etmek geçerli bir kullanımdır. Bunun nasıl daha iyi tasarlanmış olabileceğinden emin değilim.


Arabirimlerin varsayılan uygulamaları sağlayabileceği uygun bir araç sağlayarak daha iyi tasarlanabilir ve daha sonra hangi yöntemlerin iyi çalışacağını, yavaşça ya da hiç olmayacağını belirten bir özellik ile birlikte IEnumerable<T>olanlar gibi birçok yöntem içerir. Bir tüketicinin koleksiyon hakkında güvenli bir şekilde yapabileceği çeşitli varsayımların yanı sıra (örneğin, büyüklüğü ve / veya mevcut içeriğinin asla değişmemesi garanti edilir [bir tür mevcut içeriğin değişmeyeceğini garanti ederken , destekleyebilir ). List<T>FeaturesAdd
supercat

Bir türün farklı zamanlarda birbirinden tamamen farklı iki şeyden birini tutması gerekebilecek senaryolar dışında ve gereksinimlerin açıkça birbirini dışlayan (ve bu nedenle ayrı alanlar kullanmak gereksiz olabilir), genellikle denemenin bir işaret olması gerektiğini düşünürüm. temel arayüzün bir parçası olması gereken üyeler değildi. Bu, bazı üyeleri içermesi gereken bir arayüz kullanan, ancak temel arayüzün ihmali etrafında çalışmak için denemeyi kullanmaması gerektiği anlamına gelmez, ancak temel arayüz yazma o müşterilerin deneme yapma gereksinimini en aza indirmelidir.
supercat

1

Oyun geliştirmelerinde, özellikle çarpışma tespitinde, bazı tip testler kullanılmadan başa çıkılması zor olan bir örnek var.

Tüm oyun nesnelerinin ortak bir temel sınıftan geldiğini varsayalım GameObject. Her nesne, bir rijit cisim çarpışma bir şekle sahiptir CollisionShape, ancak gerçek çarpışma şekilleri bu gibi tüm beton alt sınıfları olacaktır (sorgu konum, oryantasyon, vb söylemek) ortak bir arayüz sağlayabilir Sphere, Box, ConvexHullvb geometrik cismin bu tür özel bilgileri depolamak ( gerçek bir örnek için buraya bakınız )

Şimdi, bir çarpışmayı test etmek için her çarpışma şekli tipi çifti için bir fonksiyon yazmam gerekiyor:

detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...

Bu iki geometrik türün kesişimini gerçekleştirmek için gereken belirli bir matematiği içerir.

Oyun döngümün her bir onay işaretinde çarpışmalar için nesne çiftlerini kontrol etmem gerekiyor. Ancak yalnızca GameObjects ve bunlara karşılık gelen CollisionShapes erişimine sahibim . Açıkçası hangi çarpışma algılama fonksiyonunun çağırılacağını bilmek için somut türleri bilmem gerekiyor. Çift gönderim bile (ki mantıklı bir şekilde türü kontrol etmekten farklı değildir) burada yardımcı olabilir *.

Bu durumda pratikte gördüğüm fizik motorları (Bullet ve Havok) bir ya da diğerinin tip testine dayanıyor.

Bunun mutlaka iyi bir çözüm olduğunu söylemiyorum , sadece bu soruna az sayıda olası çözümün en iyisi olabilir.

* Teknik olarak , çift ​​gönderimi, N (N + 1) / 2 kombinasyonunu gerektirecek (burada N sahip olduğunuz şekil tipi sayısıdır) ve sadece gerçekten ne yaptığınızı şaşırtacak korkunç ve karmaşık bir şekilde kullanmak mümkündür. eşzamanlı olarak iki şeklin türünü bulmak, bu yüzden bunun gerçekçi bir çözüm olduğunu düşünmüyorum.


1

Bazen, tüm sınıflara ortak bir yöntem eklemek istemezsiniz çünkü bu görevi yerine getirmek onların sorumluluğunda değildir.

Örneğin, bazı objeler çizmek istiyorsunuz ancak çizim kodunu doğrudan onlara eklemek istemiyorsunuz (bu mantıklı). Birden fazla gönderimi desteklemeyen dillerde, aşağıdaki kodla karşılaşabilirsiniz:

void DrawEntity(Entity entity) {
    if (entity instanceof Circle) {
        DrawCircle((Circle) entity));
    else if (entity instanceof Rectangle) {
        DrawRectangle((Rectangle) entity));
    } ...
}

Bu kod birkaç yerde göründüğünde ve yeni bir Varlık türü eklerken her yerde değiştirmeniz gereken bu sorunlu hale gelir. Eğer durum buysa, Ziyaretçi desenini kullanmaktan kaçınılabilir, ancak bazen işleri basit tutmak ve üzerine baskı yapmak daha iyi olmaz. Bunlar tip testinin uygun olduğu durumlardır.


0

Kullandığım tek zaman yansıma ile birlikte. Fakat o zaman bile, dinamik kontrol çoğunlukla belirli bir sınıfa kodlanmış değil (ya da sadece Stringya da gibi özel sınıflara kodlanmış List).

Dinamik kontrol ile demek istediğim:

boolean checkType(Type type, Object object) {
    if (object.isOfType(type)) {

    }
}

ve kodlanmış değil

boolean checkIsManaer(Object object) {
    if (object instanceof Manager) {

    }
}

0

Tip testi ve tip döküm, birbiriyle çok yakından ilişkili kavramlardır. Yani yakından sana gerektiğini söyleyerek emin hissediyorum ilgili hiç bir tip testi yapmak sürece senin niyet sonucuna dayanarak nesne artığını yazmaktır.

İdeal Nesne Yönelimli tasarım hakkında düşündüğünüz zaman, tip testi (ve döküm) asla yapılmamalıdır . Fakat umarım şimdi Nesne Yönelimli programlamanın ideal olmadığını keşfedersiniz. Bazen, özellikle düşük seviyeli kodla, kod idealine sadık kalamaz. Java'daki ArrayLists ile durum böyle; Çalışma zamanında dizide hangi sınıfın saklandığını bilmedikleri için Object[]diziler yaratır ve statik olarak doğru tipe koyarlar.

Tip testi (ve tip döküm) için ortak bir ihtiyacın Equals, çoğu dilde basit olması gereken yöntemden geldiği belirtildi Object. Uygulamada, iki nesnenin aynı türde olup olmadığına ve hangi tipte olduklarını test edebilmeye ihtiyaç duyan bazı detaylı kontroller yapılmalıdır.

Tip testi de yansıma sık sık ortaya çıkıyor. Genellikle, geri dönen yöntemlere Object[]veya başka bir genel diziye sahip olursunuz ve Foone olursa olsun tüm nesneleri çıkarmak istersiniz . Bu, tip testi ve döküm işlemlerinin tamamen meşru bir kullanımıdır.

Genel olarak, tür sınaması, kodunuzu belirli bir uygulamanın nasıl yazıldığıyla gereksiz olarak eşleştirdiğinde kötüdür . Bu, çizgilerin, dikdörtgenlerin ve dairelerin kesişimini bulmak isteyip istemediğiniz gibi, her bir tür veya tür birleşimi için belirli bir sınamaya kolayca gereksinim duyabilir ve kesişim işlevi her bir kombinasyon için farklı bir algoritmaya sahiptir. Amacınız, bir nesneyle ilgili herhangi bir ayrıntıyı, o nesneyle aynı yere koymak, çünkü bu, kodunuzu korumayı ve genişletmeyi kolaylaştıracaktır.


1
ArrayListsJava'nın jenerikliği olmadığı ve nihayet kullanıldığında Oracle'ın jenerik olmayan kodla geriye dönük uyumluluk için seçtiği için çalışma zamanında depolandığını bilmiyorum. equalsAynı konuya sahip ve yine de şüpheli bir tasarım kararı; Eşitlik karşılaştırmaları her tür için bir anlam ifade etmiyor.
Doval

1
Teknik olarak, Java koleksiyonları do not şey içeriklerini attı. Derleyici, her erişimin bulunduğu yere tipografi enjekte eder: örn.String x = (String) myListOfStrings.get(0)

Java kaynağına en son baktığımda (1.6 veya 1.5 olabilir), ArrayList kaynağında açık bir döküm vardı. İyi sebeplerden dolayı (bastırılmış) bir derleyici uyarısı oluşturur, ancak yine de izin verilir. Sanırım, jenerik yöntemlerin nasıl uygulandığı nedeniyle, Objectyine de erişilinceye kadar her zaman bir şeydi; Java'daki jenerikler, yalnızca derleyici kurallarına göre güvenli hale getirilmiş örtülü döküm sağlar.
meustrus

0

İki tip içeren bir karar vermeniz gerektiği ve bu kararın bu tip hiyerarşi dışındaki bir nesnede kapsanması durumunda kabul edilebilir . Örneğin, diyelim ki işleme için bekleyen nesneler listesinde hangi nesnenin işleneceğini planlıyorsunuz:

abstract class Vehicle
{
    abstract void Process();
}

class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }

Diyelim ki iş mantığımız kelimenin tam anlamıyla "tüm arabalar teknelere ve kamyonlara göre öncelik kazanıyor". PrioritySınıfa bir özellik eklemek, bu işletme mantığını temiz bir şekilde ifade etmenize izin vermiyor çünkü bununla sonuçlanacaksınız:

abstract class Vehicle
{
    abstract void Process();
    abstract int Priority { get }
}

class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }

Sorun şu ki, öncelik sırasını anlamak için tüm alt sınıflara ya da başka bir deyişle alt sınıflara birleştirme eklediğinize bakmak zorundasınız.

Elbette öncelikleri sabitler haline getirmeli ve kendileri tarafından bir sınıfa koymalısınız;

static class Priorities
{
    public const int CAR_PRIORITY = 1;
    public const int BOAT_PRIORITY = 2;
    public const int TRUCK_PRIORITY = 2;
}

Bununla birlikte, gerçekte, zamanlama algoritması gelecekte değişebilecek bir şeydir ve sonunda sadece tipten daha fazlasına bağlı olabilir. Örneğin, "5000 kg ağırlığındaki kamyonlar diğer tüm araçlara göre daha öncelikli" diyebilir. Bu yüzden programlama algoritması kendi sınıfına aittir ve hangisinin önce başlayacağını belirlemek için türü incelemek iyi bir fikirdir :

class VehicleScheduler : IScheduleVehicles
{
    public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
    {
        if(vehicle1 is Car) return vehicle1;
        if(vehicle2 is Car) return vehicle2;
        return vehicle1;
    }
}

Bu, iş mantığını uygulamanın en kolay yoludur ve gelecekteki değişikliklere en esnek olanıdır.


Özel örneğinizle aynı fikirde değilim, ancak farklı anlamlara sahip farklı tiplere sahip olabilecek özel bir referans alma prensibiyle aynı fikirdeyim. Daha iyi bir örnek olarak önerdiğim şey null, a String, ya da a'yı tutabilen bir alan olacaktır String[]. Nesnelerin% 99'unun tam olarak bir dizeye ihtiyacı olacaksa, her dizeyi ayrı olarak inşa edilmiş bir kapsül içine yerleştirmek, String[]yüke önemli miktarda depolama ekleyebilir. Tek dizili durumun doğrudan bir başvuru kullanarak kullanılması Stringdaha fazla kod gerektirir, ancak depolama alanından tasarruf sağlar ve işleri daha hızlı hale getirebilir.
supercat

0

Tip testi bir araçtır, akıllıca kullanın ve güçlü bir müttefik olabilir. Kötü kullanın ve kodunuz kokmaya başlayacaktır.

Yazılımımızda talepler doğrultusunda ağ üzerinden mesajlar aldık. Tüm seri hale getirilmiş mesajlar ortak bir temel sınıfı paylaştı Message.

Sınıfların kendileri çok basittir, sadece yazılan C # özellikleri ve rutinlerini yazdıkları yükler (bunları yazmak ve sıralarını kaldırmak için kullanılamazlardı) (Aslında, sınıf formatlarının çoğunu, mesaj formatının XML tanımından t4 şablonlarını kullanarak oluşturdum)

Kod şöyle bir şey olurdu:

Message response = await PerformSomeRequest(requestParameter);

// Server (out of our control) would send one message as response, but 
// the actual message type is not known ahead of time (it depended on 
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{ 
    // Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
    // Extract results
}
else if (response is AnotherThingHappenedMessage)
{
    // Extract another type of result
}
// Half a dozen other possible checks for messages

Biri, mesaj mimarisinin daha iyi tasarlanabileceğini ancak uzun zaman önce tasarlandığını ve C # için tasarlanmadığını söyleyebiliyordu. Burada tür testleri bizim için çok perişan olmayan bir şekilde gerçek bir sorunu çözdü.

Kayda değer olan, C # 7.0'ın desen eşleştirmesi oluyor (bir çok bakımdan steroidlerde tip testi oluyor) hepsi kötü olamaz ...


0

Genel bir JSON ayrıştırıcısını alın. Başarılı bir ayrıştırmanın sonucu bir dizi, sözlük, dize, sayı, boolean veya boş değerdir. Bunlardan herhangi biri olabilir. Dizinin elemanları veya sözlükteki değerler yine bu tiplerden biri olabilir. Veri programınızın dışından sağlanan olduğundan, herhangi bir sonucu kabul etmek zorunda (yani çökmesini olmadan kabul etmek zorunda olduğu; sen yapabilirsiniz beklediğiniz olmadığını bir sonuç reddetmek).


Peki, bazı JSON serileri gidericiler, yapınız içinde "çıkarım alanları" için bilgi varsa, bir nesne ağacını başlatmaya çalışacaktır. Ama evet. Bunun Karl B'nin cevabının yöneldiği yön olduğunu düşünüyorum.
svidgen
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.