স্বাগতম! আপনি এখন আপনার কোর্সের Section 23: SOLID Principles এর একদম শেষ লেকচারে আছেন। গত লেকচারে আমরা Inheritance ব্যবহার করে Open/Closed Principle (OCP) ইমপ্লিমেন্ট করেছিলাম। আজকে আমরা শিখব Liskov Substitution Principle (LSP) সম্পর্কে, এবং জানব কেন গত লেকচারের Inheritance পদ্ধতিটি রিয়েল-ওয়ার্ল্ড প্রজেক্টে বিপজ্জনক হতে পারে। এই লেকচারটি আপনার আর্কিটেকচারাল নলেজকে আরও এক ধাপ এগিয়ে নিয়ে যাবে। চলুন শুরু করি!
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:
- Core Concept (LSP): Parent class কে Child class দিয়ে রিপ্লেস করলে (Substitute) অ্যাপ্লিকেশনের স্বাভাবিক আচরণ ব্রেক করা যাবে না।
- Rule 1 (Same Return Value): ইনপুট একই হলে Parent এবং Child class এর মেথডকে অবশ্যই একই ধরনের/লজিকের ভ্যালু রিটার্ন করতে হবে।
- Rule 2 (No New Exceptions): Child class এর মেথড থেকে এমন কোনো নতুন Exception থ্রো করা যাবে না, যা Parent class এর মেথডে ছিল না।
- Rule 3 (No Stricter Validation): Child class এ প্যারামিটারের উপর নতুন কোনো কঠোর (Strict) Validation Rule আরোপ করা যাবে না।
- The Problem in our Project: আমাদের
PersonsGetterServiceChildক্লাসটি Excel ফাইলে কম ডাটা রিটার্ন করছিল এবং লিস্ট এম্পটি হলে নতুন Exception থ্রো করছিল, যা সরাসরি LSP ভায়োলেট করে। - The Solution: Inheritance পরিহার করে আগের লেকচারে দেখানো Interface Implementation পদ্ধতিটি ব্যবহার করা, কারণ Interface এর ক্ষেত্রে LSP ভায়োলেট হওয়ার সুযোগ নেই।
🧠 Comprehensive Breakdown
লেকচারের প্রতিটি বিষয় নিচে বিস্তারিতভাবে আলোচনা করা হলো এবং বোঝার সুবিধার্থে প্রাসঙ্গিক Code Implementation যুক্ত করা হলো।
1. What is the Liskov Substitution Principle (LSP)? [Priority: 10/10]
কনসেপ্ট: SOLID এর ‘L’ হলো Liskov Substitution Principle (LSP)। এর মূল কথা হলো: আপনি যদি কোনো Parent class কে Inherit করে একটি Child class তৈরি করেন এবং কোনো মেথডকে Override করেন, তবে অ্যাপ্লিকেশনটির আচরণ কোনোভাবেই পরিবর্তন হওয়া উচিত নয়। অর্থাৎ, Parent class এর Object এর জায়গায় Child class এর Object বসালেও অ্যাপ্লিকেশন আগের মতোই নির্বিঘ্নে কাজ করবে।
Why it matters: C# এ আমরা Dependency Injection এর মাধ্যমে പല সময় Base class বা Interface এর ভেরিয়েবলে Derived (Child) class এর Object অ্যাসাইন করি (Polymorphism)। এখন Child class যদি অপ্রত্যাশিত কোনো আচরণ করে, তবে পুরো অ্যাপ্লিকেশন ব্রেক করতে পারে এবং নতুন Bug তৈরি হতে পারে।
2. How Child Classes Violate LSP (The 3 Rules) [Priority: 10/10]
লেকচারার খুব সুন্দরভাবে ব্যাখ্যা করেছেন যে, মূলত ৩টি কারণে একটি Child class LSP ভঙ্গ বা ভায়োলেট করে:
কারণ ১: Different Return Values for Same Inputs যদি ইনপুট একই থাকা সত্ত্বেও Child class ভিন্ন লজিকের রেজাল্ট দেয়।
- Math Example: Parent class এর মেথড
aএবংbযোগ করে (a + b = 50)। কিন্তু Child class ওভাররাইড করে গুণফল রিটার্ন করে (a * b = 50)। এটি LSP ভায়োলেশন।
কারণ ২: Introducing New Exceptions Child class যদি এমন কোনো Exception থ্রো করে, যার জন্য Parent class কে ডিজাইন করা হয়নি।
- Math Example: Parent class শুধু চেক করে ইনপুট
nullকি না (ArgumentNullException)। কিন্তু Child class নতুন রুল বানালো যে ভ্যালু নেগেটিভ হওয়া যাবে না, হলে সেArgumentExceptionথ্রো করবে। এটি LSP ভায়োলেশন।
কারণ ৩: Stricter Validation Rules Child class এ প্যারামিটারের উপর এমন কোনো নতুন ভ্যালিডেশন দেওয়া (যেমন: স্ট্রিং এর লেন্থ মিনিমাম ৫ হতেই হবে), যা Parent class এ ছিল না।
// ❌ Example of LSP Violation in Math Operations
public class Calculator
{
public virtual int Calculate(int a, int b) => a + b; // Parent returns Sum
}
public class BadCalculator : Calculator
{
public override int Calculate(int a, int b)
{
if(a < 0 || b < 0) throw new ArgumentException("Negative not allowed"); // LSP Violation: New Exception
return a * b; // LSP Violation: Different Logic/Return type
}
}
3. Analyzing LSP Violation in Our Existing Project [Priority: 9/10]
গত লেকচারে আমরা PersonsGetterService কে Inherit করে PersonsGetterServiceChild বানিয়েছিলাম এবং GetPersonsExcel মেথডটি Override করেছিলাম। চলুন দেখি সেখানে কীভাবে LSP ভায়োলেট হয়েছে:
- Return Value Mismatch: Parent class এর
GetPersonsExcelমেথড Excel ফাইলে Name, Email, DOB সহ সব ফিল্ড রিটার্ন করত। কিন্তু আমাদের Child class শুধুমাত্র Name, Age এবং Gender রিটার্ন করছে। ইউজারের রিকোয়ারমেন্ট পূরণ হলেও, এটি আর্কিটেকচারালি LSP ভায়োলেট করেছে। - New Exceptions in Child: ধরুন, Child class এর
GetAllPersonsমেথডে আমরা একটি লজিক দিলাম যে, যদি Person এর কাউন্ট 0 হয়, তবেInvalidOperationExceptionথ্রো করো। কিন্তু Parent class এ এমন কোনো রুল ছিল না। এটিও সরাসরি LSP ভায়োলেশন।
// ❌ LSP Violation in our project
public class PersonsGetterServiceChild : PersonsGetterService
{
public override async Task<List<PersonResponse>> GetAllPersons()
{
var list = await base.GetAllPersons();
// LSP Violation: Parent never threw this exception, but child is throwing it!
if(list.Count == 0) throw new InvalidOperationException("List cannot be empty");
return list;
}
}
4. The Ultimate Solution: Interfaces Over Inheritance [Priority: 10/10]
Why: যেহেতু Inheritance এবং Overriding ব্যবহার করলে LSP ভায়োলেট হওয়ার বিশাল সম্ভাবনা থাকে, তাই রিয়েল-ওয়ার্ল্ড প্রজেক্টে Inheritance পরিহার করা উচিত।
The Fix: এর সমাধান হলো গত লেকচারের আগের লেকচারে দেখানো Interface ইমপ্লিমেন্টেশন পদ্ধতিটি ব্যবহার করা। আমরা যদি সরাসরি IPersonsGetterService ইন্টারফেস ইমপ্লিমেন্ট করে একটি নতুন ক্লাস (PersonsGetterServiceWithFewExcelFields) বানাই, তবে সেখানে Inheritance এর কোনো অস্তিত্বই থাকে না। ফলে LSP ভায়োলেট হওয়ার কোনো সুযোগই নেই!
Action (Updating the Code):
আমাদের ConfigureServicesExtension.cs ফাইলে গিয়ে Child class টি বাদ দিয়ে আগের Interface implementation টি রেজিস্টার করতে হবে।
// Inside ConfigureServicesExtension.cs
// ❌ Avoid the child class to prevent LSP issues
// services.AddScoped<IPersonsGetterService, PersonsGetterServiceChild>();
// ✅ Safely use the Alternative Interface Implementation
services.AddScoped<IPersonsGetterService, PersonsGetterServiceWithFewExcelFields>();
Note: লেকচারার বলেছেন আপনি চাইলে ডেমোনস্ট্রেশনের জন্য তৈরি করা Child class টি প্রজেক্ট থেকে সম্পূর্ণ Delete করে দিতে পারেন।
5. Interview Tips for SOLID Principles [Priority: 10/10]
SOLID Principles ইন্টারভিউর জন্য অত্যন্ত গুরুত্বপূর্ণ।
- ইন্টারভিউয়াররা শুধু সংজ্ঞা জানতে চায় না, তারা কনফিউজিং প্রশ্ন করে রিয়েল-ওয়ার্ল্ড ইমপ্লিমেন্টেশন যাচাই করতে চায়।
- তাই আপনাকে প্রতিটি Principle এর What (কী), How (কীভাবে ইমপ্লিমেন্ট করতে হয়) এবং Why (কেন এটি ব্যবহার করব) — এই ৩টি বিষয় খুব ভালোভাবে মেমোরাইজ করতে হবে।
🌟 Best Practices
- Favor Composition over Inheritance: Object-Oriented ডিজাইনের একটি গোল্ডেন রুল হলো Inheritance এর বদলে Composition (Interfaces/Dependency Injection) ব্যবহার করা। এটি আপনার কোডকে Flexible রাখে এবং LSP ভায়োলেশন থেকে বাঁচায়।
- Design by Contract: Interface ব্যবহার করলে একটি Contract তৈরি হয়। যে ক্লাসটি ইন্টারফেস ইমপ্লিমেন্ট করে, সে শুধু নিজের কোড নিয়ে ভাবে, কোনো Parent class এর লজিক ব্রেক করার ভয় থাকে না।
🚀 .NET 10 Context
.NET 10 এ Dependency Injection এবং SOLID প্রিন্সিপালগুলো ইমপ্লিমেন্ট করা আগের চেয়ে অনেক বেশি রিডেবল। Primary Constructors ব্যবহার করে আমরা খুব সহজেই Delegation বা Composition (যা LSP ভায়োলেট করে না) অ্যাচিভ করতে পারি।
// .NET 10 Smart Implementation (Alternative to Inheritance)
// Using Composition (Delegation) to safely modify behavior without breaking LSP
public class SafePersonsGetterService(PersonsGetterService originalService) : IPersonsGetterService
{
// Delegating unmodified behavior
public async Task<List<PersonResponse>> GetAllPersons() => await originalService.GetAllPersons();
// Providing NEW behavior safely without inheritance
public MemoryStream GetPersonsExcel()
{
// Logic for specific excel fields
return new MemoryStream();
}
}
SOLID Principles সফলভাবে শেষ করার জন্য অভিনন্দন! পরবর্তী সেকশনে (Clean Architecture) যাওয়ার জন্য প্রস্তুত হোন!