2.2.6 · Coding › Design Principles
Intuition Ek sentence mein idea
Agar code ek base class expect karta hai, toh tum usse koi bhi subclass de sako aur program bina surprises ke kaam karta rahe. Ek subclass ek promise hai: "Jo kuch mere parent ne kiya, main bhi wahi kar sakta hoon, bilkul waise jaise tum expect karte ho."
Intuition Ye principle kyun zaroori hai
Inheritance + polymorphism ka poora point yahi hai: code ek baar base type ke against likho, aur woh sabhi subtypes ke liye kaam kare. Yeh tabhi faydemand hai jab subtypes sach mein base type jaisa behave karein.
Agar koi subtype secretly contract tod de (unexpected errors throw kare, weird values return kare, kuch aisa karne se mana kare jo base kar sakti thi), toh har caller ko if (it's actually a Square) ... jaise checks daalne padte hain. Isse polymorphism barbad ho jaata hai — wahi cheez jo inheritance se milni chahiye thi.
SOLID ka L Barbara Liskov (1987) se aaya hai. Unka formal statement:
Definition Liskov Substitution Principle
Maano ϕ ( x ) ek aisi property hai jo type T ke objects x ke baare mein prove ki ja sakti hai. Toh ϕ ( y ) bhi sach hona chahiye type S ke objects y ke liye, jahan S , T ka subtype hai.
Seedhi baat: superclass ke objects ko subclass ke objects se replace kiya ja sake bina program ki correctness todte.
Ek subclass jo method override kare, usse behavioural contract ka respect karna hoga, sirf method signature ka nahi. Teen rules hain:
Intuition Rules ko scratch se derive karo
Socho ek caller jo base ke against likha gaya hai. Caller sirf base contract jaanta hai. Woh:
aisi inputs bhejega jo pre_base satisfy karti hain
aisi outputs par rely karega jo post_base satisfy karti hain
Precondition: Caller promise karta hai ki inputs pre_base mein honge. Agar subclass zyada maange (ek stronger precondition), toh caller — jo iske baare mein kuch nahi jaanta — ek ab-illegal input bhej dega → crash. Isliye subclass ko har woh cheez accept karni chahiye jo pre_base allow karta hai. Hence pre_base ⇒ pre_sub. ✅
Postcondition: Caller post_base par rely karta hai. Agar subclass kam guarantee kare, toh caller ka baad ka code toot jaata hai. Isliye subclass ko kam se kam post_base rakhna chahiye. Hence post_sub ⇒ post_base. ✅
Dhyaan do arrows opposite direction mein point karte hain — preconditions wide hoti hain (contravariant), postconditions narrow hoti hain (covariant). Yeh arbitrary nahi hai; yeh seedha "ek caller ko mat chaunkao jo sirf base jaanta hai" se nikalti hai.
Worked example Square, Rectangle nahi hai (code mein)
Math kehta hai square ek rectangle hota hai. Code isse manta nahi.
class Rectangle {
protected int w, h;
void setWidth ( int w ) { this .w = w; }
void setHeight ( int h ) { this .h = h; }
int area () { return w * h; }
}
class Square extends Rectangle {
void setWidth ( int w ) { this .w = w; this .h = w; } // keeps it square
void setHeight ( int h ) { this .h = h; this .w = h; }
}
Ab ek caller jo Rectangle ke against likha gaya hai:
void test (Rectangle r) {
r. setWidth ( 5 );
r. setHeight ( 4 );
assert r. area () == 20 ; // invariant the caller relies on
}
Yeh step kyun? Caller ne ek property prove ki: "5×4 set karo → area 20 aayega." Yeh Rectangle ke liye φ(x) hai.
Ab Square pass karo: setHeight(4) width bhi reset kar deta hai → area ban jaata hai 4 × 4 = 16 , 20 nahi. Provable property fail ho gayi → LSP violated.
Kaun sa rule toota? Base ka invariant "width aur height independent hain" silently tod diya gaya. setHeight ki Postcondition weaken ho gayi (ab yeh guarantee nahi karti ki width unchanged rahegi).
Worked example Ek sahi refactor (composition / shared abstraction ko prefer karo)
Square extends Rectangle mat karo. Iske bajaye:
interface Shape { int area (); }
class Rectangle implements Shape {
private final int w, h;
Rectangle ( int w , int h ){ this .w = w; this .h = h; }
public int area (){ return w * h; }
}
class Square implements Shape {
private final int s;
Square ( int s ){ this .s = s; }
public int area (){ return s * s; }
}
Yeh step kyun? Dono ko immutable banake aur sirf area() share karke (ek contract jo dono poori tarah honour karte hain), ab koi setWidth/setHeight nahi raha jiska contract Square violate kar sake. Substitutability wapas aa gayi.
Worked example Bird / Penguin trap
class Bird { void fly (){ /*...*/ } }
class Penguin extends Bird {
void fly (){ throw new UnsupportedOperationException (); }
}
Yeh violation kyun hai: koi bhi code jo bird.fly() karta hai woh Bird ke liye sahi hai, lekin Penguin ke liye exception throw karta hai — ek nayi exception jo base contract mein nahi thi (rule 3).
Fix: hierarchy split karo: Bird aur FlyingBird extends Bird. Penguin Bird extend kare, FlyingBird nahi. Ab FlyingBird.fly() ka koi bhi caller kabhi Penguin nahi pa sakta.
Common mistake "Agar real life mein
Square IS-A Rectangle hai, toh inheritance theek hai."
Kyun sahi lagta hai: real-world taxonomy kehta hai square ek rectangle hota hai, aur OOP textbooks kehte hain "real world model karo." Isliye inheritance natural lagti hai.
Fix: LSP behavioural substitutability ke baare mein hai, real-world category ke nahi. Test yeh nahi hai ki "kya X, Y ki ek tarah ka hai?" balki "kya X woh sab kuch kar sakta hai jo Y ne promise kiya, usi tarah?" Ek mutable square setWidth ko independently honour nahi kar sakta → woh behavioural subtype nahi hai.
Common mistake "Override mein
NotImplemented throw karna acceptable hai — yeh limit document karta hai."
Kyun sahi lagta hai: yeh explicit hai aur galat use rokta hai, safe lagta hai.
Fix: yeh preconditions strengthen karta hai / unexpected exceptions add karta hai. Base ka caller kabhi ise catch karne ki plan nahi karta → runtime crash. Iske bajaye, hierarchy fix karo taaki woh type kabhi wahan pass na ho sake jahan capability required hai (Interface Segregation yahan help karta hai).
Common mistake "Override karke
narrower type ya zyada restrictions return karna hamesha achha hai (yeh stricter hai!)."
Kyun sahi lagta hai: stricter = safer, generally sach hai.
Fix: Outputs (return types, postconditions) narrow ho sakte hain (covariant ✅), lekin inputs (parameters, preconditions) narrow nahi hone chahiye — woh sirf widen ho sakte hain (contravariant). Accepted inputs ko tight karna existing callers tod deta hai.
LSP ek sentence mein Subtypes ko base type ki jagah substitute kiya ja sake bina program correctness todte.
LSP kisne formulate kiya Barbara Liskov (1987).
Kya subclass precondition strengthen kar sakta hai? Nahi — preconditions sirf weaken/widen ho sakti hain (base ke inputs se ≥ accept karna zaroori hai).
Kya subclass postcondition weaken kar sakta hai? Nahi — postconditions sirf strengthen/narrow ho sakti hain (base ke promises se ≥ guarantee karna zaroori hai).
Preconditions contravariant aur postconditions covariant kyun hoti hain? Kyunki jo caller sirf base jaanta hai woh base-legal inputs bhejta hai aur base-promised outputs par rely karta hai; inputs widening / outputs narrowing kabhi us caller ko nahi chaunkaata.
Square extends Rectangle LSP kyun violate karta hai Mutable Square par setWidth/setHeight doosri dimension bhi change kar deta hai, woh invariant tod ke jo Rectangle caller rely karta hai ki width aur height independent hain.
Penguin extends Bird (fly() ke saath) LSP violation kyun hai Penguin.fly() ek aisi exception throw karta hai jo Bird ke contract mein nahi thi (rule 3: koi nayi exception types nahi).
Ek common fix jab subtype koi method honour nahi kar sakta Hierarchy restructure karo / interfaces split karo taaki subtype kabhi wahan substitute na ho sake jahan capability required hai.
LSP kaun si feature ko actually kaam karne deta hai Polymorphism — base type ke against ek baar code likhna aur sabhi subtypes par trust karna.
Recall Feynman: 12 saal ke bachche ko samjhao
Socho ek job posting hai jo kehti hai "Humein koi chahiye jo car chalaa sake." Tum jisko bhi aate dekho use hire kar lo, trust karte hue ki woh drive kar sakta hai. LSP kehta hai: agar tum kisi ko "Driver" bolte ho, toh unhe sach mein drive karna chahiye — tumhe aisa koi nahi milna chahiye jo, jab tum unhe keys do, toh chillaye "Main drive nahi kar sakta!" ya jo sirf left turn kar sake. Ek "subclass" ek specific tarah ka Driver hai (taxi driver, race driver). Har ek ko woh sab kuch karna hoga jo ek normal Driver kar sakta hai , warna tumhara plan bigad jaata hai. Agar ek "Penguin" kehta hai woh ek "Bird hai jo fly karta hai" lekin fly nahi kar sakta, toh use woh mat kaho — use alag label do taaki koi use sky-job na de.
Mnemonic Contract directions yaad rakho
"Jo accept karo usme lenient raho, jo promise karo usme strict raho."
Inputs: wide (zyada accept karo). Outputs: narrow (zyada promise karo).
Mnemonic word: W-I-N-O — Wide In, Narrow Out .
Inheritance plus Polymorphism
Liskov Substitution Principle
Preconditions cannot strengthen
Postconditions cannot weaken
Type-check hacks everywhere