Bir alt sınıf ile bir alt tip arasındaki fark nedir?


44

Liskov Değişim Prensibi ile ilgili bu soruya verilen en yüksek puanlı cevap, alt tip ile alt sınıf terimlerini ayırt etmek için acı çekmektedir . Ayrıca bazı dillerin ikisini birleştiği, diğerlerinin ise yapmadığı anlamına gelir.

En çok tanıdığım nesne yönelimli diller için (Python, C ++), "type" ve "class" eşanlamlı kavramlardır. C ++ açısından, alt tip ile alt sınıf arasında bir ayrım olması ne anlama gelir? Örneğin, bunun Foobir alt sınıf olduğunu, ancak alt tür olmadığını söyleyin FooBase. Bir fooörneği ise Foo, bu satır olur:

FooBase* fbPoint = &foo;

artık geçerli değil mi?


6
Aslında, Python'da "type" ve "class" farklı kavramlardır. Aslında, Python dinamik olarak yazılmış, "tip" bir kavram değildir hiç Python. Ne yazık ki, Python geliştiricileri bunu anlamıyor ve hala ikisini bir araya getiriyor.
Jörg W Mittag

11
"Tip" ve "sınıf", C ++ 'da da belirgindir. "Arts dizisi" bir türdür; hangi sınıf "int türünde bir değişkene işaretçi" bir türdür; hangi sınıf Bu şeyler herhangi bir sınıf değil, ama kesinlikle bir tür.
Eric Lippert

2
Bu soruyu ve cevabı okuduktan sonra bu şeyi merak ediyordum.
user369450

4
@JorgWMittag Python'da bir "tür" kavramı yoksa, o zaman birileri belgeleri kimin yazdığını söylemelidir: docs.python.org/3/library/stdtypes.html
Mat

Adil olmak için @Matt, türleri oldukça yeni olan 3.5'te üretti, özellikle üretimde kullanabileceğim şeye göre.
Jared Smith,

Yanıtlar:


53

Alt tipleme , alt tipin bir tür ikame edilebilirlik nosyonuyla başka bir veri tipine ( süper tip) ilişkin olan bir veri tipi olduğu bir tür polimorfizmidir; alt tip elemanları üzerinde çalışır.

Eğer Sbir alt tip ise T, alt tip ilişkisi genellikle S <: Therhangi Sbir tip teriminin bir tip terimin Tbeklendiği bir bağlamda güvenle kullanılabileceği anlamına gelir . Alt yazı yazmanın kesin anlamsal önemi, belirli bir programlama dilinde "güvenli bir şekilde kullanılan" anlamındadır.

Alt sınıflama, altyazı ile karıştırılmamalıdır. Genel olarak, alt tiplendirme bir ilişki kurar, alt sınıflama yalnızca uygulamayı yeniden kullanır ve sözdizimsel bir ilişki kurar, zorunlu olarak anlamsal bir ilişki kurmaz (kalıtım davranış alt tipini garanti etmez).

Bu kavramları ayırt etmek için alt tipleme aynı zamanda arayüz kalıtım olarak bilinirken, alt sınıflama uygulama kalıtım veya kod kalıtım olarak bilinir .

Referanslar
Alt Tip
Kalıtım


1
Çok güzel söyledi. Soru bağlamında, C ++ programcılarının alt tip ilişkilerini tip sistemine iletmek için çoğu zaman saf sanal taban sınıfları kullandıkları söylenebilir. Genel programlama yaklaşımları elbette sıklıkla tercih edilir.
Aluan Haddad,

6
"Alt yazı yazmanın kesin semantiği," belirli bir programlama dilinde "anlamına gelen bir bağlamda güvenle kullanılanın özelliklerine bağlıdır. … Ve LSP “güvenli” nin ne anlama geldiğine dair makul bir fikir tanımlamaktadır ve bu “güvenlik” biçimini sağlamak için bu özelliklerin hangi kısıtlamaları yerine getirmesi gerektiğini bize söylemektedir.
Jörg W Mittag

Yığın üzerinde bir örnek daha: eğer doğru anladıysam, C ++ 'da publickalıtım bir alt tip sunarken, privatekalıtım bir alt sınıf verir.
Quentin

