Arabirimler gevşek bağlantının elde edilmesinde üst sınıflardan neden daha yararlıdır?


15

( Bu sorunun amacı için, 'arayüz' dediğimde, başka bir deyişle kelimenin bir anlamında bir 'arayüz' değil , dil kurumu kastediyoruminterface , yani bir sınıfın iletişim kurmak için dış dünyaya sunduğu kamusal yöntemler ve manipüle edin. )

Gevşek bağlantı, bir nesnenin beton tipi yerine soyutlamaya bağlı olmasıyla elde edilebilir.

Bu, iki ana nedenden dolayı gevşek bağlantıya izin verir: 1- soyutlamaların beton tiplerinden daha az değişme olasılığı vardır, bu da bağımlı kodun kırılma olasılığının daha düşük olduğu anlamına gelir. 2- farklı beton türleri çalışma zamanında kullanılabilir, çünkü hepsi soyutlamaya uyar. Yeni beton türleri daha sonra mevcut bağımlı kodu değiştirmeye gerek kalmadan eklenebilir.

Örneğin, bir sınıf düşünün Carve iki alt sınıfını Volvove Mazda.

Kodunuz a'ya bağlıysa, çalışma zamanı sırasında Cara Volvoveya a Mazdadeğerini kullanabilir. Ayrıca daha sonra bağımlı kod değiştirmeye gerek kalmadan ilave alt sınıflar eklenebilir.

Ayrıca, Car- ki bu bir soyutlamadır - Volvoveya değişiminden daha az olasıdır Mazda. Arabalar genellikle bir süredir aynıdır, ancak Volvos ve Mazdas'ın değişme olasılığı çok daha yüksektir. Yani soyutlamalar somut tiplerden daha kararlıdır.

Bütün bunlar, gevşek bağlantının ne olduğunu ve betonlara değil soyutlamalara bağlı olarak nasıl elde edildiğini anladığımı göstermekti. (Yanlış bir şey yazdıysam lütfen söyleyin).

Anlamadığım şey şudur:

Soyutlamalar üst sınıflar veya arayüzler olabilir.

Öyleyse, arayüzler neden gevşek bağlantıya izin verdikleri için özellikle övgü alıyor? Üst sınıf kullanmaktan ne kadar farklı olduğunu görmüyorum.

Gördüğüm tek farklar şunlardır: 1- Arayüzler tek mirasla sınırlı değildir, ancak gevşek bağlantı konusuyla ilgisi yoktur. 2- Arayüzler daha 'soyut' çünkü uygulama mantığı yok. Ama yine de, bunun neden bu kadar büyük bir fark yarattığını anlamıyorum.

Lütfen bana basit süper sınıflar olmasa da, arayüzlerin neden gevşek bağlantıya izin vermede harika olduğunu söylediler.


