হ্যালো হাসিব! তুমি এখন Section 12: Dependency Injection-এর তৃতীয় লেকচার “Dependency Inversion Principle” (Lecture 130)-এ আছো। আগের লেকচারে আমরা Controller-এর ভেতরে new কি-ওয়ার্ড ব্যবহার করে Service-এর অবজেক্ট তৈরি করেছিলাম। লেকচারার বলেছিলেন যে এটি একটি বিশাল Bad Practice

আজকের লেকচারে আমরা শিখবো কেন এটি Bad Practice এবং Dependency Inversion Principle (DIP) ব্যবহার করে কীভাবে এই সমস্যার সমাধান করা যায়। এটি সলিড (SOLID) প্রিন্সিপালের ‘D’-এর অংশ এবং আর্কিটেকচারাল ডিজাইনের জন্য খুবই গুরুত্বপূর্ণ।

চলো, পুরো লেকচারটি ডিকোড করি।


সারসংক্ষেপ (Quick Revision List)

  • The Problem with new keyword: Controller-এ new CitiesService() লিখলে Controller এবং Service একে অপরের সাথে শক্তভাবে (Tightly Coupled) জুড়ে যায়। এতে করে Service পুরোপুরি ডেভেলপ না হওয়া পর্যন্ত Controller-এর কাজ আটকে থাকে।
  • Dependency Inversion Principle (DIP): এই নীতি অনুযায়ী, “High-level modules (Controller) should not depend on low-level modules (Service). Both should depend on abstractions (Interface).”
  • The Solution: Controller সরাসরি Service ক্লাসের উপর নির্ভর করবে না। এর বদলে একটি Interface (ICitiesService) তৈরি করতে হবে। Controller সেই Interface-কে কল করবে, আর Service সেই Interface-কে ইমপ্লিমেন্ট করবে।
  • Independent Development: Interface থাকার কারণে, Controller-এর ডেভেলপার এবং Service-এর ডেভেলপার স্বাধীনভাবে এবং সমান্তরালভাবে (parallelly) কাজ করতে পারে।
  • The Unanswered Question: Interface তো বানালাম, কিন্তু অবজেক্টটা কে তৈরি করবে? এর উত্তর হলো Inversion of Control (IoC) এবং Dependency Injection (DI), যা আমরা নেক্সট লেকচারে শিখবো।

Comprehensive Breakdown

১. The Tightly Coupled Problem (কেন new কি-ওয়ার্ড খারাপ?) [Priority: 10/10]

আগের কোডে আমরা লিখেছিলাম:

public HomeController() {
    _citiesService = new CitiesService();
}
 

এখানে সমস্যা কোথায়?

  1. Blocker: ধরো, তুমি Controller নিয়ে কাজ করছো (Developer A) এবং তোমার বন্ধু Service নিয়ে কাজ করছে (Developer B)। তোমার বন্ধু যদি Service ক্লাস বানানো শেষ না করে, তুমি new CitiesService() লিখতে পারবে না (এরর খাবে)। তোমাকে তার জন্য অপেক্ষা করতে হবে।
  2. Hard to Switch: ভবিষ্যতে যদি ডাটাবেস থেকে ডাটা আনার জন্য নতুন একটি সার্ভিস (CitiesServiceDatabase) বানানো হয়, তখন তোমাকে তোমার Controller-এ গিয়ে নাম চেঞ্জ করতে হবে। যদি ১০০টা Controller থাকে, ১০০ জায়গায় চেঞ্জ করতে হবে!
  3. Unit Testing Issue: Controller-কে মক (Mock) করে টেস্ট করা প্রায় অসম্ভব হয়ে যায়।

২. Dependency Inversion Principle (DIP) কী? [Priority: 10/10]

এটি একটি আর্কিটেকচারাল রুল।

  • High-level module: Controller (যে রিকোয়েস্ট হ্যান্ডল করে)।
  • Low-level module: Service (যে ডাটা প্রসেস করে)। DIP বলে, Controller কখনোই সরাসরি Service-কে চিনবে না। তাদের দুজনের মাঝখানে একটি Abstraction (Interface) থাকবে।

৩. Service Contracts (Interface) প্রজেক্ট তৈরি করা [Priority: 10/10]

এই রুল ইমপ্লিমেন্ট করার জন্য লেকচারার নতুন একটি Class Library প্রজেক্ট বানিয়েছেন।

Step A: Project Creation

  1. সলিউশনে একটি নতুন Class Library প্রজেক্ট তৈরি করো।
  2. নাম দাও ServiceContracts (বা শুধু Contracts বা Interfaces)।

Step B: Interface তৈরি করা ServiceContracts প্রজেক্টের ভেতরে একটি ইন্টারফেস তৈরি করো। ইন্টারফেসের নামের শুরুতে ক্যাপিটাল I দেওয়া গ্লোবাল স্ট্যান্ডার্ড।

Code Implementation (ServiceContracts/ICitiesService.cs):

namespace ServiceContracts
{
    // ১. Interface তৈরি (এখানে শুধু মেথডের সিগনেচার থাকে, কোনো বডি বা লজিক থাকে না)
    public interface ICitiesService
    {
        List<string> GetCities();
    }
}
 

(এই ইন্টারফেসটি বানিয়েছে Developer A বা Controller-এর ডেভেলপার। সে বলে দিচ্ছে, “আমার এমন একটি সার্ভিস লাগবে যার একটি GetCities মেথড থাকবে, ভেতরে তুমি কীভাবে লজিক লিখবে সেটা তোমার ব্যাপার”।)