@ Quentin halk mirası hem bir alt sınıf hem de bir alt sınıf olmakla birlikte, henüz özel olan sadece bir alt sınıf olmakla birlikte bir alt sınıf değildir. Java arayüzleri gibi yapılarla alt sınıflandırma yapmadan alt yazım olabilir
saat

27

Burada bahsettiğimiz bağlamda bir tür , temel olarak bir takım davranış garantileridir. Bir sözleşme yapacaksın. Veya bir protokol olan Smalltalk'tan ödünç terminoloji .

Bir sınıf , bir yöntem demetidir. Bir dizi davranış uygulamasıdır .

Subtyping protokolü rafine bir araçtır. Alt sınıflandırma , yeniden kullanım için bir diferansiyel kod aracıdır, yani yalnızca davranıştaki farkı tanımlayarak kodu yeniden kullanmaktır.

Java veya C♯ kullandıysanız, tüm türlerin tür olması gerektiği tavsiyesine rastlamış olabilirsiniz interface. Aslında, eğer William Cook'un Veri Soyutlamasını Anlama Üzerine, Yeniden Görülmüş'ü okursanız , OO'ları bu dillerde yapmak için, yalnızca s türlerini kullanmanız gerektiğini biliyor olabilirsiniz interface. (Ayrıca, eğlenceli gerçek: Java, bunları interfacedoğrudan Smalltalk'ten alınan doğrudan Objective-C'nin protokollerinden s.

Şimdi, bu kodlama tavsiyesini mantıklı bir sonuca kadar takip edersek ve yalnızca interface türlerin, sınıfların ve ilkellerin kaldığı bir Java sürümünü hayal edersek , o zaman bir interfacebaşkasından miras alan biri, bir altyazı ilişkisi classyaratacaktır sadece diferansiyel kod yeniden kullanımı için olabilir super.