3
“Arabirimleri” olan çoğu dil (ör. Java, C #) yalnızca tek mirası destekler. Her sınıfın yalnızca bir üst üst sınıfı olabileceğinden, (soyut) üst sınıflar, bir nesnenin birden fazla soyutlamayı desteklemesi için çok sınırlıdır. Birden fazla miras ile “ elmas probleminden ” kaçınan modern bir alternatif için özelliklere (örneğin, Scala veya Perl Rolleri ) göz atın .
amon

@amon Yani, gevşek bir bağlantı elde etmeye çalışırken arayüzlerin soyut sınıflara göre avantajlarının tek mirasla sınırlı olmadıklarını mı söylüyorsunuz?
Aviv Cohn

Hayır, derleyici açısından pahalı bir şey demekti, soyut bir sınıfı işlediğinde yapacak daha çok şey var , ama bu muhtemelen ihmal edilebilir.
macar

2
@Amon doğru yolda olduğu gibi görünüyor, ben bu söylendi nerede bu yazı bulundu : interfaces are essential for single-inheritance languages like Java and C# because that's the only way in which you can aggregate different behaviors into a single class(hangi arayüzleri sadece saf sanal fonksiyonları ile sınıflar olan C + +, karşılaştırma leeds).
macar

Lütfen üst sınıfların kimin kötü olduğunu söyleyin.
Tulains Córdova

Yanıtlar:


11

Terminoloji: Dilin yapısına referans alırız interfaceolarak arayüzü ve bir tür ya da nesnesi olarak arayüzüne yüzeyi (daha iyi bir terim eksikliği için).

Gevşek bağlantı, bir nesnenin beton tipi yerine soyutlamaya bağlı olmasıyla elde edilebilir.

Doğru.

Bu, iki ana nedenden dolayı gevşek bağlantıya izin verir: 1 - soyutlamaların beton tiplerinden daha az değişme olasılığı vardır, bu da bağımlı kodun kırılma olasılığının daha düşük olduğu anlamına gelir. 2 - çalışma zamanında farklı beton türleri kullanılabilir, çünkü hepsi soyutlamaya uyar. Yeni beton türleri daha sonra mevcut bağımlı kodu değiştirmeye gerek kalmadan eklenebilir.

Pek doğru değil. Mevcut diller genellikle bir soyutlamanın değişeceğini öngörmemektedir (bununla başa çıkmak için bazı tasarım modelleri olmasına rağmen). Genel şeylerden özelliklerini ayırmak olduğunu soyutlama. Bu genellikle bir soyutlama katmanı ile yapılır . Bu katman, bu soyutlama üzerine inşa edilen kodu kırmadan diğer bazı spesifikasyonlara değiştirilebilir - gevşek bağlantı elde edilir. OOP olmayan örnek: Bir sortrutin, sürüm 1'deki Quicksort'tan sürüm 2'deki Tim Sort'a değiştirilebilir. Bu nedenle, yalnızca sıralanan sonuca (yani sortsoyutlamaya dayanan ) kod gerçek sıralama uygulamasından ayrılır.

Yukarıda yüzey olarak adlandırdığım şey, soyutlamanın genel kısmıdır . Şimdi OOP'ta bir nesnenin bazen birden fazla soyutlamayı desteklemesi gerekir. Oldukça optimal olmayan bir örnek: Java java.util.LinkedList, hem List“sıralı, dizine eklenebilir koleksiyon” soyutlamasıyla ilgili Queuearayüzü destekler ve (kaba terimlerle) “FIFO” soyutlamasıyla ilgili arayüzü destekler .

Bir nesne birden fazla soyutlamayı nasıl destekleyebilir?

C ++ arayüze sahip değildir, ancak çoklu kalıtım, sanal yöntemler ve soyut sınıflara sahiptir. Daha sonra bir soyutlama, sanal yöntemleri tanımlayan, ancak sanal yöntemleri tanımlamayan soyut bir sınıf (yani hemen somutlaştırılamayan bir sınıf) olarak tanımlanabilir. Bir soyutlamanın özelliklerini uygulayan sınıflar, o soyut sınıftan miras alabilir ve gerekli sanal yöntemleri uygulayabilir.

Buradaki sorun, çoklu kalıtım, sınıfların bir yöntem uygulaması (MRO: yöntem çözüm sırası) için aranma sırasının “çelişkilere” yol açabileceği elmas sorununa yol açabilmesidir. Bunun iki yanıtı var:

  1. Aklı başında bir emir tanımlayın ve mantıklı olarak doğrusallaştırılamayan emirleri reddedin. C3 MRO oldukça mantıklı ve iyi çalışıyor. 1996 yılında yayımlandı.

  2. Kolay rotayı takip edin ve birden fazla mirası reddetme

Java ikinci seçeneği aldı ve tek davranışsal miras seçti. Bununla birlikte, bir nesnenin birden fazla soyutlamayı destekleme yeteneğine hala ihtiyacımız var. Bu nedenle, yöntem tanımlarını desteklemeyen, yalnızca bildirimleri destekleyen arabirimler kullanılmalıdır.

Sonuç olarak, MRO açıktır (sırayla her bir üst sınıfa bakın) ve nesnemiz, herhangi bir sayıda soyutlama için birden fazla yüzeye sahip olabilir.

Bu oldukça tatmin edici değildir, çünkü çoğu zaman biraz davranış yüzeyin bir parçasıdır. Bir Comparablearayüz düşünün :

interface Comparable<T> {
    public int cmp(T that);
    public boolean lt(T that);  // less than
    public boolean le(T that);  // less than or equal
    public boolean eq(T that);  // equal
    public boolean ne(T that);  // not equal
    public boolean ge(T that);  // greater than or equal
    public boolean gt(T that);  // greater than
}

Bu çok kullanıcı dostudur (birçok uygun yöntemle güzel bir API), ancak uygulanması sıkıcıdır. Arayüzün cmpdiğer metotları sadece gerekli olan metot açısından otomatik olarak içermesini ve uygulamasını istiyoruz. Mixins , ama daha da önemlisi Özellikler [ 1 ], [ 2 ] bu problemi çoklu miras tuzaklarına düşmeden çözer.

Bu, bir özellik kompozisyonu tanımlanarak yapılır, böylece özellikler aslında MRO'da yer almaz - bunun yerine tanımlanan yöntemler uygulama sınıfında oluşturulur.

ComparableArayüz olarak Scala ifade edilebilir

trait Comparable[T] {
    def cmp(that: T): Int
    def lt(that: T): Boolean = this.cmp(that) <  0
    def le(that: T): Boolean = this.cmp(that) <= 0
    ...
}

Bir sınıf bu özelliği kullandığında, diğer yöntemler sınıf tanımına eklenir:

// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
    override def cmp(that: Inty) = this.x - that.x
    // lt etc. get added automatically
}

Böyle Inty(4) cmp Inty(6)olur -2ve Inty(4) lt Inty(6)olur true.

Birçok dilde bazı özellikler desteklenir ve “Metaobject Protokolü (MOP)” olan herhangi bir dilde ek özellikler bulunabilir. Son Java 8 güncelleştirmesi, özelliklere benzer varsayılan yöntemler ekledi (arabirimlerdeki yöntemlerin geri dönüş uygulamaları olabilir, böylece bu yöntemleri uygulamak için sınıfların uygulanması isteğe bağlıdır).

