Liskov İkame İlkesinin bir örneği nedir?


Yanıtlar:


892

LSP'yi (yakın zamanda duyduğum bir podcast'te Bob Amca tarafından verilen) gösteren harika bir örnek, doğal dilde doğru görünen bir şeyin bazen kodda tam olarak çalışmadığıydı.

Matematikte Squarea Rectangle. Gerçekten de bir dikdörtgenin uzmanlaşmasıdır. "İs", bunu kalıtımla modellenmek istemenizi sağlar. Ancak yaptığınız kodda Squaretüretilmişse Rectangle, o zaman a Square, beklediğiniz her yerde kullanılabilir olmalıdır Rectangle. Bu biraz garip davranışlar yaratır.

Temel sınıfınızda olduğunu SetWidthve SetHeightyöntemlerini hayal edin Rectangle; bu tamamen mantıklı görünüyor. Ancak Rectanglereferansınız bir a işaret ediyorsa ve bu bir anlam ifade etmiyorsa Square, birini ayarlamak diğerini eşleştirmek için değiştirecektir. Bu durumda Liskov İkame Testi ile başarısız olur ve miras almanın soyutlanması kötüdür.SetWidthSetHeightSquareRectangleSquareRectangle

resim açıklamasını buraya girin

Hepiniz diğer paha biçilmez SOLID Principles Motivational Posterlerini kontrol etmelisiniz .


19
@ m-sharp SetWidth ve SetHeight yerine GetWidth ve GetHeight yöntemlerine sahip olacak şekilde değişmez bir Dikdörtgen ise ne olur?
Pacerier

139
Hikayenin ahlakı: sınıflarınızı mülklere göre olmayan davranışlara göre modelleyin; verilerinizi davranışlara değil özelliklere göre modelleyin. Ördek gibi davranıyorsa, kesinlikle bir kuştur.
Sklivvz

193
Bir kare, gerçek dünyada açıkça bir dikdörtgen biçimidir. Bunu kodumuzda modelleyip modelleyemeyeceğimiz teknik özelliklere bağlıdır. LSP'nin belirttiği, alt tür davranışının, taban türü belirtiminde tanımlandığı gibi temel tür davranışıyla eşleşmesi gerektiğidir. Dikdörtgen taban türü belirtimi, yükseklik ve genişliğin bağımsız olarak ayarlanabileceğini söylüyorsa, LSP karenin bir dikdörtgen alt türü olamayacağını söylüyor. Dikdörtgen spesifikasyonu bir dikdörtgenin değişmez olduğunu söylüyorsa, kare dikdörtgenin bir alt türü olabilir. Temel tür için belirtilen davranışı sürdüren alt türlerle ilgilidir.
SteveT

63
@Pacerier değişmezse sorun yok. Burada asıl mesele, dikdörtgenleri modellemiyoruz, aksine "yeniden şekillendirilebilir dikdörtgenler", yani genişlik veya yüksekliği oluşturma işleminden sonra değiştirilebilen dikdörtgenler (ve yine de aynı nesne olduğunu düşünüyoruz). Dikdörtgen sınıfına bu şekilde bakarsak, bir karenin "yeniden şekillendirilebilir bir dikdörtgen" olmadığı açıktır, çünkü bir kare yeniden şekillendirilemez ve hala bir kare (genel olarak) olamaz. Matematiksel olarak, sorunu görmüyoruz çünkü değişebilirlik matematiksel bir bağlamda anlam ifade etmiyor.
Ocak'ta asmeurer

14
Prensip hakkında bir sorum var. Eğer Square.setWidth(int width)böyle uygulanmış olsaydı neden sorun olurdu this.width = width; this.height = width;? Bu durumda, genişliğin yüksekliğe eşit olduğu garanti edilir.
MC İmparatoru

488

Liskov İkame İlkesi (LSP, ), Nesneye Yönelik Programlamada şunları ifade eden bir kavramdır:

İşaretçiler veya temel sınıflara başvurular kullanan işlevler, türetilmiş sınıfların nesnelerini bilmeden kullanabilmelidir.

Kalbinde LSP, arayüzler ve sözleşmeler ile bir dersi ne zaman genişleteceğinize ve hedefinize ulaşmak için kompozisyon gibi başka bir stratejiyi nasıl kullanacağınıza nasıl karar vereceğinizle ilgilidir.

Bu noktayı açıklamanın en etkili yolu Baş Önce OOA & D idi . Strateji oyunları için bir çerçeve oluşturmak için bir projede geliştirici olduğunuz bir senaryo sunarlar.

Şuna benzeyen bir tahtayı temsil eden bir sınıf sunarlar:

Sınıf diyagramı

Tüm yöntemler, X ve Y koordinatlarını iki boyutlu dizideki döşeme konumunu bulmak için parametre olarak alır Tiles. Bu, bir oyun geliştiricisinin oyun sırasında tahtadaki birimleri yönetmesine izin verecektir.

Kitap, oyun çerçevesi çalışmasının, uçuşu olan oyunları barındırmak için 3D oyun tahtalarını da desteklemesi gerektiğini söylemek için gereksinimleri değiştirmeye devam ediyor. Böylece genişleyen bir ThreeDBoardsınıf tanıtıldı Board.

İlk bakışta bu iyi bir karar gibi görünüyor. Boardhem Heightve Widthözelliklerini hem de ThreeDBoardZ eksenini sağlar.

Ayrıldığı yer, miras alınan diğer tüm üyelere baktığınız zamandır Board. Yöntemleri AddUnit, GetTile, GetUnitsve benzeri, tüm X ve Y parametreleri hem almak Boardsınıfta ama ThreeDBoardsıra Z parametresini ihtiyacı var.

Bu yüzden bu yöntemleri bir Z parametresiyle tekrar uygulamalısınız. Z parametresinin Boardsınıfa bağlamı yoktur ve sınıftan devralınan yöntemler Boardanlamlarını yitirir. ThreeDBoardSınıfı temel sınıfı olarak kullanmaya çalışan bir kod birimi Boardçok şanssız olurdu.

Belki başka bir yaklaşım bulmalıyız. Bunun yerine uzanan Board, ThreeDBoardoluşan gereken Boardnesneler. BoardZ ekseninin birimi başına bir nesne.

Bu, kapsülleme ve yeniden kullanma gibi iyi nesne yönelimli ilkeleri kullanmamızı sağlar ve LSP'yi ihlal etmez.


10
Benzer ancak daha basit bir örnek için Wikipedia'daki Dairesel Elips Sorunu'na bakın .
Brian

@NotMySelf'den gelen istek: "Örnek, bence yönetim kurulundan miras almanın ThreeDBoard bağlamında anlamlı olmadığını ve tüm yöntem imzalarının bir Z ekseni ile anlamsız olduğunu göstermektir."
Contango

1
Bir Child sınıfına başka bir yöntem eklersek, ancak Parent'in tüm işlevselliği Child sınıfında hala mantıklıysa LSP'yi kırmak mı gerekir? Bir yandan Çocuğu bir Ebeveyn olarak yayınlarsak Çocuğu biraz kullanmak için arayüzü değiştirdik çünkü bir Ebeveynin kodunun iyi çalışmasını beklediği kodu.
Nickolay Kondratyev

5
Bu bir anti-Liskov örneğidir. Liskov, Kare'den Dikdörtgen elde etmemizi sağlıyor. Daha az parametre sınıfından daha fazla parametre sınıfı. Ve kötü olduğunu güzel gösterdin. Bir cevap olarak işaretlenmiş ve liskov sorusu için bir anti-liskov cevabının 200 katına çıkmış olması gerçekten iyi bir şaka. Liskov prensibi gerçekten yanlış mıdır?
Gangnus

3
Miras işinin yanlış olduğunu gördüm. İşte bir örnek. Temel sınıf 3DBoard ve türetilmiş sınıf Board olmalıdır. Kurulun Z ekseni Maks (Z) = Min (Z) = 1
Paulustrious

169

Değiştirilebilirlik, nesne yönelimli programlamada, bir bilgisayar programında, S, T'nin bir alt tipi ise, T tipi nesnelerin S tipi nesnelerle değiştirilebileceğini belirten bir prensiptir.