Bildiğim kadarıyla, kod devralma (uygulama devralma / alt sınıflama) ve devralma sözleşmeleri (alt türleme) arasında kesin bir ayrım yapan, genel olarak yazılmış statik diller yok . Java ve C♯ yılında anayüz saf alttiplendirmesinde olduğu (ya da en azından sıra Java 8'de varsayılan yöntemlerin tanıtılması ve muhtemel C♯ 8 kadar oldu), ancak sınıf miras olduğunu da uygulama miras yanı sıra alttiplendirmesinde. Karışımlar ( davranış içeren ), yapılar (durum içeren), arayüzler ( tanımlayanlar ) arasında kesin bir ayrım yapan deneysel statik olarak yazılmış nesne yönelimli bir LISP lehçesi hakkında okumayı hatırlıyorumdavranış) ve sınıflar (bir veya daha fazla karışımla sıfır veya daha fazla yapı oluşturan ve bir veya daha fazla arabirime uyan). Yalnızca sınıflar başlatılabilir ve yalnızca arayüzler tür olarak kullanılabilir.

Python, Ruby, ECMAScript veya Smalltalk gibi dinamik olarak yazılmış bir OO dilinde, genellikle bir nesnenin türlerini uygun olduğu protokoller kümesi olarak düşünürüz. Çoğulluğa dikkat edin: bir nesnenin birden fazla türü olabilir ve yalnızca her tür nesnenin Stringaynı zamanda bir tür nesnesi olduğu gerçeğinden bahsetmiyorum Object. (BTW: tür isimleri hakkında konuşmak için sınıf isimlerini nasıl kullandığımı not edin. Ne kadar da aptalım!) Bir nesne çoklu protokolleri uygulayabilir. Örneğin, Ruby'de Arrayseklenebilir, dizine eklenebilir, yinelenebilir ve karşılaştırılabilir. Bu uyguladıkları dört farklı protokol!

Şimdi, Ruby'nin tipleri yok. Ancak Ruby topluluğunun türleri var! Ancak, yalnızca programcıların başkanlarında varlar. Ve belgelerde. Örneğin, eachelemanlarını tek tek vererek adlandırılan bir yönteme cevap veren herhangi bir nesne, numaralandırılabilir bir nesne olarak kabul edilir . Ve adı verilen bir mixin var bağlıdır bu protokole. Nesneniz doğru olup olmadığını Yani, türünü (sadece programcının kafasında var), o zaman (dan inherit) karıştırmak için izin mixin ve iyi, ücretsiz benzerleri için serin yöntemlerinin her türlü olsun , , ve bu yüzden üzerinde.EnumerableEnumerablemapreducefilter

Bir nesne yanıt Aynı şekilde, <=>o zaman uygulamak düşünülen karşılaştırılabilir protokolü ve içinde karıştırıp Comparablemixin ve benzeri şeyler olsun <, <=, >, <=, ==, between?, ve clampücretsiz. Bununla birlikte, bu yöntemlerin hepsini de uygulayabilir ve hepsinden miras kalmaz Comparableve yine de karşılaştırılabilir olarak kabul edilir .

İyi bir örnek, StringIOesas olarak I / O akımlarını dizelerle taklit eden kütüphanedir . Sınıfla aynı yöntemleri uygular IO, ancak ikisi arasında kalıtım ilişkisi yoktur. Bununla birlikte, bir StringIOkullanılabilecek her yerde IOkullanılabilir. Programınızda daha fazla değişiklik yapmak zorunda kalmadan, bir dosyayı değiştirebileceğiniz veya stdinile değiştirebileceğiniz birim testlerinde çok yararlıdır StringIO. Yana StringIOaynı protokole uygun olup IO, bunlar farklı sınıflar olmasına rağmen, aynı türden ikisi, ve (her ikisi de uzatmak o önemsiz dışında hiçbir ilişkisi paylaşması Objectnoktada).


Dillerin, programların aynı anda bir sınıf türü ve o sınıfın bir uygulama olduğu bir arabirim olduğunu bildirmesine izin vermesi ve ayrıca uygulamaların "yapıcılar" (arabirim tarafından belirtilen sınıf kurucularını zincirleyeceğini) belirtmesine izin vermesi yararlı olabilir. Referansların herkese açık olarak paylaşılacağı nesne türleri için tercih edilen model, sınıf türünün yalnızca türetilmiş sınıfları oluştururken kullanılmasıdır; çoğu referans arayüz tipinde olmalıdır. Arabirim oluşturucuları belirtebilmek, aşağıdaki durumlarda yardımcı olabilir ...
supercat,

... örneğin kodun, belirli bir değer kümesinin indeks tarafından okunmasına izin verecek bir koleksiyona ihtiyacı vardır, ancak ne tür olduğu ile ilgilenmez. Sınıfları ve arayüzleri farklı tür tipleri olarak tanımanın sağlam nedenleri olsa da, şu anda izin verilen dillerden daha yakın çalışabilmeleri gereken birçok durum vardır.
Supercat

Bahsettiğiniz deneysel LISP lehçesi hakkında karışımları, yapıları, arayüzleri ve sınıfları resmi olarak farklılaştıran daha fazla bilgi arayabileceğim bir referans veya anahtar kelimeleriniz var mı?
tel

@tel: Hayır, özür dilerim. Muhtemelen 15-20 yıl önceydi ve o zaman çıkarlarım her yerdeydi. Bunun karşısında tökezlerken aradığımı sana söylemeye başlayamadım.
Jörg W Mittag

Awww. Tüm bu cevapların en ilginç detayı buydu. Bu kavramların resmi bir şekilde ayrılmasının aslında bir dilin uygulanmasında mümkün olduğu gerçeği, benim için sınıf / tip ayrımını kristalleştirmeye gerçekten yardımcı oldu. Her halükarda, bu LISP'i kendim aramaya giderim. Bir dergi makalesinde / kitabında okuyup okuduğunu ya da konuşmasında yeni duyduğunu hatırlıyor musun?
tel

2

Muhtemelen ilk önce bir tip ile bir sınıf arasında ayrım yapmak ve sonra alt tipleme ile alt sınıflama arasındaki farka dalmak yararlıdır.

Bu cevabın geri kalanı için, tartışmaya konu olan tiplerin statik tipler olduğunu kabul edeceğim (alt tipleme genellikle statik bir bağlamda ortaya çıktığından).

Bir tür ve sınıf arasındaki farkı göstermek için bir oyuncak sözde kod geliştireceğim çünkü çoğu dil onları en azından kısmen kapsıyor (kısaca değinmek için iyi bir nedenle).

Bir tür ile başlayalım. Tür, kodunuzdaki bir ifade için bir etikettir. Bu etiketin değeri ve diğer etiketlerin değerleriyle tutarlı olup olmadığına (belirli bir sisteme özgü tutarlılık türü için), programınızı çalıştırmadan harici bir programla (typechecker) belirlenebilir. Bu etiketleri özel kılan ve kendi adlarını hak eden şey budur.

Oyuncak dilimizde bunun gibi etiketlerin oluşturulmasına izin verebiliriz.

declare type Int
declare type String

O zaman çeşitli değerleri bu tip olarak etiketleyebiliriz.

0 is of type Int
1 is of type Int
-1 is of type Int
...

"" is of type String
"a" is of type String
"b" is of type String
...

Bu ifadelerle, yazım denetleyicimiz şimdi

0 is of type String

tür sistemimizin gereksinimlerinden biri, her ifadenin benzersiz bir türünün olmasıdır.

Şimdi bunun ne kadar beceriksiz olduğunu ve sınırsız sayıda ifade türü atama konusunda nasıl sorun yaşayacağınızı bir kenara bırakalım. Daha sonra geri dönebiliriz.

Öte yandan, bir sınıf birlikte gruplandırılmış bir yöntem ve alan koleksiyonudur (potansiyel olarak özel veya genel gibi erişim değiştiricilerle).

class StringClass:
  defMethod concatenate(otherString): ...
  defField size: ...

Bu sınıfın bir örneği, bu yöntem ve alanların önceden tanımlanmış tanımlarını oluşturma veya kullanma yeteneğini kazanır.

Bir sınıfı, bir sınıfın her örneğinin otomatik olarak bu türle etiketleneceği bir türle ilişkilendirmeyi seçebiliriz.

associate StringClass with String

Ancak her türün bir ilişkili sınıfa sahip olması gerekmez.

# Hmm... Doesn't look like there's a class for Int

Ayrıca oyuncak dilimizde her sınıfın bir türünün olmadığı, özellikle de tüm ifadelerimizin türünün olmadığı düşünülebilir. Bazı ifadelerin türleri olsaydı ve bazıları olmasa, ne tür sistem tutarlılık kurallarının görüneceğini hayal etmek biraz daha zor (ama imkansız değil).

Dahası, oyuncak dilimizde bu birlikteliklerin benzersiz olması gerekmez. İki sınıfı aynı tipte ilişkilendirebiliriz.

associate MyCustomStringClass with String

Şimdi yazım denetleyicimizin bir ifadenin değerini izlemesine gerek olmadığını unutmayın (ve çoğu durumda bunu yapmaz veya yapması imkansızdır). Tek bildiği, söylediğiniz etiketler. Daha önce hatırlatmak gerekirse, typechecker ifadelerin yalnızca 0 is of type Stringyapay olarak yaratılan tür kuralımız nedeniyle ifadeleri reddedebildi; ifadelerin benzersiz türlere sahip olması gerektiğini ve ifadeyi zaten 0başka bir şey olarak etiketledik . Bunun değeri hakkında hiçbir özel bilgiye sahip değildi 0.

Peki ya altyazı? Altyazı, yazım denetlemede sahip olabileceğiniz diğer kuralları gevşeten ortak bir kuralın adıdır. Yani A is subtype of B, bilgisayar tamircinizin etiketini talep ettiği her yerde B, o da kabul edecektir A.

Örneğin, numaralarımız için daha önce sahip olduklarımız yerine aşağıdakileri yapabiliriz.

declare type NaturalNum
declare type Int
NaturalNum is subtype of Int

0 is of type NaturalNum
1 is of type NaturalNum
-1 is of type Int
...

Alt sınıflama, daha önce bildirilmiş yöntemleri ve alanları yeniden kullanmanıza izin veren yeni bir sınıfı bildirmek için kullanılan bir yoldur.

class ExtendedStringClass is subclass of StringClass:
  # We get concatenate and size for free!
  def addQuestionMark: ...

Biz ortak örneklerini gerekmez ExtendedStringClassile Stringörneğinde yaptığımız gibi StringClassbu yepyeni sınıf var sonuçta beri, biz sadece kadar yazma yoktu. Bu , yazım denetleyicisinin bakış ExtendedStringClassaçısından uyuşmayan bir tür vermemize izin verir String.

Aynı şekilde tamamen yeni bir sınıf oluşturmaya karar verdik NewClassve

associate NewClass with String

Artık her örnek , denetleyicinin bakış açısından StringClassikame edilebilir NewClass.

Dolayısıyla teoride alt yazma ve alt sınıflama tamamen farklı şeylerdir. Ama bildiğim hiçbir dilde bu tür ve sınıfları yok aslında bu şekilde işler. Dilimizi parçalamaya başlayalım ve bazı kararlarımızın arkasındaki mantığı açıklayalım.

İlk olarak, teoride tamamen farklı sınıflara aynı tip verilebilse veya bir sınıfa herhangi bir sınıfın örneği olmayan değerlerle aynı türde verilebilse de, bu, typechecker'ın yararını ciddi şekilde engeller. Tipechecker, bir ifade içinde çağırdığınız yöntem veya alanın gerçekten de bu değerde olup olmadığını kontrol etme yeteneğinden etkin bir şekilde etkilenir; bu, muhtemelen bir typechecker. Ne de olsa, bu Stringetiketin altındaki değerin kim olduğunu kim bilir ; hiç bir concatenatemetoda sahip olmayan bir şey olabilir, örneğin hiç bir yöntem!

Tamam, öyleyse her sınıfın otomatik olarak aynı sınıfla aynı adda yeni bir tür üreteceğini ve associatebu türdeki örnekleri olduğunu belirtelim. Bu associate, StringClassve arasındaki farklı isimler kadar iyi kurtulmamızı sağlar String.

Aynı sebepten dolayı, muhtemelen birinin diğerinin alt sınıfı olduğu iki sınıf türü arasında otomatik olarak bir alt tür ilişkisi kurmak istiyoruz. Tüm alt sınıfların, üst sınıfın yaptığı tüm yöntem ve alanlara sahip olması garanti edildikten sonra, bunun tersi doğru değildir. Bu nedenle, alt sınıf bir tür üst sınıfa ihtiyacınız olduğunda geçebilse de, alt sınıfın türüne ihtiyacınız varsa üst sınıfın türü reddedilmelidir.

Bunu, kullanıcı tarafından tanımlanan tüm değerlerin bir sınıfın örneği olması şartı ile birleştirirseniz, is subclass ofiki katı görev alabilir ve kurtulabilirsiniz is subtype of.

Bu da bizi popüler statik olarak yazılmış OO dillerinin paylaştığı özelliklere götürüyor. Herhangi bir sınıfla ilişkili olmayan ve kullanıcı tanımlı olmayan bir dizi "ilkel" tip (ör int. float, Vb.) Vardır . Daha sonra, otomatik olarak aynı isim tipine sahip ve alt tipleme ile alt sınıflamayı tanımlayan tüm kullanıcı tanımlı sınıflara sahipsiniz.

Yapacağım son not, değerlerin ayrı ayrı ilan edilmesi konusundaki sıkışıklığı etrafında. Çoğu dil, ikisinin oluşturulmasını sınırlar; böylece bir tür bildirimi, aynı zamanda otomatik olarak bu türle etiketlenen tamamen yeni değerler üretme bildirimidir. Örneğin, bir sınıf bildirimi tipik olarak hem türü hem de bu tür değerlerin somutlaştırılmasının bir yolunu yaratır. Bu, bazı tıkanıklıklardan kurtulur ve yapıcıların varlığında, aynı zamanda bir vuruşta tür içeren sonsuz sayıda değer etiketi oluşturmanıza da olanak sağlar.

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.