স্বাগতম! আপনি এখন আপনার কোর্সের Section 23: SOLID Principles এর Interface Segregation Principle (ISP) লেকচারে আছেন। গত লেকচারগুলোতে আমরা Dependency Inversion (DIP) এবং Single Responsibility Principle (SRP) শিখেছি। আজকে আমরা দেখব ‘I’ বা Interface Segregation Principle কীভাবে আপনার কোডকে আরও Flexible এবং Maintainable করে। চলুন শুরু করি!


📝 Quick Summary for Revision

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:

  • Core Concept (ISP): “No client should be forced to depend on the methods it doesn’t use.” অর্থাৎ, কোনো বিশাল (Giant) Interface তৈরি করার চেয়ে, ছোট ছোট এবং স্পেসিফিক কাজের জন্য আলাদা আলাদা Interface তৈরি করা উত্তম।
  • The Problem: একটি Interface এ অনেক মেথড (যেমন: Insert, Update, Delete, Get) থাকলে, যে Client শুধুমাত্র Get করতে চায় তাকেও বাকি মেথডগুলো ইমপ্লিমেন্ট করতে বাধ্য করা হয়।
  • The Solution: কাজের ধরন অনুযায়ী Interface গুলোকে ভাগ করা। যেমন: IPersonsGetter, IPersonsAdder, IPersonsUpdater ইত্যাদি।
  • Practical Benefit: যদি ভবিষ্যতে শুধুমাত্র Get অপারেশনের লজিক পরিবর্তন করতে হয় (যেমন JSON থেকে XML), তবে শুধুমাত্র IPersonsGetter ইমপ্লিমেন্ট করে একটি নতুন Service বানানো যাবে। অন্য কোনো মেথড নিয়ে চিন্তা করতে হবে না।
  • Project Refactoring: লেকচারে existing IPersonsService কে ভেঙে ৫টি আলাদা Interface এবং Service ক্লাসে ভাগ করা হয়েছে (Getter, Adder, Updater, Deleter, Sorter)।
  • Controller Dependency: PersonsController-এ যেহেতু সবগুলো অপারেশনই দরকার, তাই এখন একটির বদলে ৫টি Interface-ই ইনজেক্ট করতে হবে। তবে অন্য কোনো Controller-এ যদি শুধু Get অপারেশন দরকার হয়, তবে সে শুধু IPersonsGetter ইনজেক্ট করবে।

🧠 Comprehensive Breakdown

লেকচারের প্রতিটি বিষয় নিচে বিস্তারিতভাবে আলোচনা করা হলো এবং বোঝার সুবিধার্থে প্রাসঙ্গিক Code Implementation যুক্ত করা হলো।

1. What is Interface Segregation Principle (ISP)? [Priority: 10/10]

কনসেপ্ট: Interface Segregation Principle (ISP) বলে যে, একাধিক মেথড সম্বলিত একটি বিশাল (Fat/Giant) Interface তৈরি করার চেয়ে, ছোট ছোট অনেকগুলো Interface তৈরি করা ভালো। এর ফরমাল সংজ্ঞা হলো: “No client should be forced to depend on the methods it doesn’t use.”

কেন এটি গুরুত্বপূর্ণ? ধরে নিন, আপনার একটি IPersonsService আছে যাতে ডাটাবেস থেকে ডাটা আনা (Get) এবং ডাটাবেসে ডাটা যোগ করা (Add) - এই দুই ধরনের মেথড আছে। এখন একটি Controller শুধুমাত্র ডাটা দেখাতে চায় (Get)। কিন্তু যেহেতু সে IPersonsService ব্যবহার করছে, তাই পরোক্ষভাবে সে Add মেথডের উপরও নির্ভরশীল হয়ে পড়ছে, যা তার দরকার নেই। ISP আমাদের এই অপ্রয়োজনীয় নির্ভরশীলতা থেকে বাঁচায়।

2. The Benefit of Alternative Implementations [Priority: 9/10]

ISP এর সবচেয়ে বড় সুবিধা হলো কোড পরিবর্তন করা।

  • Scenario: ধরুন, আপনার বর্তমান Service টি ডাটা JSON ফরম্যাটে রিটার্ন করে। এখন ক্লায়েন্ট চাচ্ছে শুধু Get মেথডগুলো ডাটা XML ফরম্যাটে রিটার্ন করুক।
  • Without ISP: আপনাকে পুরো বিশাল Service ক্লাসটিতে হাত দিতে হবে অথবা নতুন একটি ক্লাস বানিয়ে সবগুলো মেথড (Add, Update, Delete) আবার ইমপ্লিমেন্ট করতে হবে।
  • With ISP: যেহেতু আপনার IPersonsGetter নামে আলাদা একটি Interface আছে, আপনি খুব সহজেই নতুন একটি ক্লাস বানাতে পারবেন যা শুধু IPersonsGetter ইমপ্লিমেন্ট করবে। Add বা Delete নিয়ে আপনাকে মাথা ঘামাতেই হবে না।

3. Refactoring the Existing Project [Priority: 10/10]

লেকচারার এখন প্রোজেক্টের existing IPersonsService কে ISP অনুযায়ী রিফ্যাক্টর করেছেন। এই কাজটি তিনি কীভাবে করেছেন তা নিচে ধাপে ধাপে দেওয়া হলো:

Step 1: Interface Segregation (Splitting the Interface) একটি বিশাল IPersonsService এর বদলে ৫টি ছোট Interface তৈরি করা হয়েছে:

  1. IPersonsGetterService: এতে থাকবে GetAllPersons, GetPersonByPersonId, GetFilteredPersons, GetPersonsCSV, GetPersonsExcel
  2. IPersonsAdderService: এতে থাকবে শুধুমাত্র AddPerson
  3. IPersonsUpdaterService: এতে থাকবে শুধুমাত্র UpdatePerson
  4. IPersonsDeleterService: এতে থাকবে শুধুমাত্র DeletePerson
  5. IPersonsSorterService: এতে থাকবে শুধুমাত্র GetSortedPersons

Code Example (Before vs After):

// ❌ Before: One Giant Interface (Violates ISP)
public interface IPersonsService
{
    void AddPerson();
    void GetPerson();
    void UpdatePerson();
    void DeletePerson();
}
 
// ✅ After: Segregated Interfaces (Follows ISP)
public interface IPersonsAdderService { void AddPerson(); }
public interface IPersonsGetterService { void GetPerson(); }
// ... and so on.
 

Step 2: Service Segregation (Splitting the Implementation) Interface ভাগ করার পর, সেগুলোকে ইমপ্লিমেন্ট করার জন্য Service ক্লাসগুলোকেও ভাগ করতে হবে।

  • PersonsGetterService ইমপ্লিমেন্ট করবে IPersonsGetterService কে।
  • PersonsAdderService ইমপ্লিমেন্ট করবে IPersonsAdderService কে।
  • একইভাবে বাকিগুলো।
// ✅ Service Implementation Example
public class PersonsAdderService : IPersonsAdderService
{
    // Constructor injections...
    
    public void AddPerson() 
    {
        // Implementation logic...
    }
}
 

Step 3: Controller Refactoring যেহেতু PersonsController-এ CRUD এর সবগুলো অপারেশনই দরকার, তাই এখন Constructor-এ ৫টি Interface-ই ইনজেক্ট করতে হবে।

// Refactored Controller using Multiple Interfaces
public class PersonsController : Controller
{
    private readonly IPersonsGetterService _getterService;
    private readonly IPersonsAdderService _adderService;
    private readonly IPersonsUpdaterService _updaterService;
    private readonly IPersonsDeleterService _deleterService;
    private readonly IPersonsSorterService _sorterService;
 
    // Constructor Injection
    public PersonsController(
        IPersonsGetterService getterService,
        IPersonsAdderService adderService,
        IPersonsUpdaterService updaterService,
        IPersonsDeleterService deleterService,
        IPersonsSorterService sorterService)
    {
        _getterService = getterService;
        _adderService = adderService;
        _updaterService = updaterService;
        _deleterService = deleterService;
        _sorterService = sorterService;
    }
 
    [HttpPost]
    public IActionResult Create(Person person)
    {
        // Calling the specific segregated service
        _adderService.AddPerson(person); 
        return RedirectToAction("Index");
    }
}
 

4. The Trade-off (Controller Boilerplate) [Priority: 8/10]

লেকচারার একটি গুরুত্বপূর্ণ বিষয় উল্লেখ করেছেন। ISP ফলো করলে কোড অনেক ক্লিন হয় ঠিকই, কিন্তু PersonsController-এর মতো জায়গায় যেখানে সব ধরনের অপারেশন দরকার, সেখানে Constructor-এ অনেকগুলো Dependency ইনজেক্ট করতে হয় (Boilerplate)।

  • তবে এর সুবিধা হলো: যদি অন্য কোনো Controller (যেমন: ReadOnlyController) শুধু ডাটা দেখাতে চায়, সে শুধুমাত্র IPersonsGetterService ইনজেক্ট করবে, অন্যগুলোর দরকার পড়বে না।

🌟 Best Practices

  • Role-based Interfaces: Interface এর নাম এমনভাবে দিন যেন তা একটি স্পেসিফিক কাজ (Role) বা আচরণ বোঝায় (যেমন: IReader, IWriter, IGetter)।
  • Don’t overdo it: একটি মেথডের জন্য একটি Interface বানানোর রুল সব জায়গায় খাটানোর দরকার নেই। লজিক্যালি যে মেথডগুলো একসাথে থাকে (Cohesive), তাদের একটি Interface-এ রাখা যেতে পারে।

🚀 .NET 10 Context

Controller-এ অনেকগুলো Dependency ইনজেক্ট করার যে সমস্যাটি (Boilerplate) তৈরি হয়েছিল, .NET 10 এ C# Primary Constructors ব্যবহার করে সেটি খুব সহজেই ক্লিন করা যায়।

Clean DI with .NET 10 Primary Constructors:

// No need to define private readonly fields manually or write explicit constructor body!
public class PersonsController(
    IPersonsGetterService getterService,
    IPersonsAdderService adderService,
    IPersonsUpdaterService updaterService,
    IPersonsDeleterService deleterService,
    IPersonsSorterService sorterService) : Controller
{
    [HttpPost]
    public IActionResult Create(Person person)
    {
        // Direct access to the injected parameter 'adderService'
        adderService.AddPerson(person);
        return RedirectToAction("Index");
    }
}