Java ile basit bir örnek yapalım:

Kötü örnek

public class Bird{
    public void fly(){}
}
public class Duck extends Bird{}

Ördek bir kuş olduğu için uçabilir, ama buna ne dersiniz:

public class Ostrich extends Bird{}

Devekuşu bir kuştur, ancak uçamaz, Devekuşu sınıfı Kuş sınıfının bir alt türüdür, ancak sinek yöntemini kullanamaz, bu da LSP ilkesini ihlal ettiğimiz anlamına gelir.

İyi örnek

public class Bird{
}
public class FlyingBirds extends Bird{
    public void fly(){}
}
public class Duck extends FlyingBirds{}
public class Ostrich extends Bird{} 

3
Güzel bir örnek, ama müşteri varsa ne yapardınız Bird bird. Sinek kullanmak için nesneyi FlyingBirds'a atmanız gerekiyor, ki bu hoş değil mi?
Moody

17
Hayır. İstemci varsa Bird bird, bu kullanılamaz demektir fly(). Bu kadar. A'yı geçmek Duckbu gerçeği değiştirmez. Müşteri varsa FlyingBirds bird, o zaman geçse bile Duckher zaman aynı şekilde çalışmalıdır.
Steve Chamaillard

9
Bu ayrıca Arayüz Ayrımı için iyi bir örnek teşkil etmez mi?
Saharsh

Mükemmel bir örnek Teşekkürler Adam
Abdelhadi Abdo

6
'Flyable' Arayüzünü kullanmaya ne dersiniz (daha iyi bir isim düşünemiyorum). Bu şekilde kendimizi bu katı hiyerarşiye adamayız .. Gerçekten bilmediğimiz sürece.
Thirdy

132

LSP değişmezlerle ilgilidir.

Klasik örnek aşağıdaki sahte kod bildirimi ile verilmiştir (uygulamalar atlanmıştır):

class Rectangle {
    int getHeight()
    void setHeight(int value)
    int getWidth()
    void setWidth(int value)
}

class Square : Rectangle { }

Arayüz eşleşmesine rağmen şimdi bir sorunumuz var. Bunun nedeni, karelerin ve dikdörtgenlerin matematiksel tanımından kaynaklanan değişmezleri ihlal etmemizdir. Alıcıların ve ayarlayıcıların çalışma şekli, a Rectangleaşağıdaki değişmezi karşılamalıdır:

void invariant(Rectangle r) {
    r.setHeight(200)
    r.setWidth(100)
    assert(r.getHeight() == 200 and r.getWidth() == 100)
}

Bununla birlikte, bu değişmez , doğru bir şekilde uygulanmasıyla ihlal edilmelidirSquare , bu nedenle geçerli bir yerine geçmez Rectangle.


35
Dolayısıyla, aslında modellemek isteyebileceğimiz her şeyi modellemek için "OO" kullanmanın zorluğu.
DrPizza

9
@DrPizza: Kesinlikle. Ancak, iki şey. Birincisi, bu tür ilişkiler, eksik olsa da veya daha karmaşık yollar kullanarak (probleminize hangisini seçerse seçin) yine de OOP'da modellenebilir. İkincisi, daha iyi bir alternatif yok. Diğer eşlemeler / modellemeler aynı veya benzer sorunlara sahiptir. ;-)
Konrad Rudolph

7
@NickW Bazı durumlarda (ancak yukarıda değil) miras zincirini tersine çevirebilirsiniz - mantıksal olarak konuşursak, bir 2D nokta - üçüncü boyutun göz ardı edildiği (veya 0 - tüm noktaların aynı düzlemde olduğu) 3B boşluk). Ama bu elbette pek pratik değil. Genel olarak, bu mirasın gerçekten yardım etmediği ve varlıklar arasında doğal bir ilişkinin olmadığı vakalardan biridir. Onları ayrı ayrı modelleyin (en azından daha iyi bir yol bilmiyorum).
Konrad Rudolph

7
OOP veriyi değil davranışları modellemek içindir. Sınıflarınız LSP'yi ihlal etmeden önce bile kapsüllemeyi ihlal ediyor.
Sklivvz

2
@AustinWBryan Yep; bu alanda ne kadar uzun süre çalışıyorsam, kalıtımları yalnızca arabirimler ve soyut temel sınıflar için ve daha çok kompozisyon için mi kullanma eğilimindeyim. Bazen biraz daha fazla iş (akıllıca yazarak) ama bir sürü problemden kaçınır ve diğer deneyimli programcılar tarafından tavsiye edilir.
Konrad Rudolph

77

Robert Martin'in Liskov İkame Prensibi ile ilgili mükemmel bir makalesi var . İlkenin ihlal edilebileceği ince ve çok ince olmayan yolları tartışır.

Makalenin bazı ilgili bölümleri (ikinci örneğin yoğun bir şekilde yoğunlaştığına dikkat edin):

LSP İhlaline Basit Bir Örnek

Bu ilkenin en göze çarpan ihlallerinden biri, bir nesnenin türüne dayalı bir işlev seçmek için C ++ Çalışma Zamanı Türü Bilgilerinin (RTTI) kullanılmasıdır. yani:

void DrawShape(const Shape& s)
{
  if (typeid(s) == typeid(Square))
    DrawSquare(static_cast<Square&>(s)); 
  else if (typeid(s) == typeid(Circle))
    DrawCircle(static_cast<Circle&>(s));
}

Açıkçası DrawShapeişlev kötü bir şekilde oluşturulmuştur. ShapeSınıfın her olası türevini bilmeli ve yeni türevleri Shapeher oluşturulduğunda değiştirilmelidir. Gerçekten de birçoğu bu işlevin yapısını Nesneye Dayalı Tasarıma anathema olarak görmektedir.

Kare ve Dikdörtgen, Daha İnce Bir İhlal.

Bununla birlikte, LSP'yi ihlal etmenin başka, çok daha ince yolları vardır. RectangleSınıfı aşağıda açıklandığı gibi kullanan bir uygulamayı düşünün :

class Rectangle
{
  public:
    void SetWidth(double w) {itsWidth=w;}
    void SetHeight(double h) {itsHeight=w;}
    double GetHeight() const {return itsHeight;}
    double GetWidth() const {return itsWidth;}
  private:
    double itsWidth;
    double itsHeight;
};

[...] Bir gün kullanıcıların dikdörtgenlere ek olarak kareleri manipüle etme yeteneğini talep ettiğini düşünün. [...]

Açıkçası, kare tüm normal niyet ve amaçlar için bir dikdörtgendir. ISA ilişkisi devam ettiğinden, Square sınıfı türetilmiş olarak modellemek mantıklıdır Rectangle. [...]

SquareSetWidthve SetHeightfonksiyonlarını devralır . Bu işlevler a için tamamen uygun değildir Square, çünkü bir karenin genişliği ve yüksekliği aynıdır. Bu, tasarımla ilgili bir sorun olduğu konusunda önemli bir ipucu olmalıdır. Ancak, sorunu ortadan kaldırmanın bir yolu var. Biz geçersiz kılabilir SetWidthve SetHeight[...]

Ancak aşağıdaki işlevi göz önünde bulundurun:

void f(Rectangle& r)
{
  r.SetWidth(32); // calls Rectangle::SetWidth
}

SquareBu işleve bir nesneye başvuru Squareiletirsek, yükseklik değişmeyeceğinden nesne bozulur. Bu LSP'nin açık bir ihlalidir. İşlev, bağımsız değişkenlerinin türevleri için çalışmaz.

[...]


14
Çok geç, ama bu makalede ilginç bir alıntı olduğunu düşündüm: Now the rule for the preconditions and postconditions for derivatives, as stated by Meyer is: ...when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one. Bir çocuk sınıfı ön koşulu, ebeveyn sınıfı ön koşulundan daha güçlü ise, ön koşulu ihlal etmeden bir çocuğu ebeveyninin yerine koyamazsınız. Dolayısıyla LSP.
user2023861

@ user2023861 Çok haklısın. Buna dayanarak bir cevap yazacağım.
inf3rno

