Men Liskov almashtirish printsipi (LSP) ob'ektga yo'naltirilgan dizaynning asosiy printsipi ekanligini eshitdim. Bu nima va undan foydalanishga qanday misollar bor?
Liskov almashtirish printsipiga misol nima?
Javoblar (33)
LSPni (yaqinda eshitgan podkastda Bob amaki tomonidan berilgan) ko'rsatuvchi ajoyib misol, ba'zida tabiiy tilda to'g'ri eshitiladigan narsa kodda ishlamasligi edi.
Matematikada Square
Rectangle
hisoblanadi. Darhaqiqat, bu to'rtburchakning ixtisoslashuvidir. "Is a" sizni meros bilan modellashtirishga majbur qiladi. Ammo agar siz kodda Square
ni Rectangle
dan kelib chiqqan bo'lsangiz, Square
dan Rectangle
kutilgan har qanday joyda foydalanish mumkin bo'ladi. Bu qandaydir g'alati xatti-harakatlarga olib keladi.
Tasavvur qiling-a, sizning Rectangle
tayanch sinfingizda SetWidth
va SetHeight
usullari bor edi; bu mutlaqo mantiqiy ko'rinadi. Biroq, agar Rectangle
ma'lumotnomangiz Square
ga ishora qilgan bo'lsa, SetWidth
va SetHeight
ma'nosi yo'q, chunki birini o'rnatish ikkinchisini unga mos keladigan tarzda o'zgartiradi. Bu holda Square
Liskov almashtirish testini Rectangle
bilan bajara olmaydi va Square
ni Rectangle
dan meros qilib olish abstraktsiyasi yomon.
Boshqa bebaho SOLID Principles motivatsion plakatlar.
Square.setWidth(int width)
quyidagi tarzda amalga oshirilgan bo'lsa, nima uchun muammo bo'ladi: this.width = width; this.height = width;
? Bunday holda, kenglik balandlikka teng bo'lishi kafolatlanadi.
- person MC Emperor; 28.10.2015
Square
da setHeight()
va setWidth()
amallarini o'zgartirsangiz, shuning uchun kodingizdagi Rectangule
ishlatadigan joylar Square
dan o'tsangiz, endi ishlamaydi va bu LSP haqida asosiy nuqta;
- person sdlins; 26.12.2016
h1
ni oʻz ichiga olgan bir xil sahifani qaytaradi. Men topdim eng so'nggi WayBack Machine snapshotva unda tasvirlar CC BY-SA bo'yicha litsenziyalanganligi aniq aytilgan. WayBack Machine snapshotiga, shuningdek, asl URL manziliga havola qilishni va rasmni Stack Overflow serverlariga (muharrir orqali) yuklashni tavsiya qilaman. Shunday qilib, uzilishlar postga ta'sir qilmaydi.
- person Palec; 10.06.2017
o.setDimensions(width, height)
bu muammoni hal qiladi, lekin LSP hali ham buziladi, chunki kvadrat to'rtburchakga qaraganda kuchliroq shartlarga ega (width == height
). Sizning postingiz LSP haqida hech narsaga javob bermaydi deb o'ylamayman.
- person inf3rno; 09.10.2017
GetHeight
va GetWidth
ni ko'rishga majbur qilishi mumkin, agar ular xuddi shu narsani qilishmoqchi. Bu kichik muammo bo'lishi mumkin, lekin baribir buni boshqa yo'l bilan qilish uchun sababdir.
- person AustinWBryan; 09.05.2018
SetWidth
va SetHeight
ni chaqirishi mumkin bo'lgan SetLength
bilan
- person alancalvitti; 29.03.2019
Liskov almashtirish printsipi (LSP, lsp) Ob'ektga yo'naltirilgan dasturlash kontseptsiyasida quyidagilar aytiladi:
Ko'rsatkichlar yoki asosiy sinflarga havolalardan foydalanadigan funksiyalar hosila sinflari ob'ektlarini bilmagan holda ishlata olishi kerak.
LSP asosan interfeyslar va shartnomalar, shuningdek, sinfni qachon kengaytirish va maqsadingizga erishish uchun kompozitsiya kabi boshqa strategiyadan foydalanishni qanday hal qilish haqida.
Bu fikrni tasvirlashning eng samarali usuli Head First OOA&Dda bo'ldi. Ular strategiya o'yinlari uchun asos yaratish bo'yicha loyihada ishlab chiquvchi bo'lgan stsenariyni taqdim etadilar.
Ular shunday ko'rinishdagi taxtani ifodalovchi sinfni taqdim etadilar:
Barcha usullar X va Y koordinatalarini Tiles
ikki o'lchovli massivdagi plitka o'rnini aniqlash uchun parametr sifatida qabul qiladi. Bu o'yinni ishlab chiquvchiga o'yin davomida doskadagi birliklarni boshqarish imkonini beradi.
Kitobda o'yin ramkasi ishi parvozga ega o'yinlarni joylashtirish uchun 3D o'yin taxtalarini ham qo'llab-quvvatlashi kerakligini aytish uchun talablarni o'zgartiradi. Shunday qilib, Board
ni kengaytiruvchi ThreeDBoard
sinfi kiritildi.
Bir qarashda bu yaxshi qarorga o'xshaydi. Board
Height
va Width
xossalarini va ThreeDBoard
Z o'qini ta'minlaydi.
U Board
dan meros qolgan barcha boshqa a'zolarni ko'rganingizda buziladi. AddUnit
, GetTile
, GetUnits
va boshqalar uchun usullar Board
sinfida X va Y parametrlarini oladi, lekin ThreeDBoard
uchun ham Z parametr kerak.
Shunday qilib, siz ushbu usullarni Z parametri bilan qayta qo'llashingiz kerak. Z parametri Board
sinfiga kontekstga ega emas va Board
sinfidan meros qilib olingan usullar o'z ma'nosini yo'qotadi. ThreeDBoard
sinfini Board
asosiy klassi sifatida ishlatishga urinayotgan kod birligi omadsiz bo'ladi.
Ehtimol, biz boshqa yondashuvni topishimiz kerak. Board
ni kengaytirish o'rniga ThreeDBoard
Board
ob'ektdan iborat bo'lishi kerak. Z o'qi birligi uchun bitta Board
ob'ekt.
Bu bizga inkapsulyatsiya va qayta foydalanish kabi ob'ektga yo'naltirilgan yaxshi tamoyillardan foydalanishga imkon beradi va LSPni buzmaydi.
GetUnits(int x, int y)
ning imzosini GetUnits(Position pos)
ga o'zgartirish orqali amalga oshirish mumkin va xuddi shu narsa boshqa funktsiyalar uchun ham amal qiladi. Shunday qilib, u LSPni buzmaydi. Agar xato qilsam, meni tuzating.
- person du369; 22.04.2020
O'rnini bosish - bu ob'ektga yo'naltirilgan dasturlash printsipi bo'lib, agar kompyuter dasturida agar S T ning kichik turi bo'lsa, u holda T tipidagi ob'ektlar S tipidagi ob'ektlar bilan almashtirilishi mumkin.
Keling, Java-da oddiy misol keltiramiz:
Yomon misol
public class Bird{
public void fly(){}
}
public class Duck extends Bird{}
O'rdak qush bo'lgani uchun ucha oladi, lekin bu haqda nima deyish mumkin:
public class Ostrich extends Bird{}
Tuyaqush qush, lekin u ucha olmaydi, Tuyaqush klassi Qushlar sinfining kichik turi, lekin u chivin usulidan foydalana olmasligi kerak, demak biz LSP tamoyilini buzyapmiz.
Yaxshi namuna
public class Bird{}
public class FlyingBirds extends Bird{
public void fly(){}
}
public class Duck extends FlyingBirds{}
public class Ostrich extends Bird{}
Bird bird
bo'lsa nima qilasiz. Chivindan foydalanish uchun ob'ektni FlyingBirds-ga tashlashingiz kerak, bu yaxshi emasmi?
- person Moody; 20.11.2017
Bird bird
bo'lsa, demak u fly()
dan foydalana olmaydi. Bo'ldi shu. Duck
belgisidan o'tish bu faktni o'zgartirmaydi. Agar mijozda FlyingBirds bird
bo'lsa, u Duck
dan o'tgan bo'lsa ham, u har doim bir xil tarzda ishlashi kerak.
- person Steve Chamaillard; 18.02.2018
LSP invariantlarga tegishli.
Klassik misol quyidagi psevdokod deklaratsiyasi bilan berilgan (amalga oshirishlar o'tkazib yuborilgan):
class Rectangle {
int getHeight()
void setHeight(int value) {
postcondition: width didn’t change
}
int getWidth()
void setWidth(int value) {
postcondition: height didn’t change
}
}
class Square extends Rectangle { }
Endi interfeys mos kelsa-da, bizda muammo bor. Sababi, biz kvadrat va to'rtburchaklarning matematik ta'rifidan kelib chiqadigan invariantlarni buzdik. Qabul qiluvchilar va sozlagichlarning ishlash usuli, Rectangle
quyidagi invariantni qondirishi kerak:
void invariant(Rectangle r) {
r.setHeight(200)
r.setWidth(100)
assert(r.getHeight() == 200 and r.getWidth() == 100)
}
Biroq, bu o'zgarmas (shuningdek, aniq postshartlar) Square
ning to'g'ri bajarilishi bilan buzilishi kerak, shuning uchun u Rectangle
ning haqiqiy o'rnini bosmaydi.
Square
ni e'lon qilishning to'g'ri yo'li qanday?
- person ca9163d9; 24.01.2012
setHeight()
va setWidth()
usullari mavjud bo'lishiga yo'l qo'ymaslik kerak, chunki bu sinfni o'zgaruvchan qiladi. Biroq, agar ular mavjud bo'lsa, faqat throw NotSupportedException() / NotImplementedException()
yoki shunga o'xshash narsa bo'lishi mumkin.
- person Leonid; 03.07.2012
square
bilan bu har doim ham to'g'ri emas, ikkala o'rnatuvchi ham kenglik va balandlikni tiklashi kerak, aks holda ular kvadratning (ko'zda tutilgan) invariantlarini saqlab qolmaydi. Boshqacha qilib aytganda: sizda nomuvofiq turdagi tizim mavjud va siz buni hech qachon xohlamaysiz.
- person Konrad Rudolph; 06.06.2013
SetArea(4); SetPerimeter(10);
oddiy to'rtburchak modelni 1x4 to'rtburchak qiladi. Qo'ng'iroqlar Square
sinf modelini avval 2x2 qiladi, keyin esa sqrt(10)xsqrt(10) kvadratga o'zgaradi yoki InvalidOperationException
yoki boshqa narsalarni tashlaydi.
- person Wolfzoon; 07.08.2016
SetDepth(int d) => throw exception
bo'lishi mumkin. Lekin men hayronman... to'rtburchak H=W ni talab qilmaydigan maxsus kvadrat turimi? Shu bilan bir qatorda..To‘rtburchak ⇒ Trapetsiya ⇒ Paralelogramma.
- person Paulustrious; 05.08.2017
Quadrilateral ⇒ Trapezoid ⇒ Parallelogram
. To'rtburchak va kvadrat oxir-oqibat hosil bo'ladi. Paralelogrammadan, garchi Paralelogram ⇒ Romb ⇒ Kvadrat bo'lar edi. O'zingizning hosilalaringizni to'g'ri olish ma'lumotlar bazasi jadvallarini loyihalash kabidir. Boshida xatoga yo'l qo'ying va siz buzg'unchilikka ketyapsiz
- person Paulustrious; 05.08.2017
Shape
yoki Parallelogram
yoki boshqa narsalarni meros qilib olgan birodarlar sinflari sifatida saqlash yaxshiroqmi?
- person AustinWBryan; 09.05.2018
Square
ni Rectangle
dan modellashtirish uchun polimorfik yondashuv shart bo'lmasa, men Rectangle
ob'ektini oladigan SquareDecorator
ni qo'llab-quvvatlayman. Siz yoki boshqa birov bu haqda qanday fikrda?
- person Reuel Ribeiro; 11.01.2019
Shape
dan meros bo'lishi mumkin bo'lgan ortogonal sinflarni taqdim etish yaxshiroqdir. Ya'ni, sizning yondashuvingiz, albatta, ko'plab real hayotda foydalanish holatlarida to'g'ri echimdir.
- person Konrad Rudolph; 11.01.2019
Rectangle
tayanch sinf sozlagichlariga tasdiqlar qo'shish orqali buni aniq qilishingiz mumkin. Ammo kvadratlarning boshqa so'zsizkontraktga ega bo'lishi aynan nima uchun bu LSP buzilishidir: ikkalasi ham yakka holda amal qiladi, lekin kvadrat to'rtburchakning to'g'ri kichik turi emas, chunki ularning yashirin shartnomalari. t mos keladi.
- person Konrad Rudolph; 13.10.2020
Rectangle
sinfi haqidagi deklaratsiyamga aniq postshartlarni qo'shdim. Ammo adolatli ogohlantirish: men ularni yana olib tashlashim mumkin, chunki ular sun'iy va har qanday holatda invariant
misol usuli bilan olingan. Bundan tashqari, LSP o'zining asl ta'rifiga ko'ra, nafaqat aniq emas, balki yashirin invariantlarga ham tegishli. Shartlarsiz asl misol LSPning asl, rasmiy ta'rifiga juda mos edi.
- person Konrad Rudolph; 13.10.2020
setHeight()
va setWidth()
mustaqil operatsiyalar bo'lganligi sababli, ularning har biri o'z-o'zidan hech qanday invariantning buzilishi emasligini ta'kidlashim mumkin. Mustaqil operatsiyalarni bitta operatsiya bo'lishga majburlamoqchi bo'lganingizdagina, sizning fikringizcha, yashirin o'zgarmas narsaga bo'lgan qarashingiz ko'rinadi. Shunday qilib, eng yaxshi holatda, bu LSP buzilishi uchun zaif holat
- person wired_in; 19.10.2020
Robert Martin ajoyib qog'ozga ega. Liskov almashtirish printsipi. Bu tamoyilni buzish mumkin bo'lgan nozik va unchalik nozik bo'lmagan usullarni muhokama qiladi.
Qog'ozning ba'zi tegishli qismlari (ikkinchi misol juda zichlashtirilganligiga e'tibor bering):
LSP buzilishining oddiy misoli
Ushbu printsipning eng ko'zga ko'ringan buzilishidan biri ob'ekt turiga qarab funktsiyani tanlash uchun C++ Run-Time Type Information (RTTI) dan foydalanishdir. ya'ni:
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)); }
Shubhasiz,
DrawShape
funksiyasi yomon shakllangan. UShape
sinfining barcha mumkin bo'lgan hosilalari haqida bilishi kerak va har safarShape
ning yangi hosilalari yaratilganda uni o'zgartirish kerak. Haqiqatan ham, ko'pchilik bu funktsiyaning tuzilishini ob'ektga yo'naltirilgan dizayn uchun antema deb biladi.Kvadrat va to'rtburchak, yanada nozik qoidabuzarlik.
Biroq, LSPni buzishning boshqa, ancha nozik usullari mavjud. Quyida tavsiflanganidek
Rectangle
sinfidan foydalanadigan dasturni ko'rib chiqing: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; };
[...] Tasavvur qiling-a, bir kun foydalanuvchilar to'rtburchaklar bilan bir qatorda kvadratlarni ham manipulyatsiya qilish qobiliyatini talab qilishadi. [...]
Shubhasiz, kvadrat barcha oddiy niyat va maqsadlar uchun to'rtburchakdir. ISA munosabatlari mavjud bo'lganligi sababli,
Square
sinfiniRectangle
dan olingan deb modellashtirish mantiqan to'g'ri. [...]
Square
SetWidth
vaSetHeight
funksiyalarini meros qilib oladi. Bu funksiyalarSquare
uchun mutlaqo mos emas, chunki kvadratning kengligi va balandligi bir xil. Bu dizayn bilan bog'liq muammo borligini ko'rsatadigan muhim ishora bo'lishi kerak. Biroq, muammoni chetlab o'tishning bir yo'li bor. BizSetWidth
vaSetHeight
ni bekor qilishimiz mumkin [...]Ammo quyidagi funktsiyani ko'rib chiqing:
void f(Rectangle& r) { r.SetWidth(32); // calls Rectangle::SetWidth }
Agar biz
Square
ob'ektiga havolani ushbu funktsiyaga o'tkazsak,Square
ob'ekti buziladi, chunki balandlik o'zgarmaydi. Bu LSPning aniq buzilishi. Funktsiya uning argumentlarining hosilalari uchun ishlamaydi.[...]
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.
Agar bolalar sinfining oldingi sharti ota-onalar sinfining oldingi shartidan kuchliroq bo'lsa, siz shartni buzmasdan bolani ota-onaga almashtira olmaysiz. . Shuning uchun LSP.
- person user2023861; 11.02.2015
LSP ba'zi kodlar T
turdagi usullarni chaqiradi deb o'ylasa, kerak bo'ladi va S
turdagi usullarni bilmagan holda chaqirishi mumkin, bu erda S extends T
(ya'ni, S
T
supertipini meros qilib oladi, undan kelib chiqadi yoki uning pastki turi hisoblanadi).
Masalan, T
tipidagi kirish parametriga ega funksiya S
turdagi argument qiymati bilan chaqirilganda (ya'ni chaqirilganda) bu sodir bo'ladi. Yoki T
tipidagi identifikatorga S
tipidagi qiymat tayinlangan bo'lsa.
val id : T = new S() // id thinks it's a T, but is a S
LSP T
(masalan, Rectangle
) tipidagi usullar uchun kutishlarni (ya'ni o'zgarmaslarni) talab qiladi, buning o'rniga S
(masalan, Square
) tipidagi usullar chaqirilganda buzilmasligi kerak.
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
Hatto oʻzgarmas maydonlari boʻlgan tur ham oʻzgarmaslarga ega, masalan. o'zgarmas To'rtburchak o'rnatuvchilar o'lchamlarni mustaqil ravishda o'zgartirishni kutadilar, ammo o'zgarmas Kvadrat o'rnatuvchilar bu taxminni buzadi.
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 S
kichik turining har bir usuli kontravariant kirish parametr(lar)i va kovariant chiqishiga ega bo'lishini talab qiladi.
Kontravariant dispersiya meros yoʻnalishiga zid ekanligini bildiradi, yaʼni S
kichik turidagi har bir kirish parametrining Si
turi bir xil boʻlishi yoki Ti
turidagi supertip boʻlishi kerak. supertipning mos keladigan usulining mos keladigan kirish parametri T
.
Kovariatsiya deganda dispersiya merosning bir xil yo‘nalishida bo‘ladi, ya’ni So
turi, S
kichik turining har bir usulining chiqishi bir xil bo‘lishi yoki mos keladigan To
tipidagi kichik turi bo‘lishi kerak. supertipning mos keladigan usulining chiqishi T
.
Buning sababi, agar qo'ng'iroq qiluvchi uni T
turiga ega deb hisoblasa, u T
usulini chaqiradi deb o'ylasa, u Ti
tipidagi argument(lar)ni taqdim etadi va chiqishni To
turiga tayinlaydi. U haqiqatda S
ning mos keladigan usulini chaqirganda, har bir Ti
kirish argumenti Si
kirish parametriga, So
chiqishi esa To
turiga tayinlanadi. Shunday qilib, agar Si
kontravariant bo'lmasa, w.r.t. Ti
ga, keyin esa Xi
kichik turi - bu Si
ning pastki turi bo'lmaydi - Ti
ga tayinlanishi mumkin.
Bundan tashqari, turdagi polimorfizm parametrlari (ya'ni, generiklar) bo'yicha ta'rif joyidagi tafovut izohlariga ega bo'lgan tillar uchun (masalan, Scala yoki Seylon) T
turdagi har bir parametr uchun dispersiya izohining ko- yoki qarama-qarshi yo'nalishi tur parametrining turiga ega bo'lgan har bir kirish parametri yoki chiqishiga (T
ning har bir usulidan) mos ravishda qarama-qarshi yoki bir xil yo'nalish.
Bundan tashqari, funktsiya turiga ega bo'lgan har bir kirish parametri yoki chiqish uchun talab qilinadigan farq yo'nalishi teskari bo'ladi. Ushbu qoida rekursiv ravishda qo'llaniladi.
Subtyping mos keladi bu erda o'zgarmaslarni sanab o'tish mumkin.
Invariantlarni kompilyator tomonidan amalga oshirilishi uchun qanday modellashtirish bo'yicha ko'plab tadqiqotlar olib borilmoqda.
Typestate (3-betga qarang) e'lon qiladi va amalga oshiradi holat invariantlari turiga ortogonal. Shu bilan bir qatorda, invariantlarni tasdiqlarni turlarga aylantirish orqali amalga oshirish mumkin. Masalan, faylni yopishdan oldin ochiqligini tasdiqlash uchun File.open() faylda mavjud bo'lmagan close() usulini o'z ichiga olgan OpenFile turini qaytarishi mumkin. Yana bir misol tic-tac-toe API bo'lishi mumkin. kompilyatsiya vaqtida invariantlarni qo'llash uchun yozishdan foydalanish. Turing tizimi hatto Turing-to'liq bo'lishi mumkin, masalan. Scala. Bog'liq terilgan tillar va teorema isbotlari yuqori tartibli tiplash modellarini rasmiylashtiradi.
mavhum kengaytma uchun semantika zaruriyati tufayli men shuni kutaman. Invariantlarni modellashtirish uchun yozishdan foydalanish, ya'ni birlashtirilgan yuqori tartibli denotatsion semantika Typestatedan ustundir. "Kengaytma" muvofiqlashtirilmagan, modulli rivojlanishning cheklanmagan, almashtirilgan tarkibini anglatadi. Menimcha, kengayadigan kompozitsiya uchun bir-biri bilan birlashtirilib bo'lmaydigan umumiy semantikani ifodalash uchun ikkita o'zaro bog'liq modelga (masalan, turlar va Typestate) ega bo'lish, menimcha, birlashma va shuning uchun erkinlik darajalariga qarama-qarshidir. . Masalan, Ifoda muammosiga o‘xshash kengaytma subtiplash, funksiyani ortiqcha yuklash va parametrik yozishda birlashtirilgan. domenlar.
Mening nazariy pozitsiyam shundaki, bilim mavjud bo'lishi ( "Markaziylashtirish ko'r va yaroqsiz" bo'limiga qarang), Turing-to'liq kompyuter tilida barcha mumkin bo'lgan invariantlarni 100% qamrab oladigan umumiy model hech qachon bo'lmaydi. Bilim mavjud bo'lishi uchun kutilmagan imkoniyatlar juda ko'p, ya'ni tartibsizlik va entropiya doimo ortib borishi kerak. Bu entropik kuch. Potensial kengaytmaning barcha mumkin bo'lgan hisoblarini isbotlash barcha mumkin bo'lgan kengaytmalarni apriori hisoblashdir.
Shuning uchun to'xtash teoremasi mavjud, ya'ni Turing-to'liq dasturlash tilidagi barcha mumkin bo'lgan dastur tugashi yoki tugashi aniq emas. Ba'zi bir maxsus dastur tugashini isbotlash mumkin (barcha imkoniyatlar aniqlangan va hisoblangan). Ammo bu dasturning barcha mumkin bo'lgan kengaytmalari tugashini isbotlash mumkin emas, agar ushbu dasturni kengaytirish imkoniyatlari Turing to'liq bo'lmasa (masalan, bog'liq bo'lgan yozish orqali). Turing-to'liqligi uchun asosiy talab cheklanmagan rekursiya bo'lgani uchun, Gödelning to'liqsizlik teoremalari va Rassell paradoksining kengaytmaga qanday tatbiq etilishini tushunish oson.
Ushbu teoremalarning talqini ularni entropik kuchning umumlashtirilgan kontseptual tushunchasiga kiritadi:
- Godelning to'liqsizlik teoremalari: barcha arifmetik haqiqatlar isbotlanishi mumkin bo'lgan har qanday rasmiy nazariya mos kelmaydi.
- Rasselning paradoksi: har bir a'zolik qoidasi to'plamni o'z ichiga olishi mumkin bo'lgan to'plam, har bir a'zoning o'ziga xos turini sanab o'tadi yoki o'zini o'z ichiga oladi. Shunday qilib, to'plamlarni kengaytirib bo'lmaydi yoki ular cheksiz rekursiyadir. Masalan, choynak bo'lmagan hamma narsaning to'plami o'zini o'z ichiga oladi, o'zini o'z ichiga oladi, o'zini o'z ichiga oladi va hokazo. Shunday qilib, qoida (to'plamni o'z ichiga olishi mumkin va) o'ziga xos turlarni sanab o'tmasa (ya'ni, barcha noaniq turlarga ruxsat bersa) va cheksiz kengaytirishga ruxsat bermasa, nomuvofiq hisoblanadi. Bu o'z a'zolari bo'lmagan to'plamlar to'plami. Barcha mumkin bo'lgan kengaytmalar bo'yicha ham izchil va to'liq sanab bo'lmaydigan bu Gödelning to'liqsizlik teoremasidir.
- Liskovni almashtirish printsipi: umuman olganda, biron bir to'plam boshqasining pastki to'plami bo'ladimi yoki yo'qmi - bu hal qilib bo'lmaydigan muammo, ya'ni merosxo'rlikni umuman hal qilib bo'lmaydi.
- Linskiyga havola qilish: biror narsa tasvirlangan yoki idrok etilganda, uni hisoblash nima ekanligini hal qilib bo'lmaydi, ya'ni idrok (haqiqat) mutlaq murojaat nuqtasiga ega emas.
- Kouz teoremasi: hech qanday tashqi mos yozuvlar nuqtasi yo'q, shuning uchun cheksiz tashqi imkoniyatlar uchun har qanday to'siq barbod bo'ladi.
- Termodinamikaning ikkinchi qonuni: butun olam (yopiq tizim, ya'ni hamma narsa) maksimal tartibsizlikka, ya'ni maksimal mustaqil imkoniyatlarga moyillik.
Men har bir javobda to'rtburchaklar va kvadratlarni va LSPni qanday buzishni ko'raman.
LSP ni haqiqiy misol bilan qanday qilib moslashtirish mumkinligini ko'rsatmoqchiman:
<?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;
}
}
Ushbu dizayn LSP ga mos keladi, chunki biz tanlagan dasturdan qat'iy nazar xatti-harakatlar o'zgarishsiz qoladi.
Va ha, siz ushbu konfiguratsiyadagi LSPni bitta oddiy o'zgartirish orqali buzishingiz mumkin:
<?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 !
}
}
Endi kichik turlardan bir xil tarzda foydalanish mumkin emas, chunki ular endi bir xil natijani bermaydilar.
Database::selectQuery
semantikasini cheklab qo'ysak, misol LSPni buzmaydi. Bu deyarli amaliy emas... Ya'ni, misolni tushunish bu erda qo'llanilgan ko'pchilikka qaraganda osonroq.
- person Palec; 25.02.2018
Liskovni buzgan yoki yo'qligini aniqlash uchun nazorat ro'yxati mavjud.
- Agar siz quyidagi bandlardan birini buzsangiz -› siz Liskovni buzasiz.
- Agar siz hech qanday qoidani buzmasangiz -› hech narsa xulosa qila olmaysiz.
Tekshirish roʻyxati:
Olingan sinfda hech qanday yangi istisnolar kiritilmasligi kerak: Agar sizning asosiy sinfingiz ArgumentNullException-ni tashlagan bo'lsa, quyi sinflaringizga faqat ArgumentNullException turidagi istisnolarni yoki ArgumentNullException-dan olingan har qanday istisnolarni tashlashga ruxsat berilgan. IndexOutOfRangeExceptionni tashlash Liskov qoidasini buzish hisoblanadi.
Oldin shartlarni kuchaytirib bo'lmaydi: Sizning asosiy sinfingiz int a'zosi bilan ishlaydi deb faraz qiling. Endi sizning pastki turingiz bu int ijobiy bo'lishini talab qiladi. Bu kuchaytirilgan oldingi shartlar va endi salbiy ints bilan oldin juda yaxshi ishlagan har qanday kod buziladi.
Post-shartlarni zaiflashtirib bo'lmaydi: Tasavvur qiling-a, sizning asosiy sinfingiz ma'lumotlar bazasiga barcha ulanishlar qaytarilishidan oldin yopilishi kerak. O'zingizning kichik sinfingizda siz ushbu usulni bekor qildingiz va ulanishni qayta ishlatish uchun ochiq qoldirdingiz. Siz ushbu usulning keyingi shartlarini zaiflashtirdingiz.
Invariantlar saqlanishi kerak: Bajarish eng qiyin va og'riqli cheklov. Invariantlar ba'zan asosiy sinfda yashiringan va ularni ochishning yagona yo'li asosiy sinf kodini o'qishdir. Asosan, siz biron bir usulni bekor qilganingizda, bekor qilingan usulingiz bajarilgandan keyin o'zgarmas narsa o'zgarishsiz qolishi kerakligiga ishonch hosil qilishingiz kerak. Men o'ylashim mumkin bo'lgan eng yaxshi narsa bu o'zgarmas cheklovlarni asosiy sinfda qo'llashdir, ammo bu oson bo'lmaydi.
Tarix cheklovi: Usulni bekor qilishda sizga asosiy sinfdagi o'zgartirilmaydigan xususiyatni o'zgartirishga ruxsat berilmaydi. Ushbu kodni ko'rib chiqing va siz Ism o'zgarmas deb belgilanganligini ko'rishingiz mumkin (shaxsiy to'plam), lekin SubType uni o'zgartirishga imkon beruvchi yangi usulni taqdim etadi (aks ettirish orqali):
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); } }
Yana 2 ta element mavjud: Usul argumentlarining kontravariatsiyasi va Qaytish turlarining kovariatsiyasi. Lekin C# da bu mumkin emas (men C# dasturchisiman), shuning uchun men ularga ahamiyat bermayman.
Uzoqning qisqasi, keling, to'rtburchaklar to'rtburchaklar va kvadratchalar qoldiraylik, ota-klassni kengaytirishda amaliy misol, siz aniq asosiy API-ni SAQLASH yoki UNNI KENGAYTISH kerak.
Aytaylik, sizda baza ItemsRepository bor.
class ItemsRepository
{
/**
* @return int Returns number of deleted rows
*/
public function delete()
{
// perform a delete query
$numberOfDeletedRows = 10;
return $numberOfDeletedRows;
}
}
Va uni kengaytiradigan kichik sinf:
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;
}
}
Shunda siz Base ItemsRepository API bilan ishlaydigan va unga tayanadigan Mijozga ega bo'lishingiz mumkin.
/**
* 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();
}
}
LSPni almashtirishda ota-ona sinfni quyi sinf bilan API shartnomasini buzganda buziladi.
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 :(
}
}
Siz mening kursimda qo'llab-quvvatlanadigan dasturiy ta'minotni yozish haqida ko'proq bilib olishingiz mumkin: https://www.udemy.com/enterprise-php/
LSP - bu to'g'ridan-to'g'ri shartnoma bo'yicha qoida: agar asosiy sinf shartnomani qondirsa, LSP tomonidan olingan sinflar ham ushbu shartnomani qondirishi kerak.
Pseudo-python tilida
class Base:
def Foo(self, arg):
# *... do stuff*
class Derived(Base):
def Foo(self, arg):
# *... do stuff*
Agar Foo-ni Derived ob'ektida har safar chaqirganingizda, arg bir xil bo'lsa, asosiy ob'ektda Foo-ga qo'ng'iroq qilish bilan bir xil natijalarni beradi, agar LSP-ni qondiradi.
Bu siz o'ylaganingizdan ancha sodda:
usr->next = group->users;
group->users = usr;
- person Charlie Martin; 04.07.2012
2 + "2"
ni sinab ko'ring). Balki siz qattiq terilgan bilan statik terilganni chalkashtirasizmi?
- person asmeurer; 20.01.2013
u'hello'
ni 'world'
bilan birlashtira olasiz, deb bahslashayotganini hech qachon ko'rmaganman) va ba'zan boshqa ko'p narsalardan birini anglatadi. en.wikipedia.org/wiki/Strong_and_weak_typing
- person Mark Amery; 27.12.2013
2 + '2'
tilini qo‘llab-quvvatlasa, u hali ham 2 + 'two'
tilini qo‘llab-quvvatlamaydi.
- person asmeurer; 27.12.2013
O'ylaymanki, hamma LSP nima ekanligini texnik jihatdan yoritadi: siz asosan subtip tafsilotlaridan mavhum bo'lishni va supertiplardan xavfsiz foydalanishni xohlaysiz.
Shunday qilib, Liskovning uchta asosiy qoidasi bor:
Imzo qoidasi: subtipdagi har bir supertip operatsiyasining sintaktik tarzda to'g'ri bajarilishi kerak. Biror narsa kompilyator sizni tekshira oladi. Kamroq istisnolardan voz kechish va hech bo'lmaganda supertip usullari kabi foydalanish mumkin bo'lishi haqida bir oz qoida mavjud.
Usullar qoidasi: Ushbu operatsiyalarni amalga oshirish semantik jihatdan to'g'ri.
- Weaker Preconditions : The subtype functions should take at least what the supertype took as input, if not more.
- Kuchliroq keyingi shartlar: ular ishlab chiqarilgan supertipli usullarning bir qismini ishlab chiqarishi kerak.
Xususiyatlar qoidasi: Bu individual funktsiya chaqiruvlaridan tashqariga chiqadi.
- Invariants : Things that are always true must remain true. Eg. a Set's size is never negative.
- Evolyutsion xususiyatlar : Odatda biror narsa o'zgarmasligi yoki ob'ekt qanday holatda bo'lishi mumkinligi bilan bog'liq. Yoki ob'ekt faqat o'sadi va hech qachon qisqarmaydi, shuning uchun subtip usullari uni yaratmasligi kerak.
Bu xususiyatlarning barchasi saqlanishi kerak va qo'shimcha pastki turdagi funksionallik supertip xususiyatlarini buzmasligi kerak.
Agar ushbu uchta narsaga e'tibor berilsa, siz asosiy narsadan mavhum bo'lgansiz va siz erkin bog'langan kod yozyapsiz.
Manba: Java-da dastur ishlab chiqish - Barbara Liskov
Keling, Java-da tasvirlab beraylik:
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() { ... }
}
Bu erda hech qanday muammo yo'q, to'g'rimi? Avtomobil, albatta, transport vositasidir va bu erda biz o'z super klassining startEngine() usulini bekor qilishini ko'rishimiz mumkin.
Keling, boshqa transport vositasini qo'shamiz:
class Bicycle extends TransportationDevice
{
@Override
void startEngine() /*problem!*/
}
Hozir hammasi rejalashtirilganidek bo'lmayapti! Ha, velosiped transport vositasidir, ammo uning dvigateli yo'q, shuning uchun startEngine() usulini amalga oshirib bo'lmaydi.
Bu Liskov almashtirish printsipining buzilishiga olib keladigan muammolardir va ular odatda hech narsa qilmaydigan yoki hatto amalga oshirib bo'lmaydigan usul bilan tan olinishi mumkin.
Ushbu muammolarni hal qilish to'g'ri meros ierarxiyasidir va bizning holatlarimizda biz dvigatelli va dvigatelsiz transport vositalarining sinflarini farqlash orqali muammoni hal qilamiz. Velosiped transport vositasi bo'lsa ham, uning dvigateli yo'q. Ushbu misolda transport qurilmasiga bizning ta'rifimiz noto'g'ri. Uning dvigateli bo'lmasligi kerak.
TransportationDevice sinfimizni quyidagicha refaktor qilishimiz mumkin:
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
}
Endi biz TransportationDevice-ni motorsiz qurilmalar uchun kengaytirishimiz mumkin.
class DevicesWithoutEngines extends TransportationDevice
{
void startMoving() { ... }
}
Va motorli qurilmalar uchun TransportationDevice-ni kengaytiring. Bu erda Dvigatel ob'ektini qo'shish maqsadga muvofiqdir.
class DevicesWithEngines extends TransportationDevice
{
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
Shunday qilib, bizning Avtomobil sinfimiz Liskov almashtirish printsipiga rioya qilgan holda yanada ixtisoslashgan bo'ladi.
class Car extends DevicesWithEngines
{
@Override
void startEngine() { ... }
}
Va bizning velosiped sinfimiz Liskov almashtirish printsipiga mos keladi.
class Bicycle extends DevicesWithoutEngines
{
@Override
void startMoving() { ... }
}
Ko'rsatkichlar yoki asosiy sinflarga havolalardan foydalanadigan funksiyalar hosila sinflari ob'ektlarini bilmagan holda ishlata olishi kerak.
Men LSP haqida birinchi marta o'qiganimda, men bu juda qat'iy ma'noda nazarda tutilgan deb o'ylagan edim, asosan uni interfeysni amalga oshirish va turdagi xavfsiz kasting bilan tenglashtirdi. Bu LSP tilning o'zi tomonidan ta'minlangan yoki ta'minlanmaganligini anglatadi. Misol uchun, bu qat'iy ma'noda, ThreeDBoard, albatta, kompilyatorga kelsak, Board o'rnini bosadi.
Kontseptsiya haqida ko'proq o'qib chiqqanimdan so'ng, men LSP odatda undan kengroq talqin qilinishini topdim.
Muxtasar qilib aytganda, mijoz kodi uchun ko'rsatgich orqasidagi ob'ekt ko'rsatgich turi emas, balki hosila turi ekanligini "bilish" nimani anglatadi, bu xavfsizlik turi bilan cheklanmaydi. LSP ga rioya qilish ob'ektlarning haqiqiy xatti-harakatlarini tekshirish orqali ham tekshiriladi. Ya'ni, ob'ekt holati va usul argumentlarining metod chaqiruvlari natijalariga ta'sirini yoki ob'ektdan tashlangan istisnolar turlarini o'rganish.
Yana misolga qaytadigan bo'lsak,nazariy jihatdanThreeDBoard-da kengash usullarini juda yaxshi ishlashi mumkin. Amalda esa, ThreeDBoard qo'shmoqchi bo'lgan funksionallikni buzmasdan, mijoz to'g'ri bajara olmaydigan xatti-harakatlardagi farqlarning oldini olish juda qiyin bo'ladi.
Ushbu bilim bilan LSPga rioya qilishni baholash merosdan ko'ra mavjud funksionallikni kengaytirish uchun qachon mosroq mexanizm ekanligini aniqlashda ajoyib vosita bo'lishi mumkin.
LSP danfoydalanishning muhim misoli dasturiy ta'minotni sinovdan o'tkazishdadir.
Agar menda B ning LSP-mos keladigan kichik sinfi bo'lgan A sinfim bo'lsa, men A testini o'tkazish uchun B test to'plamidan qayta foydalanishim mumkin.
A kichik sinfini to'liq sinab ko'rish uchun men yana bir nechta test holatlarini qo'shishim kerak, lekin hech bo'lmaganda barcha supersinf B test holatlarini qayta ishlatishim mumkin.
Buni tushunishning bir yo'li MakGregor "sinov uchun parallel ierarxiya" deb atagan narsani yaratishdir: Mening ATest
sinfim BTest
sinfidan meros bo'lib qoladi. Sinov ishining B tipidagi emas, balki A tipidagi ob'ektlar bilan ishlashini ta'minlash uchun in'ektsiyaning qandaydir shakli kerak bo'ladi (oddiy shablon usuli namunasi ishlaydi).
Shuni esda tutingki, barcha kichik sinf ilovalari uchun super-testlar to'plamidan qayta foydalanish, aslida, ushbu kichik sinf ilovalari LSP-mosligini tekshirishning bir usuli hisoblanadi. Shunday qilib, har qanday kichik sinf kontekstida supersinf test to'plaminiishlatishkerakligi haqida ham bahslashish mumkin.
Shuningdek, Stackoverflow savoliga "Interfeys amalga oshirilishini sinab ko‘rish uchun qayta foydalanish mumkin bo‘lgan bir qator testlarni amalga oshira olamanmi?"ga qarang.
Juda oddiy jumlada aytishimiz mumkin:
Bolalar sinfi o'zining asosiy sinf xususiyatlarini buzmasligi kerak. U bunga qodir bo'lishi kerak. Aytishimiz mumkinki, bu subtyping bilan bir xil.
Liskov almashtirish printsipi
- Bekor qilingan usul bo'sh qolmasligi kerak
- Bekor qilingan usul xatoga yo'l qo'ymasligi kerak
- Asosiy sinf yoki interfeys harakati, olingan sinf xatti-harakatlari tufayli o'zgartirish (qayta ishlash) uchun ketmasligi kerak.
LSP ning ushbu formulasi juda kuchli:
Agar S tipidagi har bir o1 ob'ekti uchun T tipidagi o2 ob'ekti mavjud bo'lsa, T bilan belgilangan barcha P dasturlar uchun o2 o'rniga o1 o'rniga P ning xatti-harakati o'zgarmaydi, u holda S T ning kichik turidir.
Bu, asosan, S bu T bilan bir xil narsaning boshqa, to'liq qamrab olingan amalga oshirilishini anglatadi. Va men dadil bo'lishim va ishlash P xatti-harakatining bir qismi ekanligiga qaror qilishim mumkin ...
Shunday qilib, asosan, kechikishning har qanday ishlatilishi LSPni buzadi. Bir turdagi ob'ektni boshqa turdagi ob'ektga almashtirganimizda, boshqa xatti-harakatlarga ega bo'lish OO'ning butun maqsadidir!
wikipedia tomonidan keltirilgan formula yaxshiroq, chunki xususiyat kontekstga bog'liq va barchani o'z ichiga olmaydi. dasturning xatti-harakati.
Liskovning almashtirish printsipi (LSP)
Biz har doim dastur modulini loyihalashtiramiz va ba'zi sinf ierarxiyalarini yaratamiz. Keyin biz ba'zi hosila sinflarni yaratib, ba'zi sinflarni kengaytiramiz.
Biz yangi olingan sinflar eski sinflarning funksiyalarini almashtirmasdan kengaytirilishiga ishonch hosil qilishimiz kerak. Aks holda, yangi sinflar mavjud dastur modullarida foydalanilganda istalmagan effektlarni keltirib chiqarishi mumkin.
Liskovning almashtirish printsipida aytilishicha, agar dastur moduli Base sinfidan foydalansa, u holda Base sinfiga havolani dastur modulining funksionalligiga ta'sir qilmasdan Derived sinfiga almashtirish mumkin.
Misol:
Quyida Liskovning almashtirish printsipi buzilgan klassik misol keltirilgan. Misolda 2 ta sinfdan foydalanilgan: To'rtburchak va Kvadrat. Faraz qilaylik, Rectangle obyekti ilovaning biror joyidan foydalanilgan. Biz ilovani kengaytiramiz va Square sinfini qo'shamiz. Kvadrat sinf ba'zi shartlarga asoslanib zavod namunasi bilan qaytariladi va biz qaysi turdagi ob'ekt qaytarilishini aniq bilmaymiz. Lekin biz bu to'rtburchak ekanligini bilamiz. Biz to'rtburchak ob'ektni olamiz, kengligi 5 ga va balandlikni 10 ga o'rnatamiz va maydonni olamiz. Kengligi 5 va balandligi 10 bo'lgan to'rtburchak uchun maydon 50 bo'lishi kerak. Buning o'rniga natija 100 bo'ladi.
// 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.
}
}
Xulosa:
Bu tamoyil faqat Ochiq yopish printsipining kengaytmasi bo'lib, biz yangi olingan sinflar o'z xatti-harakatlarini o'zgartirmasdan asosiy sinflarni kengaytirayotganiga ishonch hosil qilishimiz kerakligini anglatadi.
Shuningdek qarang: Ochiq yopish printsipi
Yaxshiroq tuzilish uchun shunga o'xshash tushunchalar: Konfiguratsiya bo'yicha konventsiya
LSP oddiy tilda bir xil ob'ektlarni superklass hech narsani buzmasdan bir-biri bilanalmashtirishga ega bo'lishi kerak.
Misol uchun, agar bizda Animal
sinfidan olingan Cat
va Dog
sinflari bo'lsa, Animal sinfidan foydalanadigan har qanday funktsiyalar Cat
yoki Dog
dan foydalanishi va odatdagidek harakat qilishi kerak.
Ushbu tamoyil 1987 yildaBarbara Liskov tomonidan kiritilgan va supersinf va uning kichik turlarining xatti-harakatlariga e'tibor qaratish orqali Ochiq-yopiq printsipni kengaytiradi.
Uni buzish oqibatlarini hisobga olsak, uning ahamiyati ayon bo'ladi. Quyidagi sinfdan foydalanadigan dasturni ko'rib chiqing.
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;
}
}
}
Tasavvur qiling-a, bir kun mijoz to'rtburchaklar bilan bir qatorda kvadratlarni ham manipulyatsiya qilish qobiliyatini talab qiladi. Kvadrat to'rtburchak bo'lgani uchun kvadrat sinfi Rectangle sinfidan olinishi kerak.
public class Square : Rectangle
{
}
Biroq, bu bilan biz ikkita muammoga duch kelamiz:
Kvadrat to'rtburchakdan meros qilib olingan balandlik va kenglik o'zgaruvchilariga muhtoj emas va agar biz yuz minglab kvadrat ob'ektlarni yaratishimiz kerak bo'lsa, bu xotirada sezilarli chiqindilarni keltirib chiqarishi mumkin. To'g'ri to'rtburchakdan meros qilib olingan kenglik va balandlik o'rnatuvchisi xususiyatlari kvadrat uchun mos emas, chunki kvadratning kengligi va balandligi bir xil. Ikkala balandlik va kenglikni bir xil qiymatga o'rnatish uchun biz ikkita yangi xususiyatni quyidagicha yaratishimiz mumkin:
public class Square : Rectangle
{
public double SetWidth
{
set
{
base.Width = value;
base.Height = value;
}
}
public double SetHeight
{
set
{
base.Height = value;
base.Width = value;
}
}
}
Endi, kimdir kvadrat ob'ektning kengligini o'rnatsa, uning balandligi mos ravishda o'zgaradi va aksincha.
Square s = new Square();
s.SetWidth(1); // Sets width and height to 1.
s.SetHeight(2); // sets width and height to 2.
Keling, oldinga siljiymiz va ushbu boshqa funktsiyani ko'rib chiqamiz:
public void A(Rectangle r)
{
r.SetWidth(32); // calls Rectangle.SetWidth
}
Agar biz kvadrat ob'ektga havolani ushbu funktsiyaga o'tkazsak, biz LSPni buzgan bo'lamiz, chunki funktsiya uning argumentlarining hosilalari uchun ishlamaydi. Kenglik va balandlik xususiyatlari polimorf emas, chunki ular to'rtburchakda virtual deb e'lon qilinmaydi (kvadrat ob'ekt buziladi, chunki balandlik o'zgartirilmaydi).
Biroq, sozlagich xususiyatlarini virtual deb e'lon qilish orqali biz yana bir qoidabuzarlikka, OCPga duch kelamiz. Darhaqiqat, olingan sinf kvadratining yaratilishi asosiy sinf to'rtburchagida o'zgarishlarga olib keladi.
A()
usulida siz r.Width = 32;
ni nazarda tutdingiz deb o'ylayman, chunki Rectangle
da SetWidth()
usuli yo'q.
- person wired_in; 13.10.2020
Ba'zi qo'shimchalar:
Nima uchun hech kim Invariant , oldingi shartlar va olingan sinflar tomonidan bajarilishi kerak bo'lgan asosiy sinfning post shartlari haqida yozmaganiga hayronman. Olingan D sinfi B asosiy sinfiga to'liq mos kelishi uchun D sinfi ma'lum shartlarga bo'ysunishi kerak:
- Asosiy sinfning in-variantlari olingan sinf tomonidan saqlanishi kerak
- Asosiy sinfning oldingi shartlari olingan sinf tomonidan mustahkamlanmasligi kerak
- Asosiy sinfning keyingi shartlari olingan sinf tomonidan zaiflashmasligi kerak.
Shunday qilib, olingan asosiy sinf tomonidan qo'yilgan yuqoridagi uchta shartdan xabardor bo'lishi kerak. Demak, subtiplash qoidalari oldindan hal qilinadi. Bu shuni anglatadiki, "IS A" munosabatlari faqat kichik turdagi ma'lum qoidalarga bo'ysunganda bo'ysunadi. Invariantlar, oldingi shartlar va keyingi shartlar ko'rinishidagi ushbu qoidalar rasmiy "dizayn shartnomasi'.
Bu haqda keyingi muhokamalar mening blogimda mavjud: Liskov almashtirish printsipi
Kvadrat - kengligi balandlikka teng bo'lgan to'rtburchak. Agar kvadrat kenglik va balandlik uchun ikki xil o'lchamni o'rnatsa, u kvadrat invariantini buzadi. Bu yon ta'sirlarni kiritish orqali ishlaydi. Ammo agar to'rtburchakda 0 ‹ balandlik va 0 ‹ kenglik sharti bilan belgilangan o'lcham (balandlik, kenglik) bo'lsa. Olingan subtip usuli balandligi == kenglikni talab qiladi; kuchliroq shart (va bu lspni buzadi). Bu shuni ko'rsatadiki, kvadrat to'rtburchak bo'lsa-da, u haqiqiy kichik tur emas, chunki old shart mustahkamlangan. Atrofdagi ish (umuman yomon narsa) yon ta'sirga olib keladi va bu post holatini zaiflashtiradi (bu lspni buzadi). Bazadagi setWidth post sharti 0 ‹ kengligiga ega. Olingan uni balandligi == kengligi bilan zaiflashtiradi.
Shuning uchun o'lchami o'zgartiriladigan kvadrat o'lchami o'zgartiriladigan to'rtburchak emas.
Kengashlar qatori nuqtai nazaridan ThreeDBoardni amalga oshirish foydali bo'ladimi?
Ehtimol, siz ThreeDBoard bo'laklarini turli tekisliklarda taxta sifatida ko'rib chiqishni xohlashingiz mumkin. Bunday holda siz bir nechta ilovalarga ruxsat berish uchun Kengash uchun interfeysni (yoki mavhum sinfni) abstrakt qilishni xohlashingiz mumkin.
Tashqi interfeys nuqtai nazaridan siz TwoDBoard va ThreeDBoard uchun Board interfeysini hisobga olishingiz mumkin (yuqoridagi usullardan hech biri mos kelmasa ham).
LSP uchun men hozirgacha aniqlagan eng aniq tushuntirish "Liskov almashtirish printsipi shuni ko'rsatadiki, olingan sinf ob'ekti tizimda hech qanday xatolikka yo'l qo'ymasdan yoki asosiy sinfning xatti-harakatlarini o'zgartirmasdan asosiy sinf ob'ektini almashtirishi kerak. " dan bu yerda. Maqolada LSPni buzish va uni tuzatish uchun kod misoli keltirilgan.
Aytaylik, biz kodimizda to'rtburchakdan foydalanamiz
r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);
Geometriya darsimizda biz kvadrat to'rtburchakning alohida turi ekanligini bilib oldik, chunki uning kengligi balandligi bilan bir xil uzunlikda. Keling, ushbu ma'lumotlarga asoslanib, Square
sinfini ham yarataylik:
class Square extends Rectangle {
setDimensions(width, height){
assert(width == height);
super.setDimensions(width, height);
}
}
Agar biz birinchi kodimizdagi Rectangle
ni Square
bilan almashtirsak, u buziladi:
r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);
Buning sababi, Square
ning Rectangle
sinfida bizda mavjud bo'lmagan yangi old sharti bor: width == height
. LSPga ko'ra Rectangle
misollari Rectangle
pastki sinf misollari bilan almashtirilishi kerak. Buning sababi shundaki, bu misollar Rectangle
misollar uchun turdagi tekshiruvdan o'tadi va shuning uchun ular sizning kodingizda kutilmagan xatolarga olib keladi.
Bu "bir kichik turda old shartlarni kuchaytirib bo'lmaydi" qismiga misol bo'ldi. >wiki-maqola. Xulosa qilib aytadigan bo'lsak, LSPni buzish, ehtimol bir nuqtada kodingizda xatolarga olib kelishi mumkin.
LSPning ta'kidlashicha, "Ob'ektlar o'zlarining kichik turlari bilan almashtirilishi kerak". Boshqa tomondan, bu printsipga ishora qiladi
Bolalar sinflari hech qachon ota-onalar sinfining ta'riflarini buzmasligi kerak.
va quyidagi misol LSPni yaxshiroq tushunishga yordam beradi.
LSPsiz:
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 tomonidan tuzatish:
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();
}
Men sizga maqolani o'qishni taklif qilaman: Liskov almashtirish printsipini buzish (LSP).
U erda siz Liskov almashtirish printsipi nima ekanligini tushuntirishni, uni allaqachon buzgan yoki yo'qligini taxmin qilishga yordam beradigan umumiy maslahatlarni va sinf ierarxiyasini yanada xavfsizroq qilishga yordam beradigan yondashuv namunasini topishingiz mumkin.
LISKOV ALMASH PRINCIPI (Mark Seemann kitobidan) biz mijoz yoki amalga oshirishni buzmasdan interfeysning bir ilovasini boshqasi bilan almashtirishimiz kerakligini aytadi. Aynan shu printsip kelajakda yuzaga keladigan talablarni hal qilishga imkon beradi, hatto imkonimiz bo'lsa ham. Bugun ularni oldindan ko'ra olmayman.
Agar kompyuterni devordan uzib qo'ysak (Implementation), na devor rozetkasi (Interfeys) ham, na kompyuter (Mijoz) buzilmaydi (aslida, agar u noutbuk bo'lsa, u hatto ma'lum vaqt davomida batareyasi bilan ham ishlashi mumkin) . Biroq, dasturiy ta'minot bilan mijoz ko'pincha xizmat mavjudligini kutadi. Agar xizmat o'chirilgan bo'lsa, biz NullReferenceException olamiz. Ushbu turdagi vaziyatni hal qilish uchun biz "hech narsa" qilmaydigan interfeysni amalga oshirishimiz mumkin. Bu Null Object[4] deb nomlanuvchi dizayn naqshidir va u taxminan kompyuterni devordan uzib qo'yishga mos keladi. Biz bo'sh ulanishdan foydalanayotganimiz sababli, biz haqiqiy dasturni muammo tug'dirmasdan hech narsa qilmaydigan narsa bilan almashtirishimiz mumkin.
Likovning almashtirish printsipida aytilishicha, agar dastur moduli Base sinfdan foydalanayotgan bo'lsa, u holda Base sinfiga havola dastur modulining funksionalligiga ta'sir qilmasdan Derived sinfiga almashtirilishi mumkin.
Maqsad - Olingan turlar ularning asosiy turlarini to'liq almashtira olishi kerak.
Misol - Java-da ko-variant qaytish turlari.
Mana ushbu postdan parcha. narsalarni yaxshi tushuntiradi:
[...] Ba'zi tamoyillarni tushunish uchun, qachon buzilganligini tushunish muhimdir. Men hozir shunday qilaman.
Ushbu tamoyilning buzilishi nimani anglatadi? Bu ob'ekt interfeys bilan ifodalangan mavhumlik bilan yuklangan shartnomani bajarmasligini anglatadi. Boshqacha qilib aytganda, bu sizning abstraksiyalaringizni noto'g'ri aniqlaganingizni anglatadi.
Quyidagi misolni ko'rib chiqing:
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 LSPni buzishmi? Ha. Buning sababi, hisob shartnomasida hisob yechib olinishi haqida aytilgan, ammo bu har doim ham shunday emas. Xo'sh, uni tuzatish uchun nima qilishim kerak? Men faqat shartnomani o'zgartiraman:
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à, endi shartnoma qanoatlantirildi.
Ushbu nozik qoidabuzarlik ko'pincha mijozga qo'llaniladigan aniq ob'ektlar orasidagi farqni aytib berish qobiliyatiga ega bo'ladi. Masalan, birinchi Hisob shartnomasini hisobga olsak, u quyidagicha ko'rinishi mumkin:
class Client
{
public function go(Account $account, Money $money)
{
if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
return;
}
$account->withdraw($money);
}
}
Va bu avtomatik ravishda ochiq-yopiq tamoyilni buzadi [ya'ni, pulni yechib olish talabi uchun. Chunki shartnomani buzgan ob'ektda pul yetarli bo'lmasa nima bo'lishini hech qachon bilmaysiz. Ehtimol, u hech narsa qaytarmaydi, ehtimol istisno tashlanadi. Shunday qilib, siz hasEnoughMoney()
yoki interfeysning bir qismi emasligini tekshirishingiz kerak. Shunday qilib, bu majburiy beton sinfga bog'liq tekshiruv OCP buzilishidir].
Bu nuqta, shuningdek, LSP buzilishi haqida tez-tez duch keladigan noto'g'ri tushunchaga ham tegishli. Unda aytilishicha, "agar ota-onaning xatti-harakati bolada o'zgargan bo'lsa, bu LSPni buzadi". Biroq, bunday emas - agar bola ota-onasining shartnomasini buzmasa.
Unda aytilishicha, agar C E ning kichik turi bo'lsa, u holda E dasturning harakatini o'zgartirmasdan yoki buzmasdan C tipidagi ob'ektlar bilan almashtirilishi mumkin. Oddiy so'zlar bilan aytganda, olingan sinflar o'zlarining ota-onalari o'rnini bosadigan bo'lishi kerak. Misol uchun, agarFermerning o'g'lidehqon bo'lsa, u otasining o'rnida ishlashi mumkin, lekinFermerning o'g'likriketchi bo'lsa, u o'z o'rnida ishlay olmaydi. ota.
Qoidabuzarlik misoli:
public class Plane{
public void startEngine(){}
}
public class FighterJet extends Plane{}
public class PaperPlane extends Plane{}
Berilgan misolda FighterPlane
va PaperPlane
sinflari startEngine()
usulini o'z ichiga olgan Plane
sinfini kengaytiradi. Shunday qilib, FighterPlane
dvigatelni ishga tushirishi aniq, lekin PaperPlane
ishlay olmaydi, shuning uchun u LSP
ni buzadi.
PaperPlane
klassi Plane
sinfini kengaytirsa ham va uning o'rnini almashtirishi kerak, lekin Plane namunasi almashtirilishi mumkin bo'lgan mos ob'ekt emas, chunki qog'oz samolyot dvigatelni ishga tushira olmaydi, chunki unda yo'q. Shunday qilib, yaxshi misol bo'ladi,
Hurmatli misol:
public class Plane{
}
public class RealPlane{
public void startEngine(){}
}
public class FighterJet extends RealPlane{}
public class PaperPlane extends Plane{}
Wiki Liskov almashtirish printsipi (LSP)
Old shartlarni kichik turda kuchaytirib bo'lmaydi.
Quyi turda keyingi shartlarni zaiflashtirib bo'lmaydi.
Super tipdagi invariantlar quyi turda saqlanishi kerak.
* Old shart va keyingi shartlar function (method) types
[Swift funktsiyasi turi. Swift funktsiyasi va usuli]
//C1 <- C2 <- C3
class C1 {}
class C2: C1 {}
class C3: C2 {}
Old shartlar (masalan,
parameter type
funktsiyasi) bir xil yoki zaifroq bo'lishi mumkin. (-› C1 ga intiladi)Keyingi shartlar (masalan,
returned type
funktsiyasi) bir xil yoki kuchliroq bo'lishi mumkin (-› C3 uchun harakat qiladi)Super turdagi o'zgarmas o'zgaruvchi[Haqida] o'zgarmas bo'lishi kerak
Tezkor
class A {
func foo(a: C2) -> C2 {
return C2()
}
}
class B: A {
override func foo(a: C1) -> C3 {
return C3()
}
}
Java
class A {
public C2 foo(C2 a) {
return new C2();
}
}
class B extends A {
@Override
public C3 foo(C2 a) { //You are available pass only C2 as parameter
return new C3();
}
}
Subtiplash
- Sybtype qo'ng'iroq qiluvchidan supertipdan ko'proq narsani talab qilmasligi kerak
- Sybtype qo'ng'iroq qiluvchiga supertipdan kamroq ta'sir qilmasligi kerak
Argument turlarining kontravariatsiyasi va qaytish turining kovariatsiyasi.
- Subtipdagi metod argumentlarining kontravariatsiyasi.
- Subtipdagi qaytish turlarining kovariatsiyasi.
- Hech qanday yangi istisnolar kichik turdagi usullar bilan o'tkazilmasligi kerak, bundan mustasno, bu istisnolarning o'zi supertip usullari bilan chiqarilgan istisnolarning kichik turlari bo'lgan hollar bundan mustasno.
[Variant, kovariatsiya, ziddiyat]
Keling, sinab ko'ring, interfeysni ko'rib chiqaylik:
interface Planet{
}
Bu sinf tomonidan amalga oshiriladi:
class Earth implements Planet {
public $radius;
public function construct($radius) {
$this->radius = $radius;
}
}
Siz Yerdan quyidagi tarzda foydalanasiz:
$planet = new Earth(6371);
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
Endi Yerni kengaytiradigan yana bir sinfni ko'rib chiqing:
class LiveablePlanet extends Earth{
public function color(){
}
}
Endi LSPga ko'ra, siz Yer o'rnida LiveablePlanet-dan foydalanishingiz kerak va u tizimingizni buzmasligi kerak. Kabi:
$planet = new LiveablePlanet(6371); // Earlier we were using Earth here
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
Misollar bu yerdan olingan
Earth
plant
ning misolidir, nega u undan olingan?
- person zar; 03.05.2019