Yedekli özel durumlar Liskov İkame İlkesini ihlal ediyor mu?


20

Diyelim ki FooInterfaceaşağıdaki imzayı içeren bir arayüzüm var:

interface FooInterface {
    public function doSomething(SomethingInterface something);
}

Ve ConcreteFoobu arayüzü uygulayan somut bir sınıf :

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
    }

}

ConcreteFoo::doSomething()Özel bir SomethingInterfacenesne türünden geçtiyse benzersiz bir şey yapmak istiyorum (diyelim ki çağrılıyor SpecialSomething).

Yöntemin önkoşullarını güçlendirirsem veya yeni bir istisna atarsam kesinlikle bir LSP ihlalidir, ancak SpecialSomethinggenel SomethingInterfacenesneler için bir geri dönüş sağlarken nesneleri özel olarak kaplarsam yine de bir LSP ihlali olur mu? Gibi bir şey:

class ConcreteFoo implements FooInterface {

    public function doSomething(SomethingInterface something) {
        if (something instanceof SpecialSomething) {
            // Do SpecialSomething magic
        }
        else {
            // Do generic SomethingInterface magic
        }
    }

}

Yanıtlar:


19

Bu olabilir için sözleşmede başka ne bağlı LSP ihlali anlamına doSomethingyöntemle. Ancak LSP'yi ihlal etmese bile neredeyse kesinlikle bir kod kokusu.

Örneğin, sözleşmesinin bir kısmı geri dönmeden önce en az bir kez doSomethingçağıracağı something.commitUpdates()ve commitSpecialUpdates()bunun yerine özel durum için çağıracağı ise , bu LSP'nin ihlalidir. Bile SpecialSomething'ın commitSpecialUpdates()yöntemi bilinçli olarak aynı şeyler her yapmak için tasarlanmıştır commitUpdates()sadece önleme amaçlı olarak LSP ihlalini etrafında hack ediyor, ve biri sürekli LSP'yi takip eğer tam olarak hackery birinin sıralama yapmak zorunda değildir. Böyle bir şeyin davanız için geçerli olup olmadığı, sözleşmenizi bu yöntem için kontrol ederek (açık veya kapalı olsun) anlamanız gereken bir şeydir.

Bunun bir kod kokusu olmasının nedeni, argümanlarınızdan birinin somut tipini kontrol etmenin, ilk etapta bunun için bir arayüz / soyut tip tanımlama noktasını kaçırması ve prensipte yöntemin bile çalıştığını garanti edememenizdir (hayal edin Birisi çağrılacak SpecialSomethingvarsayımla bir alt sınıf yazarsa commitUpdates()). Her şeyden önce, bu özel güncellemelerin mevcutSomethingInterface; bu mümkün olan en iyi sonuçtur. Bunu yapamayacağınızdan gerçekten eminseniz, arayüzü güncellemeniz gerekir. Arayüzü kontrol etmezseniz, istediğinizi yapan kendi arayüzünüzü yazmayı düşünmeniz gerekebilir. Hepsi için çalışan bir arabirim bile bulamıyorsanız, belki de arabirimi tamamen hurdaya çıkarmalı ve farklı beton türlerini alan birden çok yönteme sahip olmalısınız, ya da belki daha büyük bir refactor uygundur. Bunlardan hangisinin uygun olduğunu söylemek için yorumladığınız sihir hakkında daha fazla bilgi sahibi olmamız gerekir.


Teşekkürler! Bu yardımcı olur. Varsayımsal olarak, doSomething()yöntemin amacı şuna dönüşüm türünü kullanmaktır SpecialSomething: eğer alırsa SpecialSomething, nesneyi değiştirmeden geri döndürürken, genel bir SomethingInterfacenesne alırsa nesneyi dönüştürmek için bir algoritma çalıştırır SpecialSomething. Ön koşullar ve son koşullar aynı kaldığından, sözleşmenin ihlal edildiğine inanmıyorum.
Evan

1
@Evan Oh vay ... bu ilginç bir durum. Bu aslında tamamen problemsiz olabilir. Aklıma gelen tek şey, yeni bir nesne inşa etmek yerine mevcut nesneyi iade ediyorsanız, belki birileri yepyeni bir nesne döndüren bu yönteme bağlıdır ... ama bu tür şeylerin insanları kırabileceği dil. Birinin çağırmak mümkün mü y = doSomething(x)ardından, x.setFoo(3)ve sonra bulmak y.getFoo()döner 3?
Ixrec