40

LSP, bazı kodların bir türün yöntemlerini çağırdığını düşündüğü durumlarda gereklidir Tve bilmeden bir türün yöntemlerini çağırabilir ( Sburada mirastan türetir veya bir alt tiptir ).S extends TST

Örneğin, bu tür bir girdi parametresine sahip bir işleve tür Tbağımsız değişken değeriyle çağrıldığında (yani çağrıldığında) oluşur S. Veya, bir tür tanımlayıcısına bir tür Tdeğeri atanır S.

val id : T = new S() // id thinks it's a T, but is a S

LSP, tip T(örn. Rectangle) Yöntemleri için beklentileri (yani değişmezler) gerektirir, bunun yerine tip S(ör. Square) Yöntemleri çağrıldığında ihlal edilmez .

val rect : Rectangle = new Square(5) // thinks it's a Rectangle, but is a Square
val rect2 : Rectangle = rect.setWidth(10) // height is 10, LSP violation

Değişmez alanları olan bir türün bile değişmezleri vardır, örneğin değişmez Dikdörtgen ayarlayıcılar boyutların bağımsız olarak değiştirilmesini bekler, ancak değişmez Kare ayarlayıcılar bu beklentiyi ihlal eder.

class Rectangle( val width : Int, val height : Int )
{
   def setWidth( w : Int ) = new Rectangle(w, height)
   def setHeight( h : Int ) = new Rectangle(width, h)
}

class Square( val side : Int ) extends Rectangle(side, side)
{
   override def setWidth( s : Int ) = new Square(s)
   override def setHeight( s : Int ) = new Square(s)
}

LSP, alt tipin her yönteminin Skontravaryant giriş parametrelerine ve bir kovaryant çıkışa sahip olmasını gerektirir.

Varyans kalıtım yönüne karşı olan kontravaryant aracı tipi, yani Si, alt-tipinin her yöntemin her bir giriş parametresi S, aynı veya olmalıdır süper tip Çeşidi Tisüper tip karşılık gelen yönteminin karşılık gelen giriş parametresinin T.

Kovaryans, varyansın kalıtımla aynı yönde olduğu anlamına gelir, yani Soalt tipin her bir yönteminin çıktısının tipi S, üst tipin karşılık gelen yönteminin karşılık gelen çıktısının tipiyle aynı veya alt tip olmalıdır .ToT

Bunun nedeni, çağıranın bir türü Tolduğunu düşünmesi, bir yöntem çağırdığını düşünmesi durumunda, tür Targüman (ları) Tisağlaması ve çıktıyı türe atamasıdır To. Gerçekte karşılık gelen yöntemi çağırdığında, Sher Tigirdi bağımsız değişkeni bir Sigirdi parametresine Soatanır ve çıktı türüne atanır To. Böylece eğer Sikarşı kontravaryant wrt değildi Ti, o zaman bir alt tipi Xibir alt tip olmaz -ki Siatanacak misiniz, Ti.

Buna ek olarak, tanım yerinde varyans tipi polimorfizm parametrelerine ek açıklamaları (örneğin jenerik), tip her tür parametresi için varyans ek açıklama ko- ya da kontra yönü dilleri (örneğin Scala veya Seylan) için Tolması gereken ters veya aynı yönde sırasıyla Ttür parametresinin türüne sahip her giriş parametresine veya çıkışına (her yöntemin ).

Ayrıca, bir fonksiyon tipine sahip her bir giriş parametresi veya çıkışı için, gereken varyans yönü tersine çevrilir. Bu kural yinelemeli olarak uygulanır.


Değişmezlerin numaralandırılabileceği yerlerde alt tipleme uygundur .

Değişmezlerin nasıl modelleneceğine dair çok sayıda araştırma var, böylece derleyici tarafından uygulanıyorlar.

Typestate (bkz. Sayfa 3), türüne dik durumdaki durum değişmezlerini bildirir ve uygular. Alternatif olarak, değişmezler iddiaları türlere dönüştürerek uygulanabilir . Örneğin, bir dosyayı kapatmadan önce açık olduğunu iddia etmek için File.open (), Dosya'da bulunmayan bir close () yöntemi içeren bir OpenFile türü döndürebilir. Bir tic tac ayak API derleme zamanında değişmezler uygulanması için yazma kullanılarak başka bir örnek olabilir. Tip sistemi Turing tamamlanmış olabilir, örneğin Scala . Bağımlı olarak yazılan diller ve teorem kanıtlayıcılar, yüksek dereceli yazım modellerini resmileştirir.

Anlambilimin uzatma üzerine soyutlanmasına duyulan ihtiyaç nedeniyle, model değişmezlere, yani birleşik yüksek dereceli anlamsal anlambilime yazmak için yazı yazmanın, Typetate'den daha üstün olmasını bekliyorum. 'Uzatma', koordine edilmemiş, modüler gelişimin sınırsız, izin verilen bileşimi anlamına gelir. Bana göre birleşme ve dolayısıyla serbestlik derecesi antitezi, genişletilebilir kompozisyon için birbiriyle birleştirilemeyen paylaşılan semantikleri ifade etmek için karşılıklı olarak bağımlı iki modele (örn. Tipler ve Tiptat) sahip olmak . Örneğin, İfade Sorunu benzeri uzantı, alt tipleme, işlev aşırı yüklenmesi ve parametrik yazım alanlarında birleştirildi.

Teorik konumum, bilginin var olması için (bkz. “Merkezileşme kör ve elverişsizdir” bölümüne), Turing-complete bilgisayar dilinde olası tüm değişmezlerin% 100 kapsamını zorlayabilecek genel bir model asla olmayacaktır. Bilginin var olması için, beklenmedik olasılıklar çoktur, yani düzensizlik ve entropi her zaman artıyor olmalıdır. Bu entropik kuvvettir. Potansiyel bir uzantının tüm olası hesaplamalarını kanıtlamak, tüm olası uzatmaların önceden hesaplanmasıdır.

Bu yüzden Durdurma Teoremi var, yani bir Turing-complete programlama dilinde her olası programın sonlanıp sonlanamayacağı kesin değil. Bazı özel programların (tüm olasılıkların tanımlandığı ve hesaplandığı) sona erdiği kanıtlanabilir. Ancak, bu programın genişletilmesi için olasılıklar Turing tamamlanmadığı sürece (örneğin, bağımlı yazarak), o programın tüm olası uzantılarının sona erdiğini kanıtlamak imkansızdır. Turing-tamlık için temel gereklilik sınırsız özyineleme olduğundan , Gödel'in eksiklik teoremlerinin ve Russell paradoksunun uzatma için nasıl uygulandığını anlamak sezgiseldir.

Bu teoremlerin bir yorumu, onları entropik gücün genel bir kavramsal anlayışına dahil eder:

  • Gödel'in eksiklik teoremleri : tüm aritmetik gerçeklerin kanıtlanabileceği herhangi bir biçimsel teori tutarsızdır.
  • Russell'ın paradoksu : Bir küme içerebilen bir kümenin her üyelik kuralı, ya her üyenin kendine özgü türünü numaralandırır ya da kendisini içerir. Böylece kümeler ya genişletilemez ya da sınırsız özyineleme olur. Örneğin, bir çaydanlık olmayan her şey, kendisini içeren, kendisini içeren, vb. İçerir. Bu nedenle, bir kural (bir küme içerebilir ve) belirli türleri numaralandırmazsa (yani tüm belirtilmemiş türlere izin verirse) ve sınırsız uzantıya izin vermiyorsa tutarsızdır. Bu, kendilerinin üyesi olmayan kümeler kümesidir. Olası tüm genişleme boyunca hem tutarlı hem de tamamen numaralandırılamaması, Gödel'in eksiklik teoremleri.
  • Liskov Substisyon Prensibi : Herhangi bir setin diğerinin alt kümesi olup olmadığı genellikle kararsız bir sorundur, yani kalıtım genellikle kararsızdır.
  • Linsky Referanslama : bir şeyin hesaplanmasının ne olduğu, tanımlandığında veya algılandığında, yani algılamanın (gerçeklik) mutlak bir referans noktası olmadığına karar verilemez.
  • Coase teoremi : harici bir referans noktası yoktur, bu nedenle sınırsız harici olasılıkların önündeki engeller başarısız olacaktır.
  • Termodinamiğin ikinci yasası : tüm evren (kapalı bir sistem, yani her şey) maksimum düzensizliğe, yani maksimum bağımsız olasılıklara yönelir.