৪. Controller এবং Service-এ Interface কানেক্ট করা [Priority: 10/10]

Step A: Web Project-এ Reference অ্যাড করা Web Project (DIExample)-এর Dependencies-এ গিয়ে ServiceContracts প্রজেক্টের রেফারেন্স অ্যাড করো।

Step B: Controller আপডেট করা (The Magic of Abstraction) এখন Controller থেকে Service-এর ক্লাসের নাম মুছে ফেলে Interface-এর নাম ব্যবহার করতে হবে।

Code Implementation (Controllers/HomeController.cs):

using ServiceContracts; // Interface-এর namespace
 
namespace DIExample.Controllers
{
    public class HomeController : Controller
    {
        // ১. Service Class-এর বদলে Interface-এর রেফারেন্স ভ্যারিয়েবল
        private readonly ICitiesService _citiesService;
 
        public HomeController()
        {
            // ২. (এখনো অবজেক্ট কে বানাবে তা আমরা জানি না, তাই আপাতত Error এড়াতে null রাখছি)
            _citiesService = null; 
        }
 
        [Route("/")]
        public IActionResult Index()
        {
            // ৩. Interface দিয়ে মেথড কল করা
            List<string> cities = _citiesService.GetCities();
            return View(cities);
        }
    }
}
 

Step C: Services প্রজেক্টে Reference অ্যাড করা এবার Services প্রজেক্টের Dependencies-এ গিয়ে ServiceContracts প্রজেক্টের রেফারেন্স অ্যাড করো।

Step D: Service Class আপডেট করা (Implementing the Interface) Service-এর ডেভেলপার এখন তার ক্লাসটিকে এই ইন্টারফেসের সাথে যুক্ত করে দেবে।

Code Implementation (Services/CitiesService.cs):

using ServiceContracts;
 
namespace Services
{
    // Interface-কে implement করা হচ্ছে
    public class CitiesService : ICitiesService 
    {
        private List<string> _cities;
 
        public CitiesService()
        {
            _cities = new List<string>() { "London", "Paris", "New York" };
        }
 
        public List<string> GetCities()
        {
            return _cities;
        }
    }
}
 

৫. The Unanswered Question (অবজেক্ট কে বানাবে?) [Priority: 8/10]

এখন আমাদের Controller এবং Service পুরোপুরি আলাদা হয়ে গেছে (Loosely Coupled)। Controller শুধু জানে যে একটি ICitiesService আছে, কিন্তু সে আসল CitiesService ক্লাসটিকে চেনে না।

কিন্তু Controller রান করলে NullReferenceException খাবে, কারণ আমরা Constructor-এ _citiesService = null; লিখেছি!

তাহলে আসল অবজেক্টটা কে তৈরি করে দেবে? এই অবজেক্ট তৈরি করে Controller-এর হাতে ধরিয়ে দেওয়ার দায়িত্ব হলো Inversion of Control (IoC) Container এবং Dependency Injection (DI)-এর। এটিই আমরা পরের লেকচারে প্র্যাকটিক্যালি ইমপ্লিমেন্ট করবো।


VS / VS Code Shortcuts (For faster development)

  • Extract Interface: Visual Studio বা VS Code-এ কোনো ক্লাসের নামের উপর Ctrl + . চাপলে “Extract Interface” অপশন আসে। এটি ব্যবহার করলে ক্লাস থেকে অটোমেটিক্যালি ইন্টারফেস ফাইল জেনারেট হয়ে যায় (তোমাকে ম্যানুয়ালি ফাইল বানিয়ে টাইপ করতে হবে না)।
  • Implement Interface: ইন্টারফেস ইমপ্লিমেন্ট করার সময় ক্লাসের পাশে লাল দাগ আসলে Ctrl + . চেপে “Implement Interface” এ ক্লিক করলে মেথডগুলোর স্কেলিটন অটোমেটিক তৈরি হয়ে যায়।

Best Practices & .NET 10 Context

Best Practices for DIP:

  1. Always Program to Interfaces: প্রফেশনাল প্রজেক্টে কখনোই একটি ক্লাস থেকে সরাসরি আরেকটি ক্লাসকে রেফারেন্স করবে না (DTO বা Model বাদে)। সবসময় Interface দিয়ে কথা বলবে।
  2. Separate Contracts Project: ইন্টারফেসগুলোকে সবসময় একটি আলাদা Class Library-তে (যেমন: ProjectName.Core বা ProjectName.Contracts) রাখা উচিত, যাতে সার্কুলার ডিপেন্ডেন্সি (Circular Dependency) তৈরি না হয়।

.NET 10 Context: Dependency Inversion Principle (DIP) হলো একটি সলিড (SOLID) আর্কিটেকচারাল রুল, যা .NET 6 হোক বা .NET 10, সব জায়গাতেই ১০০% একইভাবে অ্যাপ্লিকেবল। আধুনিক .NET 10 প্রজেক্টে Clean Architecture ফলো করা হয়, যেখানে Core বা Domain লেয়ারে ইন্টারফেসগুলো থাকে এবং Infrastructure লেয়ারে সেগুলো ইমপ্লিমেন্ট করা হয়।

হাসিব, কেন new কি-ওয়ার্ড ব্যবহার করা খারাপ এবং ইন্টারফেসের ম্যাজিকটা কি বুঝতে পেরেছো? তুমি রেডি থাকলে আমরা পরবর্তী লেকচার “Inversion of Control”-এ মুভ করতে পারি!