স্বাগতম! আপনি এখন আপনার কোর্সের Section 23: SOLID Principles এর Open/Closed Principle (OCP) with Inheritance লেকচারে আছেন। গত লেকচারে আমরা দেখেছি কীভাবে Interface ব্যবহার করে OCP ইমপ্লিমেন্ট করতে হয়। আজকে আমরা দেখব OCP ইমপ্লিমেন্ট করার দ্বিতীয় পদ্ধতি— Inheritance। এই টপিকের ঠিক পরেই আমরা Liskov Substitution Principle (LSP) এ প্রবেশ করব, যার সাথে আজকের টপিকের সরাসরি সম্পর্ক রয়েছে। চলুন শুরু করি!
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:
- Two Ways for OCP: OCP ইমপ্লিমেন্ট করার দুটি প্রধান উপায় হলো Interface এবং Inheritance।
- Best Practice: Real-world প্রজেক্টে Interface ব্যবহার করাটাই সবচেয়ে বেশি রেকমেন্ডেড, কারণ এটি কম Bug তৈরি করে।
- The Inheritance Approach: Inheritance ব্যবহার করে OCP মানতে হলে পুরনো (Base) ক্লাসের মেথডগুলোকে
virtualকরতে হয়। - Child Class: Base class টিকে Inherit করে একটি Child class তৈরি করতে হয়।
- Overriding: Child class এ শুধুমাত্র যে মেথডটির ফাংশনালিটি পরিবর্তন করা দরকার, সেটিকে
overrideকিওয়ার্ড ব্যবহার করে রি-ইমপ্লিমেন্ট করতে হয়। - Constructor Chaining: Child class এর Constructor থেকে Dependency গুলোকে
baseকিওয়ার্ডের মাধ্যমে Parent class এর Constructor এ পাঠাতে হয়। - IoC Container Update:
ConfigureServicesExtensionফাইলে Base class এর বদলে Child class টিকে রেজিস্টার করতে হয়। - The Drawback: Inheritance ব্যবহার করলে পরবর্তী SOLID Principle অর্থাৎ Liskov Substitution Principle (LSP) ব্রেক হওয়ার সম্ভাবনা থাকে।
🧠 Comprehensive Breakdown
লেকচারের প্রতিটি বিষয় নিচে বিস্তারিতভাবে আলোচনা করা হলো এবং বোঝার সুবিধার্থে প্রাসঙ্গিক Code Implementation যুক্ত করা হলো।
1. Interface vs Inheritance for OCP [Priority: 8/10]
Why: OCP এর মূল লক্ষ্য হলো পুরনো কোড পরিবর্তন না করে নতুন ফিচার যোগ করা। আগের লেকচারে আমরা Interface ইমপ্লিমেন্ট করে একটি সম্পূর্ণ নতুন ক্লাস বানিয়েছিলাম। কিন্তু Inheritance ব্যবহার করে আমরা চাইলে পুরনো ক্লাসটিকেই Parent বা Base class হিসেবে ব্যবহার করতে পারি।
- Note: লেকচারার বারবার মনে করিয়ে দিয়েছেন যে, Interface approach টিই বেস্ট। তবে একজন এক্সপার্ট ডেভেলপার হিসেবে Inheritance দিয়ে কীভাবে কাজ করা যায়, সেটি জানাও (Good to know) খুবই জরুরি।
2. Making Base Class Methods Virtual [Priority: 10/10]
Why: C# এ আপনি চাইলেই যেকোনো Base class এর মেথড Child class এ পরিবর্তন করতে পারবেন না। মেথডটিকে পরিবর্তনযোগ্য করার জন্য Base class এর মেথড সিগনেচারে virtual কিওয়ার্ড যোগ করতে হয়।
Step 1: আমাদের PersonsGetterService ক্লাসের সবগুলো মেথডের নামের আগে virtual কিওয়ার্ড যোগ করতে হবে।
(VS / VS Code Shortcut: আপনি চাইলে একসাথে মাল্টিপল লাইনে কার্সর রেখে (Alt + Click) দ্রুত virtual টাইপ করতে পারেন।)
public class PersonsGetterService : IPersonsGetterService
{
// Base class constructors and fields...
// Adding 'virtual' keyword allows child classes to override this method
public virtual async Task<List<PersonResponse>> GetAllPersons() { /* ... */ }
public virtual MemoryStream GetPersonsExcel() { /* ... */ }
}
3. Creating the Child Class and Overriding [Priority: 10/10]
Why: আমরা Base class এর অন্যান্য মেথডগুলো (যেমন GetAllPersons) ঠিক রাখতে চাই, শুধু GetPersonsExcel মেথডটি আপডেট করতে চাই। তাই আমরা একটি Child class তৈরি করব এবং শুধুমাত্র টার্গেট মেথডটিকে override করব।
Step 2: PersonsGetterServiceChild নামে একটি নতুন ক্লাস তৈরি করে তাতে PersonsGetterService কে Inherit করুন।
public class PersonsGetterServiceChild : PersonsGetterService
{
// Type 'override' and press Space to see the list of virtual methods
public override MemoryStream GetPersonsExcel()
{
// Paste the UPDATED logic here (e.g., exporting fewer fields: Name, Age, Gender)
// This completely replaces the parent's implementation for this specific method.
}
}
4. Handling Base Class Dependencies in Constructor [Priority: 10/10]
Why: যখন একটি ক্লাসকে Inherit করা হয়, তখন Base class এর যদি কোনো Constructor থাকে যাতে প্যারামিটার দরকার (যেমন: Repository, Logger), তবে Child class এর দায়িত্ব হলো সেই প্যারামিটারগুলো রিসিভ করে Base class কে সাপ্লাই দেওয়া।
Step 3: Child class এ Constructor তৈরি করে base কিওয়ার্ড ব্যবহার করা।
public class PersonsGetterServiceChild : PersonsGetterService
{
// Receiving dependencies and passing them directly to the base class constructor
public PersonsGetterServiceChild(
IPersonsRepository personsRepository,
ILogger<PersonsGetterService> logger,
IDiagnosticContext diagnosticContext)
: base(personsRepository, logger, diagnosticContext)
{
// No extra code needed here unless child class needs its own dependencies
}
public override MemoryStream GetPersonsExcel() { /* ... */ }
}
5. Registering the Child Class in IoC Container [Priority: 10/10]
Why: অ্যাপ্লিকেশন তো এখনো জানে না যে আমরা একটি নতুন Child class বানিয়েছি। তাই Service Provider কে জানাতে হবে যে যখনই কেউ IPersonsGetterService চাইবে, তাকে যেন PersonsGetterServiceChild এর অবজেক্ট দেওয়া হয়।
Step 4: ConfigureServicesExtension ফাইলে গিয়ে ম্যাপিং আপডেট করা।
// Inside ConfigureServicesExtension.cs
// ❌ Old implementation
// services.AddScoped<IPersonsGetterService, PersonsGetterService>();
// ✅ New implementation using the Child class
services.AddScoped<IPersonsGetterService, PersonsGetterServiceChild>();
এখন Controller যখন ইন্টারফেস ইনজেক্ট করবে, সে স্বয়ংক্রিয়ভাবে Child class এর অবজেক্ট পাবে এবং Excel ডাউনলোড বাটনে ক্লিক করলে নতুন আপডেটেড লজিকটিই কাজ করবে।
6. The Problem with Inheritance (Transition to LSP) [Priority: 9/10]
লেকচারার শেষে একটি অত্যন্ত গুরুত্বপূর্ণ ওয়ার্নিং দিয়েছেন। Inheritance দিয়ে OCP এচিভ করা গেলেও এটি রেকমেন্ডেড নয়। কারণ, Child class এমন কোনো আচরণ করতে পারে যা Base class এর মূল উদ্দেশ্যকে ব্যাহত করে। একে বলা হয় Liskov Substitution Principle (LSP) এর ভায়োলেশন। এই সমস্যার বিস্তারিত আমরা পরবর্তী লেকচারে শিখব।
🌟 Best Practices
- Favor Composition over Inheritance: OCP ইমপ্লিমেন্ট করার সময় সবসময় Interface (Composition/Delegation) ব্যবহার করার চেষ্টা করবেন। Inheritance কোডকে Tightly Coupled করে দেয়।
- Use
virtualSparingly: শুধুমাত্র সেই মেথডগুলোকেইvirtualকরুন যেগুলো আপনি ভবিষ্যতে Override করার কথা ভাবছেন। অপ্রয়োজনে সব মেথডvirtualকরা ভালো প্র্যাকটিস নয়।
🚀 .NET 10 Context
আমরা লেকচারে দেখেছি যে Child class এর Constructor থেকে base(...) কল করতে অনেকগুলো বয়লারপ্লেট কোড লিখতে হয়। .NET 10 (এবং C# 12 এর Primary Constructors) ব্যবহার করে এই Inheritance এবং Constructor Chaining এর কাজটি জাদুর মতো ক্লিন হয়ে যায়!
Updated .NET 10 Smart Implementation:
// The class declaration and constructor chaining happen in a single, clean line!
public class PersonsGetterServiceChild(
IPersonsRepository personsRepository,
ILogger<PersonsGetterService> logger,
IDiagnosticContext diagnosticContext)
: PersonsGetterService(personsRepository, logger, diagnosticContext)
{
// Direct override, no explicit constructor block needed!
public override MemoryStream GetPersonsExcel()
{
// Modern C# logic for generating excel with fewer fields...
return new MemoryStream();
}
}
এই আধুনিক সিনট্যাক্সটি আপনার কোডবেসকে অনেক বেশি রিডেবল এবং সংক্ষিপ্ত করে তোলে।