17
@Shelyby: Çok fazla şey karıştırdınız. Her şey sizin ifade ettiğiniz kadar kafa karıştırıcı değildir. Teorik iddialarınızın çoğu dayanıksız zeminde durur, örneğin 'Bilginin var olması için, beklenmedik olasılıklar çoktur, .........' VE 'genellikle herhangi bir kümenin bir diğerinin alt kümesi olması kararlaştırılamaz bir sorundur, yani kalıtım genellikle kararsızdır '. Bu noktaların her biri için ayrı bir blog başlatabilirsiniz. Her neyse, iddialarınız ve varsayımlarınız oldukça tartışmalıdır. Kişi farkında olmayan şeyleri kullanmamalıdır!
aknon

1
@aknon Bu konuları daha derinlemesine açıklayan bir blogum var. Sonsuz uzay-zaman TOE modelim sınırsız frekanslardır. Özyinelemeli bir endüktif fonksiyonun sonsuz bir uç sınırına sahip bilinen bir başlangıç ​​değerine sahip olması veya bir koindüktif fonksiyonun bilinmeyen bir uç değerine ve bilinen bir başlangıç ​​sınırına sahip olması kafa karıştırıcı değildir. Görelilik, özyineleme uygulandıktan sonra sorundur. Bu yüzden Turing complete, sınırsız özyineleme ile eşdeğerdir .
Shelby Moore III

4
@ShelbyMooreIII Çok fazla yöne gidiyorsunuz. Bu bir cevap değil.
Soldalma

1
@Soldalma bu bir cevap. Cevap bölümünde görmüyor musunuz? Seninki bir yorum çünkü yorum bölümünde.
Shelby Moore III

1
Scala dünyasıyla karıştırmak gibi!
Ehsan M. Kermani

24

Her cevapta dikdörtgenler ve kareler ve LSP'nin nasıl ihlal edileceğini görüyorum.

LSP'nin gerçek dünyadaki bir örnekle nasıl uyumlu olabileceğini göstermek istiyorum:

<?php

interface Database 
{
    public function selectQuery(string $sql): array;
}

class SQLiteDatabase implements Database
{
    public function selectQuery(string $sql): array
    {
        // sqlite specific code

        return $result;
    }
}

class MySQLDatabase implements Database
{
    public function selectQuery(string $sql): array
    {
        // mysql specific code

        return $result; 
    }
}

Bu tasarım LSP'ye uygundur, çünkü kullanmayı seçtiğimiz uygulamadan bağımsız olarak davranış değişmeden kalır.

Ve evet, bu yapılandırmada LSP'yi aşağıdaki gibi basit bir değişiklik yaparak ihlal edebilirsiniz:

<?php

interface Database 
{
    public function selectQuery(string $sql): array;
}

class SQLiteDatabase implements Database
{
    public function selectQuery(string $sql): array
    {
        // sqlite specific code

        return $result;
    }
}

class MySQLDatabase implements Database
{
    public function selectQuery(string $sql): array
    {
        // mysql specific code

        return ['result' => $result]; // This violates LSP !
    }
}

Artık alt türler artık aynı sonucu vermediği için aynı şekilde kullanılamıyor.


6
Örnek, yalnızca tüm DB motorları Database::selectQuerytarafından desteklenen SQL alt kümesini destekleme anlamını kısıtladığımız sürece LSP'yi ihlal etmez . Bu neredeyse pratik değil ... Yani, örneğin burada kullanılan diğerlerinden daha kolay kavranması daha kolay.
Palec

5
Bu cevabı diğerlerinden en kolay anlaşılanı buldum.
Malcolm Salvador

23

Liskov'u ihlal edip etmediğinizi belirlemek için bir kontrol listesi vardır.

  • Aşağıdaki öğelerden birini ihlal ederseniz -> Liskov'u ihlal edersiniz.
  • Eğer herhangi birini ihlal etmezseniz -> hiçbir şey sonuçlandıramaz.

