স্বাগতম! আপনি এখন আপনার কোর্সের Section 23: SOLID Principles এর Open/Closed Principle (OCP) লেকচারে আছেন। গত লেকচারে আমরা Interface Segregation (ISP) শিখেছিলাম, আর আজ আমরা জানব ‘O’ অর্থাৎ OCP সম্পর্কে। এই প্রিন্সিপালটি আপনাকে শেখাবে কীভাবে পুরনো কোড পরিবর্তন না করেই নতুন ফিচার অ্যাড করতে হয়, যা আপনার অ্যাপ্লিকেশনকে বাগ-ফ্রি রাখতে সাহায্য করে। চলুন শুরু করি!
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:
- Core Concept (OCP): “Software entities should be open for extension, but closed for modification.” অর্থাৎ, নতুন ফিচার যুক্ত করার জন্য এক্সটেন্ড করা যাবে, কিন্তু পুরনো কাজ করছে এমন কোডে হাত দেওয়া যাবে না (বাগ ফিক্সিং ছাড়া)।
- The Problem: পুরনো কোড মডিফাই করলে Regression Testing এর ঝামেলা বাড়ে, existing Unit Tests ফেইল করতে পারে এবং সিস্টেমে নতুন Bug তৈরি হতে পারে।
- The Solution: বিদ্যমান কোডে হাত না দিয়ে, একই Interface ইমপ্লিমেন্ট করে একটি নতুন (Alternative) Service Class তৈরি করা।
- Practical Scenario: ক্লায়েন্ট চেয়েছে Excel ফাইলে সব ডাটার বদলে শুধুমাত্র Name, Age এবং Gender দেখাতে।
- Implementation Strategy:
IPersonsGetterServiceইমপ্লিমেন্ট করে নতুন একটি ক্লাস বানানো হয়েছে। যে মেথডগুলো পরিবর্তন করার দরকার নেই, সেগুলো Constructor Injection এর মাধ্যমে পুরনো ক্লাস থেকেই কল করা হয়েছে। আর শুধুমাত্রGetPersonsExcelমেথডটি রি-রাইট করা হয়েছে। - IoC Registration:
ConfigureServicesExtension-এ পুরনো সার্ভিসের পরিবর্তে নতুন সার্ভিসটি ইন্টারফেসের সাথে ম্যাপ করে দেওয়া হয়েছে।
🧠 Comprehensive Breakdown
লেকচারের প্রতিটি বিষয় নিচে বিস্তারিতভাবে আলোচনা করা হলো এবং বোঝার সুবিধার্থে প্রাসঙ্গিক Code Implementation যুক্ত করা হলো।
1. What is the Open/Closed Principle (OCP)? [Priority: 10/10]
কনসেপ্ট: OCP বলে যে, আপনার ক্লাসটি Closed for modification হতে হবে (অর্থাৎ, একবার একটি ফিচার কাজ করা শুরু করলে সেখানে আর কোড পরিবর্তন করা যাবে না), কিন্তু Open for extension হতে হবে (অর্থাৎ, নতুন রিকোয়ারমেন্ট আসলে যেন নতুন ক্লাস বানিয়ে তা এক্সটেন্ড করা যায়)।
ব্যতিক্রম: শুধুমাত্র যদি existing কোডে কোনো Bug বা Error ধরা পড়ে, তবেই আপনি পুরনো কোড মডিফাই করতে পারবেন।
2. The Dangers of Modifying Existing Code [Priority: 9/10]
কেন আমরা শুধু পুরনো কোডের কয়েক লাইন পরিবর্তন করে দিলাম না? ধরুন, Excel ফাইল জেনারেট করার পুরনো কোডে আপনি কিছু লাইন ডিলিট করে নতুন লাইন বসিয়ে দিলেন। এতে সমস্যা কী?
- Regression: আপনার existing কোড হয়তো অন্য কোনো মডিউল ব্যবহার করছিল, তারা এখন ব্রেক করতে পারে।
- Unit Tests: পুরনো কোডের জন্য লেখা Unit Test গুলো ফেইল করবে। আপনাকে আবার সেগুলো নতুন করে লিখতে হবে।
- Client Impact: হয়তো কিছু ক্লায়েন্টের পুরনো (সব ফিল্ড সহ) Excel ফাইলটিই দরকার। কোড পরিবর্তন করলে তারা সেই সুবিধা থেকে বঞ্চিত হবে।
3. Practical Implementation: The Excel Export Scenario [Priority: 10/10]
লেকচারার একটি চমৎকার রিয়েল-ওয়ার্ল্ড দৃশ্যপট তৈরি করেছেন:
- Old Requirement: Excel ফাইলে Name, Email, DOB, Age, Gender, Country ইত্যাদি সব ফিল্ড থাকবে।
- New Requirement: Excel ফাইলে শুধুমাত্র Name, Age এবং Gender থাকবে।
Step-by-Step Solution (Without modifying old code):
Step 1: Create an Alternative Class
PersonsGetterService এ হাত না দিয়ে, আমরা নতুন একটি ক্লাস বানাবো PersonsGetterServiceWithFewExcelFields এবং এটিও IPersonsGetterService ইন্টারফেসটি ইমপ্লিমেন্ট করবে।
Step 2: Inject the Old Service
যে মেথডগুলো (যেমন GetAllPersons) আমরা পরিবর্তন করতে চাই না, সেগুলো নতুন করে না লিখে পুরনো সার্ভিস থেকেই কল করব। এজন্য Constructor-এ ইন্টারফেসের বদলে সরাসরি পুরনো ক্লাসটি (PersonsGetterService) ইনজেক্ট করব।
public class PersonsGetterServiceWithFewExcelFields : IPersonsGetterService
{
private readonly PersonsGetterService _originalService;
// Injecting the concrete old class, not the interface!
public PersonsGetterServiceWithFewExcelFields(PersonsGetterService originalService)
{
_originalService = originalService;
}
// Unchanged methods: Delegate to the old service
public async Task<List<PersonResponse>> GetAllPersons()
{
return await _originalService.GetAllPersons();
}
// ... Delegate other unchanged methods ...
}
Step 3: Re-implement the Target Method
এখন শুধুমাত্র GetPersonsExcel মেথডের কোডটি নতুন ক্লাসে কপি করে এনে সেখানে প্রয়োজনীয় পরিবর্তন করব (অর্থাৎ শুধু ৩টি কলাম রাখব)।
public MemoryStream GetPersonsExcel()
{
// Write NEW logic here to export only Name, Age, and Gender
// ...
}
4. Updating the IoC Container [Priority: 10/10]
নতুন ক্লাসটি তৈরি করার পর, অ্যাপ্লিকেশনকে জানাতে হবে যে এখন থেকে IPersonsGetterService চাইলে যেন নতুন ক্লাসটি দেওয়া হয়। পাশাপাশি, নতুন ক্লাসটি যেহেতু পুরনো ক্লাসটিকে Constructor-এ ইনজেক্ট করছে, তাই পুরনো ক্লাসটিও সরাসরি IoC Container-এ রেজিস্টার করতে হবে।
// Inside ConfigureServicesExtension.cs
// 1. Register the concrete old class (so the new class can inject it)
services.AddScoped<PersonsGetterService>();
// 2. Map the Interface to the NEW implementation
services.AddScoped<IPersonsGetterService, PersonsGetterServiceWithFewExcelFields>();
এখন অ্যাপ্লিকেশন রান করলে Controller স্বয়ংক্রিয়ভাবে নতুন Excel জেনারেশনের কোড কল করবে।
5. Why ISP helps OCP [Priority: 10/10]
লেকচারার শেষে একটি চমৎকার পয়েন্ট বলেছেন: Interface Segregation Principle (ISP) যত ভালোভাবে মানবেন, OCP ইমপ্লিমেন্ট করা তত সহজ হবে।
আমাদের বর্তমান উদাহরণে, শুধুমাত্র একটি মেথড (GetPersonsExcel) পরিবর্তন করার জন্য আমাদের নতুন ক্লাসে বাকি সবগুলো মেথডকে (GetAllPersons, GetFilteredPersons ইত্যাদি) ইমপ্লিমেন্ট করে পুরনো সার্ভিসকে Delegate করতে হয়েছে।
যদি GetPersonsExcel এর জন্য শুরুতেই একটি আলাদা ইন্টারফেস (IPersonsExcelService) থাকত (যা ISP এর নিয়ম), তবে আমাদের নতুন ক্লাসে অন্য কোনো মেথড নিয়ে মাথাব্যথা করতে হতো না।
🌟 Best Practices
- Favor Composition over Inheritance: এই লেকচারে আমরা Composition বা Delegation ব্যবহার করেছি (পুরনো ক্লাসকে ইনজেক্ট করে)। এটি Inheritance এর চেয়ে অনেক বেশি ফ্লেক্সিবল।
- Keep Old Tests Intact: নতুন ক্লাস তৈরি করলে পুরনো Unit Test গুলোতে হাত দেওয়ার দরকার নেই। শুধু নতুন ক্লাসের
GetPersonsExcelমেথডের জন্য একটি নতুন Test লিখে নিলেই হবে।
🚀 .NET 10 Context
.NET 10 (এবং C# 12+) এ Primary Constructors ব্যবহার করে Delegation বা Composition এর কাজগুলো আরও সহজে এবং কম কোডে করা যায়। নতুন ক্লাসটি দেখতে আরও ক্লিন হবে:
// Using C# Primary Constructor for cleaner Composition/Delegation
public class PersonsGetterServiceWithFewExcelFields(PersonsGetterService originalService) : IPersonsGetterService
{
// Delegate unchanged methods cleanly
public async Task<List<PersonResponse>> GetAllPersons() => await originalService.GetAllPersons();
public MemoryStream GetPersonsExcel()
{
// New logic for Excel export
}
}
এইভাবে কোড লিখলে পুরনো কোড সুরক্ষিত থাকে এবং অ্যাপ্লিকেশন যেকোনো নতুন রিকোয়ারমেন্টের জন্য সর্বদা প্রস্তুত থাকে!