LSP és öröklődés
Olvasgatok a contract-ekről, és felvetődött bennem, hogy valami nem teljesen világos az LSP-vel kapcsolatban.
https://dlang.org/spec/contracts.html
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
Na most elvileg mindkettő azt mondja, hogy alosztály példánnyal helyettesíthető kell, hogy legyen az aktuális példány. Ahhoz viszont, hogy az alosztály példány is átmenjen minden teszten, amit az eredeti osztállyal szemben támasztottunk (feltéve, ha elég alaposan teszteljük), az kell, hogy meglévő metódust ne írjunk felül, hanem csak új metódusokat adjunk hozzá. Ha felülírunk egy meglévő metódust, pl kiveszünk egy in contract-et, amit javasolnak a contract-es leírásban, akkor az alosztály elhasal minden olyan teszten, ami az adott in contract ellenőrzését végzi.
pl:Feloldható ez az ellentmondás valahogy?
■ In, Out and Inheritance
If a function in a derived class overrides a function in its super class, then only one of the in contracts of the function and its base functions must be satisfied. Overriding functions then becomes a process of loosening the in contracts.
A function without an in contract means that any values of the function parameters are allowed. This implies that if any function in an inheritance hierarchy has no in contract, then in contracts on functions overriding it have no useful effect.
Conversely, all of the out contracts need to be satisfied, so overriding functions becomes a processes of tightening the out contracts.
If a function in a derived class overrides a function in its super class, then only one of the in contracts of the function and its base functions must be satisfied. Overriding functions then becomes a process of loosening the in contracts.
A function without an in contract means that any values of the function parameters are allowed. This implies that if any function in an inheritance hierarchy has no in contract, then in contracts on functions overriding it have no useful effect.
Conversely, all of the out contracts need to be satisfied, so overriding functions becomes a processes of tightening the out contracts.
https://dlang.org/spec/contracts.html
Liskov substitution principle[6]
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
Na most elvileg mindkettő azt mondja, hogy alosztály példánnyal helyettesíthető kell, hogy legyen az aktuális példány. Ahhoz viszont, hogy az alosztály példány is átmenjen minden teszten, amit az eredeti osztállyal szemben támasztottunk (feltéve, ha elég alaposan teszteljük), az kell, hogy meglévő metódust ne írjunk felül, hanem csak új metódusokat adjunk hozzá. Ha felülírunk egy meglévő metódust, pl kiveszünk egy in contract-et, amit javasolnak a contract-es leírásban, akkor az alosztály elhasal minden olyan teszten, ami az adott in contract ellenőrzését végzi.
pl:
class Range {
change(x,y){
assert(x<=y);
this.limits = [x,y];
}
}
class BetterRange extends Range {
change(x,y){
if (x>y)
this.limits = [y,x];
else
this.limits = [x,y];
}
}
function testRange(Range){
var range = new Range();
try {
range.change(20,10);
console.log("range validation failed by class: " + Range.name);
}
catch (e){
console.log(".");
}
}
testRange(Range); // .
testRange(BetterRange); // range validation failed by class: BetterRange
Esetleg az LSP megfogalmazása
objects in a program should
Ez alapján én úgy értem, hogy maga a forráskód jó, értelmezhető, lefordul, lefut, viszont az eredmény helyessége már egy másik téma.
Mint a strategy pattern.
Hát akkor többféleképpen is
Ha a strategyt vesszük
Az ősosztályban megvalósuló publikus metódus tesztelésére is készítenék egy külön tesztet, ami bemenetként fogadhatja a gyerek osztályok példányát, hiszen nem gyerek specifikus funkcionalitást tesztelsz, hanem ős funkciót. Ez a teszt pedig azt validálná, hogy a gyerek osztály nem teszi tönkre valamilyen módon az ős funkciót.
Ja, így a logikus.
In addition to the signature
Liskov substitution principle
A másik írásban is ugyanez
Példa
A fenti példádnál sajnos nem térsz ki arra, hogy a Range-nek mi a feladata üzleti szempontból, de gyanítható, hogy itt mindkét Range megvalósításnak egy közös, absztrakt őstől vagy interface-től kellene származnia, különben kénytelen vagy a Range bugjait és korlátozásait a leszármaztatott osztályban is lekövetni.
A fenti példában látszólag a Range egy bugját / limitációját javítod a leszármaztatott osztályban, ami azért probléma, mert simán lehet, hogy már egy rakás kód épül erre a bugra. Ha most a Te BetterRange osztályodat adod át az eredeti helyett, további bugokat fogsz előhívni, ami megint berheléshez vezet az eredeti hiba javítása helyett, további bugokat okozva, stb.
A range-nek nincs semmi
A négyzetes példád elég érdekes. Lehet egyszerre is változtatni a-t és b-t.
Nyilván ha normálisan csináljuk, akkor a Square nem származhat a Rectangle-ből, mert máshogy méretezzük, hanem talán valami absztrakt közös ősből, ha vannak közös metódusaik.
Nem
Hát pont ez az, hogy azt
Feltettem a kérdést
Link
Szégyellős vagyok.
Szerintem Bart van Ingen
Ha a kivételdobás az eredmény része, akkor nem definiálhatsz olyan leszármazottat, ami ezen lazítana (továbbá az előfeltételek már a lehető leglazábbak, mivel definiáltad az összes "valid" bemenetet, csak ezek közül némelyikre a "helyes" válasz a kivételdobás).
Ha a kivétel nem az eredmény része, akkor definiálhatsz "értelmes" eredményt az "invalid"-ra, vagy arra, hogy x > y a leszármazottban, csak akkor a tesztjeidet ennek figyelembevételével kell megírnod. Tehát az őst ne teszteld "invalid" vagy x > y bemenetre, mert az nem része a valid előfeltételnek, nem dolga lekezelni/"helyes" működést produkálnia ezekre. A leszármazottat már tesztelheted az ős összes valid inputjára, plusz azokra, amiket esetleg újként bevezetsz.
Még nem olvastam, meg most
No én elolvastam ezt: link
A -> B
és!(B -> A)
, akkor A erősebb és B gyengébb feltétel. Hoztam egy egyszerű példát is:B: x<1
Ha megvizsgáljuk a fenti fogalmakkal
A -> B
igaz és!(B -> A)
is igaz, tehát A erősebb és B gyengébb feltétel. Nézzük meg más nézőpontból is. Legyen xA az olyan x-ek halmaza, amelyekre A igaz és legyen xB az olyan x-ek halmaza, amire B igaz. Ha megnézzük xA részhalmaza xB-nek. Ilyen szempontból tehát jól gondoltam, hogy az előfeltétel lazítása azt jelenti, hogy több minden átmegy a szűrőn.Az még mindig nem jött át, hogy miért nem jó példa erre az assertion, amit írtam.
B: true
Ez alapján B gyengébb előfeltétel, mint A. No elolvasom mit írtatok.
Huhh, nekem még mindig nem
Közben agyaltam még egy sort
Ennek ismeretében viszonylag egyszerű olyan kódot írni, ami lazít az előfeltételeken és életképes marad, pl
Ezért fontos postcondition explicitté tétele is (ahelyett, hogy csak dokumentációban írnánk le), pl:
Ami érdekessége a dolognak, hogy pl:
Visszatérve az eredeti
Így az eredeti példánál maradva így újrahasznosíthatóak a tesztek:
Felreertes
if (x instanceof BetterRange)
checkeket fog implementalni. Ez pedig torekeny kodhoz fog vezetni.Alapvetoen sehogy nem uszod meg, hogy az eredeti
Range
hibas/korlatozott mukodesere epito kodhelyeket megvizsgald es egyesevel kijavitsd. Ha csak siman bepattintasz egy "javito" osztalyt, egy csomo helyen hibas mukodest fogsz eloidezni. Nem ilyen egyszeru peldanal, de egy bonyolultabbnal mar igen.Vagyis nem marad mas mint hogy csinalj egy kozos os osztalyt / interfacet, abba kiemeld az elvart mukodest, es atird az osszes helyet ahol a korlatozasra/bugra epitesz. Es ez fajni fog.
Nem tudom fancy szakszavakba onteni, mindig is fajt megjegyezni az elmeletet, de a gyakorlat ezt mutatja.
Az ilyesmi akkor szokott
Nem
Ami a Range-nél valid, az
Nem
LSP szerint nem szükséges.
Pl
Persze, a Range egy végletekig leegyszerűsített példa, de komplexebb, nagyobb alkalmazásban bármilyen apró baromságból lehetnek ordas nagy hibák, szóval számomra a tutibiztos módszer az, hogy interface, az interfaceben leírni a kívánt működés dokumentációját, és minden implementáció ehhez tartsa magát. Ha valamilyen külső szoftvert kell használni, akkor azt egy másik osztállyal elfedem és az adott osztályban lekezelem, egségesítem a külső lib baromságait.
Pont semmibe nem kerül, cserébe kevesebb fejfájás egy év múlva.
ha a hibás / nem várt
Az új osztály szempontjából ezek nem hibás értékek, tud velük dolgozni. Ha nem így lenne, akkor dobna kivételt ő is. Mint már írtam, a topic azzal foglalkozik, hogy az LSP betartásával hogyan lehet helyesen örököltetni. Az interface-ek használata nem oldja meg ezt a problémát, a kettő legalább részben független egymástól. Egy újabb példa ennek bizonyítására: az ősosztály egyik metódusa csak maximum 10 karakteres string-et fogad el, az alosztály azonos metódusa pedig hosszabb string-eket is. Az interface-nél csak a string típusra ellenőrzünk, tehát mindkét esetben átmegy az interface ellenőrzésén a 15 karakteres szöveg, és csak az ősosztály dob kivételt, mert nem képes kezelni, az alosztály gond nélkül dolgozik tovább vele.
Nem értem teljesen mi is itt
BetterRange
teljesen megfelel az LSP követelményeinek (hiszen meg van engedve, hogy fordítva kapja meg az argumentumokat). AholRange
működik, ott aBetterRange
is működni fog, arról viszont sehol sincs szó, hogy ahol aRange
nem működik, ott aBetterRange
mit fog csinálni.Ha ez utóbbira is lenne megkötés, akkor tök értelmetlen lenne öröklődésről beszélni, hiszen az pont arra van használva, hogy egy objektum viselkedését módosítsuk (erre pedig nem lenne szükség, ha kikötjük, hogy a két objektum viselkedésének meg kell egyeznie minden esetben). Ilyenkor egy interfész két implementációjáról lehet maximum szó (mindkét implementáció pontosan ugyanúgy viselkedik, így nyilván a kódban felcserélhetőek).
Ja, menetközben rájöttem én