Kontrol listesi:

  • Türetilmiş sınıfta yeni bir istisna atılmamalıdır : Temel sınıfınız ArgumentNullException öğesini attıysa, alt sınıflarınızın yalnızca ArgumentNullException türünde veya ArgumentNullException öğesinden türetilen tüm özel durumları atmasına izin verilir. IndexOutOfRangeException Fırlatma, Liskov'un ihlalidir.
  • Ön koşullar güçlendirilemez : Temel sınıfınızın bir üye int ile çalıştığını varsayın. Şimdi alt türünüz bu int'in pozitif olmasını gerektirir. Bu güçlendirilmiş ön koşullar ve şimdi negatif ints ile daha önce mükemmel çalışan herhangi bir kod kırıldı.
  • Son koşullar zayıflatılamıyor : Temel sınıfınızın gerekli olan yöntem döndürülmeden önce veritabanına yapılan tüm bağlantıların kapatılması gerektiğini varsayın. Alt sınıfınızda, bu yöntemi geçersiz kıldınız ve daha fazla yeniden kullanmak için açık bağlantıyı açtınız. Bu yöntemin son koşullarını zayıflattınız.
  • Değişmezler korunmalıdır : Gerçekleşmesi en zor ve acı verici kısıtlama. Değişmezler temel sınıfta bir süre gizlidir ve bunları ortaya çıkarmanın tek yolu temel sınıfın kodunu okumaktır. Temel olarak, bir yöntemi geçersiz kıldığınızda, geçersiz kılınan yöntem yürütüldükten sonra değiştirilemez bir şeyin değişmeden kalması gerektiğinden emin olmalısınız. Aklıma gelen en iyi şey, temel sınıftaki bu değişmez kısıtlamaları uygulamaktır, ancak bu kolay olmayacaktır.
  • Geçmiş Kısıtlaması : Bir yöntemi geçersiz kılarken, temel sınıfta değiştirilemez bir özelliği değiştirmenize izin verilmez. Bu koda bir göz atın ve Ad'ın değiştirilemez (özel küme) olarak tanımlandığını görebilirsiniz, ancak SubType, değiştirmeye izin veren yeni bir yöntem sunar (yansıma yoluyla):

    public class SuperType
    {
        public string Name { get; private set; }
        public SuperType(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
    public class SubType : SuperType
    {
        public void ChangeName(string newName)
        {
            var propertyType = base.GetType().GetProperty("Name").SetValue(this, newName);
        }
    }
    

Başka 2 öğe daha vardır: Yöntem bağımsız değişkenlerinin çelişkisi ve dönüş türlerinin kovaryansı . Ama bu C # (ben bir C # geliştirici) mümkün değildir, bu yüzden onları umurumda değil.

Referans:


Ayrıca bir C # geliştiricisiyim ve son ifadenizin .Net 4.0 çerçevesi ile Visual Studio 2010'dan itibaren doğru olmadığını söyleyeceğim. Dönüş türlerinin kovaryansı, arabirim tarafından tanımlanandan daha türetilmiş bir dönüş türüne izin verir. Örnek: Örnek: IEnumerable <T> (T kovaryant) IEnumerator <T> (T kovaryant) IQueryable <T> (T kovaryant) IGrouping <TKey, TElement> (TKey ve TElement kovaryant) IComparer <T> (T çelişkili) IEqualityComparer <T> (T çelişkili) IComparable <T> (T
çelişkili

1
Harika ve odaklanmış cevap (orijinal sorular kurallardan çok örnekler hakkında olmasına rağmen).
Mike

22

LSP sınıfların sözleşmesiyle ilgili bir kuraldır: eğer bir temel sınıf bir sözleşmeyi yerine getiriyorsa, o zaman LSP'den türetilen sınıflar da bu sözleşmeyi yerine getirmelidir.

Yalancı Piton

class Base:
   def Foo(self, arg): 
       # *... do stuff*

class Derived(Base):
   def Foo(self, arg):
       # *... do stuff*

Türetilmiş bir nesnede Foo'yu her çağırdığınızda, arg aynı olduğu sürece bir Base nesnesinde Foo'yu çağırmakla aynı sonuçları verirse LSP'yi tatmin eder.


9
Ama ... her zaman aynı davranışı elde ederseniz, türetilmiş sınıfa sahip olmanın anlamı nedir?
Leonid

2
Bir noktayı kaçırdınız: aynı gözlemlenen davranış. Örneğin, O (n) performansı olan bir şeyi işlevsel olarak eşdeğer bir şeyle, ancak O (lg n) performansı ile değiştirebilirsiniz. Veya MySQL ile uygulanan verilere erişen bir şeyi değiştirebilir ve bunu bir bellek içi veritabanıyla değiştirebilirsiniz.
Charlie Martin

@Charlie Martin, bir uygulama yerine bir arabirime kodlama - Bunu kazarım. Bu OOP'ye özgü değildir; Clojure gibi fonksiyonel diller de bunu desteklemektedir. Java veya C # açısından bile, soyut bir sınıf artı sınıf hiyerarşileri kullanmak yerine bir arayüz kullanmanın verdiğiniz örnekler için doğal olacağını düşünüyorum. Python güçlü bir şekilde yazılmamıştır ve en azından açıkça değil, gerçekten arayüzlere sahip değildir. Zorluğum, SOLID'e bağlı kalmadan birkaç yıldır OOP yapıyor olmam. Şimdi onunla karşılaştığım için, sınırlayıcı ve neredeyse kendiyle çelişen görünüyor.
Hamish Grubijan

Geri dönüp Barbara'nın orijinal gazetesine bakmalısın. report-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.ps Arayüzler açısından gerçekten belirtilmemiş ve herhangi bir durumda tutan (veya olmayan) mantıklı bir ilişki kalıtım biçimi olan programlama dili.
Charlie Martin

1
@HamishGrubijan Sana kimin Python'un güçlü bir şekilde yazılmadığını söylediğini bilmiyorum, ama sana yalan söylüyorlardı (ve bana inanmıyorsan bir Python yorumlayıcıyı ateşle ve dene 2 + "2"). Belki de "güçlü yazılan" ı "statik olarak yazılan" ile karıştırıyorsunuz?
Ocak'ta asmeurer

21

Uzun lafın kısası, dikdörtgenler ve kare kareler bırakalım, bir ebeveyn sınıfını genişletirken pratik bir örnek olarak, tam ana API'yi KORUYMALISINIZ veya BUNU UZATMALISINIZ.

Diyelim ki temel bir ItemsRepository'niz var.

class ItemsRepository
{
    /**
    * @return int Returns number of deleted rows
    */
    public function delete()
    {
        // perform a delete query
        $numberOfDeletedRows = 10;

        return $numberOfDeletedRows;
    }
}

Ve onu genişleten bir alt sınıf:

class BadlyExtendedItemsRepository extends ItemsRepository
{
    /**
     * @return void Was suppose to return an INT like parent, but did not, breaks LSP
     */
    public function delete()
    {
        // perform a delete query
        $numberOfDeletedRows = 10;

        // we broke the behaviour of the parent class
        return;
    }
}

Ardından , Base ItemsRepository API'sı ile çalışan ve ona güvenen bir İstemciniz olabilir .

/**
 * Class ItemsService is a client for public ItemsRepository "API" (the public delete method).
 *
 * Technically, I am able to pass into a constructor a sub-class of the ItemsRepository
 * but if the sub-class won't abide the base class API, the client will get broken.
 */
class ItemsService
{
    /**
     * @var ItemsRepository
     */
    private $itemsRepository;

    /**
     * @param ItemsRepository $itemsRepository
     */
    public function __construct(ItemsRepository $itemsRepository)
    {
        $this->itemsRepository = $itemsRepository;
    }

    /**
     * !!! Notice how this is suppose to return an int. My clients expect it based on the
     * ItemsRepository API in the constructor !!!
     *
     * @return int
     */
    public function delete()
    {
        return $this->itemsRepository->delete();
    }
} 

LSP zaman bozuldu ikame ebeveyn bir ile sınıf alt sınıf sonları API sözleşme .

class ItemsController
{
    /**
     * Valid delete action when using the base class.
     */
    public function validDeleteAction()
    {
        $itemsService = new ItemsService(new ItemsRepository());
        $numberOfDeletedItems = $itemsService->delete();

        // $numberOfDeletedItems is an INT :)
    }

    /**
     * Invalid delete action when using a subclass.
     */
    public function brokenDeleteAction()
    {
        $itemsService = new ItemsService(new BadlyExtendedItemsRepository());
        $numberOfDeletedItems = $itemsService->delete();

        // $numberOfDeletedItems is a NULL :(
    }
}

Kursumda sürdürülebilir yazılım yazma hakkında daha fazla bilgi edinebilirsiniz: https://www.udemy.com/enterprise-php/


20

İşaretçiler veya temel sınıflara başvurular kullanan işlevler, türetilmiş sınıfların nesnelerini bilmeden kullanabilmelidir.

LSP hakkında ilk okuduğumda, bunun çok sıkı bir anlamda kastedildiğini, aslında arayüzün uygulanması ve tipte güvenli dökümle eşdeğer olduğunu varsaydım. Bu, LSP'nin dil tarafından sağlanmış olup olmadığı anlamına gelir. Örneğin, bu katı anlamda, ThreeDBoard derleyici açısından kesinlikle Kurul yerine geçer.

Konsept hakkında daha fazla okuduktan sonra LSP'nin genel olarak bundan daha geniş bir şekilde yorumlandığını gördüm.

Kısacası, istemci kodunun, işaretçinin arkasındaki nesnenin işaretçi türü yerine türetilmiş bir tür olduğunu "bilmesi" ne anlama gelirse, tür güvenliği ile sınırlı değildir. LSP'ye bağlılık, nesnelerin gerçek davranışını inceleyerek de test edilebilir. Yani, bir nesnenin durumu ve yöntem bağımsız değişkenlerinin yöntem çağrılarının sonuçları veya nesneden atılan özel durum türleri üzerindeki etkisini incelemek.

Yine örneğe dönersek , teorik olarak Board yöntemleri ThreeDBoard'da iyi çalışabilir. Bununla birlikte, uygulamada, ThreeDBoard'un eklemeyi amaçladığı işlevselliği engellemeden, istemcinin düzgün işleyemeyeceği davranış farklılıklarını önlemek çok zor olacaktır.

Elimizdeki bu bilgi ile LSP uyumunun değerlendirilmesi, mirastan ziyade mevcut işlevselliği genişletmek için kompozisyonun ne zaman daha uygun bir mekanizma olduğunu belirlemede harika bir araç olabilir.


19

Sanırım herkes LSP'nin teknik olarak ne olduğunu kapsıyor: Temel olarak alt tip detaylardan soyutlamak ve süper tipleri güvenle kullanmak istersiniz.

Yani Liskov'un altında yatan 3 kural var:

  1. İmza Kuralı: Alt türdeki her tür işlemin sözdizimsel olarak geçerli bir uygulaması olmalıdır. Bir derleyici sizin için kontrol edebilecek bir şey. Daha az istisna atma ve en azından süper tip yöntemler kadar erişilebilir olma konusunda küçük bir kural vardır.

  2. Yöntemler Kural: Bu işlemlerin uygulanması anlamsal olarak doğrudur.

    • Zayıf Önkoşullar: Alt tip fonksiyonları, daha fazla değilse, en azından üst tipin girdi olarak aldığını almalıdır.
    • Daha Güçlü Son Koşullar: Üretilen süpertip yöntemlerin çıktısının bir alt kümesini üretmelidir.
  3. Özellikler Kural: Bu, bireysel işlev çağrılarının ötesine geçer.

    • Değişmezler: Her zaman doğru olan şeyler doğru kalmalıdır. Örneğin. bir Setin boyutu asla negatif değildir.
    • Evrimsel Özellikler: Genellikle değişmezlik ya da nesnenin içinde bulunduğu durumlarla ilgili bir şeydir. Ya da belki de nesne sadece büyür ve asla küçülmez, böylece alt tip yöntemler bunu yapmamalıdır.

Tüm bu özelliklerin korunması ve ekstra alt tip işlevselliğinin süper tip özelliklerini ihlal etmemesi gerekir.

Eğer bu üç şey halledilirse, altta yatan şeylerden soyutlandınız ve gevşek bir şekilde kod yazıyorsunuz.

Kaynak: Java'da Program Geliştirme - Barbara Liskov


18

LSP kullanımının önemli bir örneği yazılım testidir .

B'nin LSP uyumlu bir alt sınıfı olan bir A sınıfım varsa, B'yi test etmek için A test paketini yeniden kullanabilirim.

A alt sınıfını tam olarak test etmek için, muhtemelen birkaç test senaryosu daha eklemem gerekiyor, ancak en azından üst sınıf B'nin tüm test vakalarını tekrar kullanabilirim.

Bunu fark etmenin bir yolu, McGregor'un "test için paralel hiyerarşi" dediği şeyi oluşturarak: ATestSınıfımın miras alacaktır BTest. Daha sonra, test durumunun B tipi yerine A tipi nesnelerle çalışmasını sağlamak için bir çeşit enjeksiyona ihtiyaç duyulur (basit bir şablon yöntemi deseni yapılır).

Süper sınama paketinin tüm alt sınıf uygulamaları için yeniden kullanılmasının aslında bu alt sınıf uygulamalarının LSP uyumlu olduğunu test etmenin bir yolu olduğunu unutmayın. Dolayısıyla, üst sınıf test takımının herhangi bir alt sınıf bağlamında çalıştırılması gerektiği de iddia edilebilir .

Ayrıca Stackoverflow sorusunun cevabına da bakınız " Bir arayüzün uygulamasını test etmek için bir dizi tekrar kullanılabilir test uygulayabilir miyim? "


14

Java ile açıklayalım:

class TrasportationDevice
{
   String name;
   String getName() { ... }
   void setName(String n) { ... }

   double speed;
   double getSpeed() { ... }
   void setSpeed(double d) { ... }

   Engine engine;
   Engine getEngine() { ... }
   void setEngine(Engine e) { ... }

   void startEngine() { ... }
}

class Car extends TransportationDevice
{
   @Override
   void startEngine() { ... }
}

Burada sorun yok değil mi? Bir araba kesinlikle bir ulaşım cihazıdır ve burada üst sınıfının startEngine () yöntemini geçersiz kıldığını görebiliriz.

Başka bir taşıma cihazı ekleyelim:

class Bicycle extends TransportationDevice
{
   @Override
   void startEngine() /*problem!*/
}

Şimdi her şey planlandığı gibi gitmiyor! Evet, bir bisiklet bir ulaşım aracıdır, ancak motoru yoktur ve bu nedenle startEngine () yöntemi uygulanamaz.

Bunlar, Liskov İkame Prensibi'nin ihlal ettiği türden problemlerdir ve çoğunlukla hiçbir şey yapmayan veya hatta uygulanamayan bir yöntemle tanınabilirler.

Bu sorunların çözümü doğru bir miras hiyerarşisidir ve bizim durumumuzda, motorlu ve motorsuz ulaşım cihazları sınıflarını farklılaştırarak sorunu çözeceğiz. Bir bisiklet bir ulaşım cihazı olsa da, motoru yoktur. Bu örnekte nakliye cihazı tanımımız yanlış. Motoru olmamalı.

TransportationDevice sınıfımızı aşağıdaki gibi yeniden düzenleyebiliriz:

class TrasportationDevice
{
   String name;
   String getName() { ... }
   void setName(String n) { ... }

   double speed;
   double getSpeed() { ... }
   void setSpeed(double d) { ... }
}

Artık motorsuz cihazlar için TransportationDevice'i genişletebiliriz.

class DevicesWithoutEngines extends TransportationDevice
{  
   void startMoving() { ... }
}

Ve motorlu cihazlar için TransportationDevice'i uzatın. Engine nesnesini eklemek daha uygundur.

class DevicesWithEngines extends TransportationDevice
{  
   Engine engine;
   Engine getEngine() { ... }
   void setEngine(Engine e) { ... }

   void startEngine() { ... }
}

Böylece Liskov İkame İlkesine bağlı kalarak Araba sınıfımız daha uzmanlaşır.

class Car extends DevicesWithEngines
{
   @Override
   void startEngine() { ... }
}

Bisiklet sınıfımız da Liskov İkame Prensibi ile uyumludur.

class Bicycle extends DevicesWithoutEngines
{
   @Override
   void startMoving() { ... }
}

9

LSP'nin bu formülasyonu çok güçlü:

S tipi her nesne o1 için T tipi bir nesne O2 varsa, öyle ki T açısından tüm P programları tanımlanırsa, o1 o2 ile ikame edildiğinde P'nin davranışı değişmez, o zaman S T'nin bir alt tipidir.

Bu temelde S'nin T ile aynı şeyin tamamen, tamamen kapsüllenmiş bir uygulaması olduğu anlamına gelir. Ve cesur olabilirim ve performansın P'nin davranışının bir parçası olduğuna karar verebilirim ...

Yani, temel olarak, geç bağlamanın herhangi bir kullanımı LSP'yi ihlal eder. Bir tür bir nesneyi başka bir türün yerine koyduğumuzda farklı bir davranış elde etmek OO'nun bütün amacı!

Vikipedi tarafından belirtilen formülasyon daha iyidir çünkü özellik bağlama bağlıdır ve programın tüm davranışını içermez.


2
Hmm, bu formülasyon Barbara Liskov'un kendisidir. Barbara Liskov, “Veri Soyutlama ve Hiyerarşi,” SIGPLAN Bildirimleri, 23,5 (Mayıs 1988). "Çok güçlü" değil, "tam olarak doğru" ve sahip olduğunu düşündüğünüz bir imaya sahip değil. Güçlüdür, ancak doğru miktarda güce sahiptir.
DrPizza

Sonra, gerçek hayatta çok az alt tip var :)
Damien Pollet

3
"Davranış değişmez", bir alt türün size aynı somut sonuç değerlerini vereceği anlamına gelmez. Bu, alt türün davranışının taban türünde beklenenden farklı olduğu anlamına gelir. Örnek: base type Shape bir draw () yöntemine sahip olabilir ve bu yöntemin şekli oluşturmasını şart koşar. Şeklin iki alt türü (örneğin, Kare ve Daire) draw () yöntemini uygular ve sonuçlar farklı görünür. Ancak davranış (şekli oluşturma) Shape'in belirtilen davranışıyla eşleştiği sürece, Kare ve Daire LSP'ye göre Shape'nin alt tipleri olacaktır.
SteveT

9

Çok basit bir cümleyle şunu söyleyebiliriz:

Çocuk sınıfı temel sınıf özelliklerini ihlal etmemelidir. Onunla yetenekli olmalı. Bunun alt tiple aynı olduğunu söyleyebiliriz.


9

Liskov'un Değiştirme Prensibi (LSP)

Her zaman bir program modülü tasarlıyoruz ve bazı sınıf hiyerarşileri yaratıyoruz. Sonra bazı sınıfları türetilmiş sınıflar yaratarak genişletiyoruz.

Yeni türetilmiş sınıfların eski sınıfların işlevlerini değiştirmeden genişlediğinden emin olmalıyız. Aksi takdirde, yeni sınıflar mevcut program modüllerinde kullanıldıklarında istenmeyen etkiler üretebilirler.

Liskov'un Değiştirme Prensibi, bir program modülü bir Base sınıfı kullanıyorsa, Base sınıfına yapılan başvurunun, program modülünün işlevselliğini etkilemeden Türetilmiş bir sınıfla değiştirilebileceğini belirtir.

Misal:

Aşağıda, Liskov'un Değiştirme Prensibi'nin ihlal edildiği klasik örnek yer almaktadır. Örnekte 2 sınıf kullanılmıştır: Dikdörtgen ve Kare. Dikdörtgen nesnesinin uygulamada bir yerde kullanıldığını varsayalım. Uygulamayı genişletip Square sınıfını ekliyoruz. Kare sınıf, bazı koşullara bağlı olarak fabrika modeliyle döndürülür ve ne tür nesnelerin döndürüleceğini tam olarak bilmiyoruz. Ama bunun bir Dikdörtgen olduğunu biliyoruz. Dikdörtgen nesnesini alıyoruz, genişliği 5'e ve yüksekliği 10'a ayarlıyoruz ve alanı alıyoruz. Genişliği 5 ve yüksekliği 10 olan bir dikdörtgen için alan 50 olmalıdır. Bunun yerine, sonuç 100 olacaktır.

    // Violation of Likov's Substitution Principle
class Rectangle {
    protected int m_width;
    protected int m_height;

    public void setWidth(int width) {
        m_width = width;
    }

    public void setHeight(int height) {
        m_height = height;
    }

    public int getWidth() {
        return m_width;
    }

    public int getHeight() {
        return m_height;
    }

    public int getArea() {
        return m_width * m_height;
    }
}

class Square extends Rectangle {
    public void setWidth(int width) {
        m_width = width;
        m_height = width;
    }

    public void setHeight(int height) {
        m_width = height;
        m_height = height;
    }

}

class LspTest {
    private static Rectangle getNewRectangle() {
        // it can be an object returned by some factory ...
        return new Square();
    }

    public static void main(String args[]) {
        Rectangle r = LspTest.getNewRectangle();

        r.setWidth(5);
        r.setHeight(10);
        // user knows that r it's a rectangle.
        // It assumes that he's able to set the width and height as for the base
        // class

        System.out.println(r.getArea());
        // now he's surprised to see that the area is 100 instead of 50.
    }
}

Sonuç:

Bu ilke sadece Açık Kapalı Prensibinin bir uzantısıdır ve yeni türetilmiş sınıfların temel sınıfları davranışlarını değiştirmeden genişlettiğinden emin olmamız gerektiği anlamına gelir.

Ayrıca bakınız: Açık Kapalı Prensibi

Daha iyi yapı için bazı benzer kavramlar: Konfigürasyon üzerine sözleşme


8

Liskov İkame İlkesi

  • Geçersiz kılınan yöntem boş kalmamalıdır
  • Geçersiz kılınan yöntem hata vermemelidir
  • Temel sınıf veya arabirim davranışı türetilmiş sınıf davranışlarından dolayı değişiklik (yeniden işleme) yapılmamalıdır.

7

Bazı zeyilname:
Neden hiç kimse türetilmiş sınıflar tarafından uyulması gereken temel sınıfın Değişmez, önkoşulları ve post koşulları hakkında bir şey yazmadı. Türetilmiş bir D sınıfının Temel sınıf B tarafından tamamen sustitlabilmesi için D sınıfı belirli koşullara uymalıdır:

  • Temel sınıfın varyantları türetilmiş sınıf tarafından korunmalıdır
  • Temel sınıfın ön koşulları türetilmiş sınıf tarafından güçlendirilmemelidir
  • Temel sınıfın post-koşulları türetilmiş sınıf tarafından zayıflatılmamalıdır.

Bu nedenle türetilmiş olanlar, temel sınıfın empoze ettiği yukarıdaki üç koşulun farkında olmalıdır. Bu nedenle, alt tipleme kuralları önceden kararlaştırılmıştır. Yani 'IS A' ilişkisine ancak bazı kurallar alt tip uyulduğu takdirde uyulacaktır. Bu kurallar, değişmezler, önkoşullar ve sonkoşul şeklinde, resmi bir ' tasarım sözleşmesi ' ile kararlaştırılmalıdır .

Bu konuda daha fazla tartışma blogumda mevcuttur: Liskov İkame ilkesi


6

LSP basit terimlerle , aynı üst sınıftaki nesnelerin hiçbir şeyi bozmadan birbirleriyle değiştirilebilmesi gerektiğini belirtir .

Örneğin, Catbir Dogsınıftan türetilen bir ve Animalsınıfımız varsa, Animal sınıfını kullanan herhangi bir işlev normal şekilde kullanabilmeli Catveya Dogdavranabilmelidir.


4

Bir Kurul dizisi açısından ThreeDBoard uygulamak bu kadar faydalı olur mu?

Belki de çeşitli düzlemlerde ThreeDBoard dilimleri bir Tahta olarak tedavi etmek isteyebilirsiniz. Bu durumda, Board için birden fazla uygulamaya izin verecek bir arabirim (veya soyut sınıf) tasarlamak isteyebilirsiniz.

Harici arabirim açısından, hem TwoDBoard hem de ThreeDBoard için bir Board arabirimini hesaba katmak isteyebilirsiniz (yukarıdaki yöntemlerin hiçbiri uygun olmasa da).


1
Örnek sadece tahtadan devralmanın ThreeDBoard bağlamında anlamsız olduğunu ve tüm yöntem imzalarının bir Z ekseni ile anlamsız olduğunu göstermek olduğunu düşünüyorum.
NotMyself

4

Kare, genişliğin yüksekliğe eşit olduğu bir dikdörtgendir. Kare, genişlik ve yükseklik için iki farklı boyut ayarlarsa, kare değişmezini ihlal eder. Bu, yan etkiler getirilerek çözülmüştür. Ancak, dikdörtgenin önkoşulu 0 <yükseklik ve 0 <genişlik olan bir setSize (yükseklik, genişlik) varsa. Türetilmiş alt tip yöntemi height == width; daha güçlü bir önkoşul (ve bu da lsp'yi ihlal ediyor). Bu, kare bir dikdörtgen olmasına rağmen, önkoşul güçlendirildiği için geçerli bir alt tür olmadığını gösterir. Etraftaki çalışma (genel olarak kötü bir şey) bir yan etkiye neden olur ve bu da post koşulunu (lsp'yi ihlal eder) zayıflatır. Tabandaki setWidth, 0 <genişlik direk koşuluna sahiptir. Türetilmiş, height == width ile onu zayıflatır.

Bu nedenle yeniden boyutlandırılabilir bir kare yeniden boyutlandırılabilir bir dikdörtgen değildir.


4

Bu ilke Barbara Liskov tarafından tanıtıldı tarafından 1987 ve bir Üst Sınıf ve alt türlerinin davranışlarına odaklanarak Açık-Kapalı Prensibi'ni genişletti.

İhlal etmenin sonuçlarını düşündüğümüzde önemi belirginleşir. Aşağıdaki sınıfı kullanan bir uygulamayı düşünün.

public class Rectangle 
{ 
  private double width;

  private double height; 

  public double Width 
  { 
    get 
    { 
      return width; 
    } 
    set 
    { 
      width = value; 
    }
  } 

  public double Height 
  { 
    get 
    { 
      return height; 
    } 
    set 
    { 
      height = value; 
    } 
  } 
}

Bir gün, müşterinin dikdörtgenlere ek olarak kareleri manipüle etme yeteneğini istediğini düşünün. Kare bir dikdörtgen olduğundan, kare sınıfı Rectangle sınıfından türetilmelidir.

public class Square : Rectangle
{
} 

Ancak bunu yaparak iki sorunla karşılaşacağız:

Kare, dikdörtgenden devralınan hem yükseklik hem de genişlik değişkenlerine ihtiyaç duymaz ve yüz binlerce kare nesne oluşturmak zorunda kalırsak, bellekte önemli bir atık yaratabilir. Dikdörtgenden devralınan genişlik ve yükseklik ayarlayıcı özellikleri bir kare için uygun değildir, çünkü bir karenin genişliği ve yüksekliği aynıdır. Hem yüksekliği hem de genişliği aynı değere ayarlamak için aşağıdaki gibi iki yeni özellik oluşturabiliriz:

public class Square : Rectangle
{
  public double SetWidth 
  { 
    set 
    { 
      base.Width = value; 
      base.Height = value; 
    } 
  } 

  public double SetHeight 
  { 
    set 
    { 
      base.Height = value; 
      base.Width = value; 
    } 
  } 
}

Şimdi, birisi kare bir nesnenin genişliğini ayarladığında, yüksekliği buna göre değişecektir ve bunun tersi de geçerlidir.

Square s = new Square(); 
s.SetWidth(1); // Sets width and height to 1. 
s.SetHeight(2); // sets width and height to 2. 

İlerleyelim ve bu diğer işlevi düşünelim:

public void A(Rectangle r) 
{ 
  r.SetWidth(32); // calls Rectangle.SetWidth 
} 

Bu işleve kare bir nesneye bir başvuru iletirsek, işlev bağımsız değişkenlerinin türevleri için çalışmadığından LSP'yi ihlal ederiz. Width ve height özellikleri polimerik değildir çünkü dikdörtgen içinde sanal olarak bildirilmezler (yükseklik değiştirilmediği için kare nesne bozulur).

Ancak, ayarlayıcı özelliklerini sanal olarak ilan ederek, başka bir ihlal olan OCP ile karşılaşacağız. Aslında, türetilmiş bir sınıf karesinin oluşturulması, temel sınıf dikdörtgeninde değişikliklere neden olmaktadır.


3

Şimdiye kadar bulduğum LSP için en açık açıklama "Liskov İkame İlkesi, türetilmiş bir sınıfın nesnesinin, sistemde herhangi bir hata getirmeden veya temel sınıfın davranışını değiştirmeden temel sınıfın bir nesnesini değiştirebileceğini söylüyor. " buradan . Makale, LSP'yi ihlal etmek ve düzeltmek için kod örneği verir.


1
Lütfen yığın akışı ile ilgili kod örneklerini sağlayın.
sebenalern

3

Diyelim ki kodumuzda bir dikdörtgen kullanıyoruz

r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);