Ne yazık ki, özellikler oldukça yeni bir buluştur (2002) ve bu nedenle daha büyük ana akım dillerde oldukça nadirdir.


İyi cevap, ancak tek miras dillerinin kompozisyonla arayüzler kullanarak birden fazla mirasa geçebileceğini ekleyeceğim .

4

Anlamadığım şey şudur:

Soyutlamalar üst sınıflar veya arayüzler olabilir.

Öyleyse, arayüzler neden gevşek bağlantıya izin verdikleri için özellikle övgü alıyor? Üst sınıf kullanmaktan ne kadar farklı olduğunu görmüyorum.

Birincisi, alt tipleme ve soyutlama iki farklı şeydir. Alt tipleme, bir tipteki değerleri başka bir tipteki değerlerle değiştirebileceğim anlamına gelir - her iki türün de soyut olması gerekmez.

Daha da önemlisi, alt sınıflar üst sınıflarının uygulama detaylarına doğrudan bağımlıdır. Bu en güçlü bağlantıdır. Aslında, temel sınıf kalıtım göz önünde bulundurularak tasarlanmamışsa, temel sınıfta davranışını değiştirmeyen değişiklikler yine de alt sınıfları kırabilir ve kırılma olursa bir önsel bilmenin yolu yoktur . Bu, kırılgan taban sınıfı sorunu olarak bilinir .

Bir arabirim uygulamak, sizi arabirimin kendisi dışında hiçbir şey içermeyen bir davranışla birleştirmez.


Cevabın için teşekkür ederim. Anladığımı görmek için: A adlı bir nesnenin C adlı soyutlamanın somut bir uygulaması yerine B adlı bir soyutlamaya bağımlı olmasını istediğinizde, B'nin C tarafından uygulanan bir üst sınıf yerine C tarafından uygulanan bir arayüz olması genellikle daha iyidir C. Bunun nedeni: C alt sınıfı B, C'yi B'ye sıkıca bağlar. B değişirse - C değişir. Bununla birlikte C'nin B (B'nin bir arayüz olması) B'yi C'ye bağlamaz: B sadece C'nin uygulanması gereken yöntemlerin bir listesidir, bu nedenle sıkı bağlantı yoktur. Ancak A nesnesine (bağımlı) ilişkin olarak, B'nin bir sınıf mı yoksa arayüz mi olduğu önemli değildir.
Aviv Cohn

Doğru? ..... .....
Aviv Cohn

Neden bir arayüzün herhangi bir şeye bağlı olduğunu düşünüyorsunuz?
Michael Shaw

Bence bu cevap kafasına çivi çakıyor. Ben C ++ biraz kullanın ve diğer cevaplardan birinde belirtildiği gibi, C ++ oldukça arayüzleri yok ama "saf sanal" (yani çocuklar tarafından uygulanan) bırakılan tüm yöntemleri ile üst sınıf kullanarak sahte. Mesele şu ki, yetki verilmiş işlevsellik ile birlikte bir şeyler yapan temel sınıflar yapmak kolaydır. Birçok, birçok, birçok durumda, ben ve arkadaşlarım bunu yaparak yeni bir kullanım durumunun ortaya çıktığını ve bu paylaşılan işlevsellikleri geçersiz kıldığını görüyoruz. Paylaşılan işlevsellik gerekiyorsa, yardımcı bir sınıf yapmak yeterince kolaydır.
J Trana

@Prog Düşünce çizginiz çoğunlukla doğrudur, ancak yine de soyutlama ve alt tipleme iki ayrı şeydir. Dediğinizde you want an object named A to depend on an abstraction named B instead of a concrete implementation of that abstraction named Csize sınıfları nasılsa soyut olmadığını varsayıyoruz. Bir soyutlama, uygulama ayrıntılarını gizleyen herhangi bir şeydir, bu nedenle özel alanlara sahip bir sınıf, aynı genel yöntemlerle bir arayüz kadar soyuttur.
Doval

1

Ebeveyn ve çocuk sınıfları arasında birleşme vardır, çünkü çocuk ebeveyne bağlıdır.

Diyelim ki A sınıfımız var ve B sınıfı ondan miras alıyor. A sınıfına girip bir şeyleri değiştirirsek, B sınıfı da değişir.

Diyelim ki bir arayüzümüz var ve B sınıfı bunu uyguluyor. Arabirim I'yi değiştirirsek, B sınıfı artık uygulayamasa da, B sınıfı değişmez.


Downvoter'ların bir nedeni olup olmadığını veya sadece kötü bir gün geçirip geçirmediklerini merak ediyorum.
Michael Shaw

1
Ben inmedim, ama sanırım ilk cümle ile ilgili olabilir. Çocuk sınıfları, başka yollarla değil, ebeveyn sınıflarıyla birleştirilir. Ebeveynin çocuk hakkında hiçbir şey bilmesine gerek yoktur, ancak çocuğun ebeveyn hakkında samimi bilgiye ihtiyacı vardır.

@JohnGaughan: Geri bildiriminiz için teşekkürler. Netlik için düzenlenmiştir.
Michael Shaw
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.