Bu, dile bağlı olarak sorunlu olsa da, SpecialSomethingbunun yerine nesnenin bir kopyasını döndürerek kolayca çözülmesi gerekir . Saflık uğruna olsa da, geçen nesne özel durum optimizasyonunun devam ettiğini SpecialSomethingve sadece SomethingInterfacenesne olarak da hesaba katılması gerektiğinden daha büyük dönüşüm algoritmasıyla çalıştırıldığını görebiliyordum .
Evan

1

LSP'nin ihlali değil. Ancak, yine de "tür denetimleri yapma" kuralının ihlalidir. Kodu, özelliğin doğal olarak ortaya çıkacağı şekilde tasarlamak daha iyi olurdu; belki SomethingInterfacebunu başarabilecek başka bir üyeye ihtiyaç duyabilir ya da belki bir yere soyut bir fabrika enjekte etmeniz gerekebilir.

Ancak, bu zor ve hızlı bir kural değildir, bu yüzden ödünleşimin buna değip değmeyeceğine karar vermeniz gerekir. Şu anda bir kod kokunuz ve gelecekteki geliştirmeler için olası bir engeliniz var. Bundan kurtulmak çok daha kıvrımlı bir mimari anlamına gelebilir. Daha fazla bilgi olmadan hangisinin daha iyi olduğunu söyleyemem.


1

Hayır, belirli bir argümanın sadece A arayüzünü değil, A2'yi de LSP'yi ihlal etmediğinden yararlanmak.

Sadece özel yolun daha güçlü ön koşullara sahip olmadığından (almaya karar verirken test edilenlerin dışında) veya daha zayıf post-koşullara sahip olmadığından emin olun.

C ++ şablonları genellikle bunu daha iyi performans sağlamak için yapar, örneğin InputIterators gerektirerek , ancak RandomAccessIterators ile çağrılırsa ek garantiler verir .

Bunun yerine çalışma zamanında karar vermeniz gerekiyorsa (örneğin dinamik döküm kullanarak), tüm potansiyel kazançlarınızı veya daha fazlasını tüketmek için hangi yolu kullanacağınıza karar verin.

Özel durumdan yararlanmak, kodu çoğaltmanız gerekebileceğinden DRY'ye (kendinizi tekrar etmeyin) ve daha karmaşık olduğu için KISS'e (Basit Tutun) gider.


0

"Tip kontrolleri yapma" ve "arayüzlerinizi ayırın" ilkesi arasında bir değiş tokuş vardır. Birçok sınıf, bazı görevleri yerine getirmek için uygulanabilir ancak verimsiz bir araç sağlarsa ve bunlardan birkaçı daha iyi bir araç sunabilir ve görevi gerçekleştirebilecek daha geniş öğe kategorilerinden herhangi birini kabul edebilecek koda ihtiyaç vardır. (belki de verimsiz olarak) ancak daha sonra görevi olabildiğince verimli bir şekilde gerçekleştirin, ya tüm nesnelerin daha verimli yöntemin desteklenip desteklenmediğini söylemek için bir üye içeren bir arabirim uygulamasına gerek duyulursa, diğeri ise onu kullanmalıdır, yoksa başka nesneyi alan kodun genişletilmiş bir arayüzü destekleyip desteklemediğini kontrol etmesini sağlayın ve varsa yayınlayın.

Şahsen, eski yaklaşımı destekliyorum, ancak .NET gibi nesne odaklı çerçevelerin arayüzlerin varsayılan yöntemleri belirtmesine izin vermesini diliyorum (daha büyük arayüzlerle çalışmak daha az acı verici hale getiriyor). Ortak arabirim isteğe bağlı yöntemler içeriyorsa, tek bir sarıcı sınıf, tüketicilere yalnızca orijinal sarılmış nesnede bulunan yetenekleri vaat ederken birçok farklı yetenek kombinasyonuna sahip nesneleri işleyebilir. Birçok işlev farklı arabirimlere ayrılırsa, kaydırılan nesnelerin desteklemesi gerekebilecek her farklı arabirim birleşimi için farklı bir sarma nesnesi gerekecektir.


0

Liskov ikame prensibi, süper tiplerinin bir sözleşmesine uygun olarak hareket eden alt tiplerle ilgilidir. Yani, Ixrec'in yazdığı gibi, bunun bir LSP ihlali olup olmadığını cevaplamak için yeterli bilgi yoktur.

Burada ihlal edilen şey Açık kapalı bir ilkedir. Yeni bir gereksiniminiz varsa - SpecialSomething magic yapın - ve mevcut kodu değiştirmeniz gerekiyorsa, kesinlikle OCP'yi ihlal ediyorsunuz .

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.