Geometri sınıfımızda, bir karenin özel bir dikdörtgen türü olduğunu öğrendik çünkü genişliği yüksekliğiyle aynı uzunluktadır. SquareBu bilgilere dayanarak bir sınıf da yapalım :

class Square extends Rectangle {
    setDimensions(width, height){
        assert(width == height);
        super.setDimensions(width, height);
    }
} 

İlk kodumuzda Rectangleile değiştirirsek, Squareo zaman kırılır:

r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);

Bunun nedeni Squarebiz yoktu yeni ön şartı vardır Rectanglesınıfın: width == height. LSP'ye göre, Rectangleörnekler Rectanglealt sınıf örnekleriyle değiştirilmelidir. Bunun nedeni, bu örneklerin örnekler için tür denetimini geçmesi ve Rectanglekodunuzda beklenmedik hatalara neden olmalarıdır.

Bu, wiki makalesinde "bir alt türde önkoşullar güçlendirilemez" kısmına bir örnektir . Özetlemek gerekirse, LSP'yi ihlal etmek muhtemelen bir noktada kodunuzda hatalara neden olacaktır.


3

LSP, `` Nesnelerin alt türleriyle değiştirilmesi gerekir '' diyor. Öte yandan, bu ilke

Çocuk sınıfları asla ana sınıfın tür tanımlarını kırmamalıdır.

ve aşağıdaki örnek LSP'nin daha iyi anlaşılmasına yardımcı olur.

LSP olmadan:

public interface CustomerLayout{

    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            return; //it isn`t rendered in this case
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}

LSP ile sabitleme:

public interface CustomerLayout{
    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            showAd();//it has a specific behavior based on its requirement
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}

2

Makaleyi okumanızı tavsiye ederim: İhlali Liskov İkame İlkesi (LSP) .

Orada Liskov İkame İlkesi'nin ne olduğunu, zaten ihlal edip etmediğinizi tahmin etmenize yardımcı olacak genel ipuçları ve sınıf hiyerarşinizi daha güvenli hale getirmenize yardımcı olacak bir yaklaşım örneği bulabilirsiniz.


2

LISKOV YERLEŞTİRME İLKESİ (Mark Seemann kitabından), bir arabirimin bir uygulamasını başka bir istemciyi veya uygulamayı bozmadan değiştirebileceğimizi belirtmektedir. Bugün onları öngöremiyorum.

Bilgisayarı duvardan çıkarırsak (Uygulama), ne duvar prizi (Arayüz) ne de bilgisayar (İstemci) bozulur (aslında, bir dizüstü bilgisayarsa, bir süre pilleri üzerinde bile çalışabilir) . Bununla birlikte, yazılımla, istemci genellikle bir hizmetin kullanılabilir olmasını bekler. Hizmet kaldırıldı, bir NullReferenceException alır. Bu tür bir durumla başa çıkmak için, “hiçbir şey” yapmayan bir arayüz uygulaması oluşturabiliriz. Bu, Boş Nesne olarak bilinen bir tasarım modelidir [4] ve kabaca bilgisayarın duvardan çıkarılmasına karşılık gelir. Gevşek kuplaj kullandığımız için, gerçek bir uygulamayı sorun yaratmadan hiçbir şey yapmayan bir şeyle değiştirebiliriz.


2

Likov'un Değiştirme Prensibi , bir program modülü bir Base sınıfı kullanıyorsa, Base sınıfına yapılan başvurunun, program modülünün işlevselliğini etkilemeden Türetilmiş bir sınıfla değiştirilebileceğini belirtir.

Niyet - Türetilmiş türler, taban türleri için tamamen ikame edilebilir olmalıdır.

Örnek - Java'daki ko-varyant dönüş türleri.


1

İşte bir alıntıdır bu yazı o açıklık getirmektedir şeyler güzel:

[..] bazı ilkeleri kavramak için, ne zaman ihlal edildiğini anlamak önemlidir. Şimdi yapacağım şey bu.

Bu ilkenin ihlali ne anlama geliyor? Bir nesnenin, bir arayüzle ifade edilen bir soyutlamanın getirdiği sözleşmeyi yerine getirmediği anlamına gelir. Başka bir deyişle, soyutlamalarınızı yanlış belirlediğiniz anlamına gelir.

Aşağıdaki örneği düşünün:

interface Account
{
    /**
     * Withdraw $money amount from this account.
     *
     * @param Money $money
     * @return mixed
     */
    public function withdraw(Money $money);
}
class DefaultAccount implements Account
{
    private $balance;
    public function withdraw(Money $money)
    {
        if (!$this->enoughMoney($money)) {
            return;
        }
        $this->balance->subtract($money);
    }
}

Bu bir LSP ihlali mi? Evet. Bunun nedeni, hesabın sözleşmesinin bize bir hesabın geri çekileceğini söylemesi, ancak durum her zaman böyle değildir. Düzeltmek için ne yapmalıyım? Sadece sözleşmeyi değiştiriyorum:

interface Account
{
    /**
     * Withdraw $money amount from this account if its balance is enough.
     * Otherwise do nothing.
     *
     * @param Money $money
     * @return mixed
     */
    public function withdraw(Money $money);
}

Voilà, şimdi sözleşme yerine getirildi.

Bu ince ihlal genellikle bir müşteriye kullanılan somut nesneler arasındaki farkı söyleme yeteneği kazandırır. Örneğin, ilk Hesabın sözleşmesi göz önüne alındığında, aşağıdaki gibi görünebilir:

class Client
{
    public function go(Account $account, Money $money)
    {
        if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
            return;
        }
        $account->withdraw($money);
    }
}

Ve bu, otomatik olarak açık-kapalı prensibini (yani para çekme gereksinimi) ihlal eder. Çünkü sözleşmeyi ihlal eden bir nesnenin yeterli parası yoksa ne olacağını asla bilemezsiniz. Muhtemelen hiçbir şey döndürmez, muhtemelen bir istisna atılır. Yani kontrol etmelisinhasEnoughMoney() bir arayüzün parçası olmayan - . Yani bu zorla somut sınıfa bağımlı kontrol bir OCP ihlalidir].

Bu nokta ayrıca LSP ihlali hakkında sık sık karşılaştığım bir yanlış anlama da giderir. “Bir ebeveynin çocukta davranışı değiştiyse, o zaman LSP'yi ihlal eder” der. Bununla birlikte, bir çocuk ebeveyninin sözleşmesini ihlal etmediği sürece değildir.

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.