স্বাগতম! আপনি এখন আপনার কোর্সের Section 23: SOLID Principles এর Single Responsibility Principle লেকচারে আছেন। গত লেকচারে আমরা ‘D’ (Dependency Inversion) নিয়ে রিভিশন করেছি, আর আজ আমরা ‘S’ অর্থাৎ SRP নিয়ে বিস্তারিত জানব। এই লেকচারটি আপনাকে শেখাবে কীভাবে আপনার কোডকে পরিষ্কার, বাগ-মুক্ত এবং সহজে পরিবর্তনযোগ্য রাখতে হয়। চলুন শুরু করি!
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:
- Core Concept: একটি Class এর শুধুমাত্র একটি নির্দিষ্ট কাজ বা দায়িত্ব থাকা উচিত (A class should have only one reason to change)।
- Bad Practice: একটি মাত্র Class এর ভেতরে Validation, Configuration থেকে Data Read করা এবং Database-এ Data Access করার কোড একসাথে লেখা।
- The Problems: একসাথে অনেক কাজ করলে কোড Cumbersome (জটিল) হয়ে যায়, Bug খুঁজে বের করা কঠিন হয়, Regression Testing এর ঝামেলা বাড়ে এবং টিমওয়ার্ক কঠিন হয়।
- The Solution: প্রতিটি আলাদা কাজের জন্য (যেমন: Validation, Caching, Data Access) আলাদা আলাদা Class তৈরি করা।
- Delegation is OK: একটি Class তার নিজের ভেতরে সব কোড না লিখে, প্রয়োজনে অন্য Class এর Method কে কল করতে পারে। এটি SRP ব্রেক করে না, বরং এটাই সঠিক নিয়ম।
- Project Context: কোর্সের
PersonsServiceক্লাসটি ইতোমধ্যেই SRP ফলো করছে। কারণ এটি Validation এর জন্যValidationHelper, Data Access এর জন্যRepositoryএবং Logging এর জন্যILoggerব্যবহার করে।
🧠 Comprehensive Breakdown
লেকচারের প্রতিটি বিষয় নিচে বিস্তারিতভাবে আলোচনা করা হলো এবং বোঝার সুবিধার্থে প্রাসঙ্গিক Code Implementation যুক্ত করা হলো।
1. What is Single Responsibility Principle (SRP)? [Priority: 10/10]
কনসেপ্ট: SOLID এর ‘S’ হলো Single Responsibility Principle। এর ফরমাল সংজ্ঞাটি হলো- “A class should have only one reason to change.” অর্থাৎ, একটি Class শুধুমাত্র একটি মাত্র Functionality বা কাজ প্রোভাইড করবে, একাধিক নয়।
কেন এটি গুরুত্বপূর্ণ? যদি আপনার একটি Class ডাটাবেস থেকে ডাটা আনে, আবার সেই ডাটা ভ্যালিডেট করে এবং কনফিগারেশন ফাইলও রিড করে, তবে এটি একটি Bad Practice। এর বদলে আপনার উচিত Data Access এর জন্য একটি Class, Validation এর জন্য একটি Class এবং User Request হ্যান্ডেল করার জন্য আলাদা Class তৈরি করা।
2. The Dangers of Multiple Responsibilities [Priority: 9/10]
একটি ক্লাসে একাধিক কাজ একসাথে করলে কী কী সমস্যা হতে পারে, লেকচারার তা খুব সুন্দরভাবে ব্যাখ্যা করেছেন:
- Cumbersome Code & Bugs: কোড খুব বড় ও জটিল হয়ে যায়, ফলে Bug খুঁজে বের করা এবং ফিক্স করা কঠিন হয়।
- Regression Testing Issue: যখন আপনি একটি নির্দিষ্ট Functionality পরিবর্তন করতে যাবেন, তখন একই Class এর অন্যান্য অংশের কোডও প্রভাবিত হতে পারে। এর ফলে existing Unit Test Cases গুলো ফেইল করতে পারে এবং আপনাকে পুনরায় Unit Tests লিখতে হতে পারে (যাকে Regression Testing বলে)।
- Maintenance & Team Segregation: যখন একটি প্রোজেক্টে অনেক ডেভেলপার কাজ করে (Multiple teams), তখন সবাই যদি একই Class এ পরিবর্তন করার চেষ্টা করে, তবে কোড মেইনটেইন করা অসম্ভব হয়ে পড়ে।
Code Example (Bad vs Good):
// ❌ Bad Practice: Multiple responsibilities in one class
public class CustomerManager
{
public void AddCustomer(Customer customer)
{
// 1. Validation Logic
if(string.IsNullOrEmpty(customer.Name)) throw new Exception("Invalid");
// 2. Data Access Logic
// SQL connection and insert query...
// 3. Logging Logic
System.IO.File.WriteAllText("log.txt", "Customer added");
}
}
3. How to Answer SRP in Interviews [Priority: 8/10]
ইন্টারভিউতে SRP সম্পর্কে জানতে চাওয়া হলে লেকচারার একটি নির্দিষ্ট উদাহরণ দিয়ে বোঝাতে বলেছেন: “ধরুন, একটি Class একই সাথে Config file থেকে Configuration Settings রিড করছে এবং Model Validations এর কাজও করছে। এটি ডিজাইন করা, ডিবাগ করা এবং বাগ ফিক্স করা খুবই কঠিন। এই সমস্যার সমাধানে আমরা কোডটিকে দুটি আলাদা Class এ ভাগ করব— একটি শুধুমাত্র Validation এর কাজ করবে, অন্যটি শুধুমাত্র Configuration রিড করার কাজ করবে। এটাই Single Responsibility Principle।“
4. The Golden Rule: Calling Another Class [Priority: 10/10]
অনেকের মনে প্রশ্ন আসতে পারে, একটি Class যদি একটাই কাজ করে, তবে পুরো অ্যাপ্লিকেশন কীভাবে কাজ করবে? এর উত্তর হলো: একটি Class অন্য Class এর Method কল করতে পারে। Class 1 থেকে Class 2 এর Method কল করাতে কোনো সমস্যা নেই। আপনি শুধু Class 2 এর সম্পূর্ণ কোড Class 1 এর ভেতরে লিখবেন না। আপনি কাজটি Delegate (অর্পণ) করছেন, যা SRP এর মূল ভিত্তি।
5. SRP Implementation in Existing Project [Priority: 10/10]
লেকচারার জানিয়েছেন যে, আমাদের কোর্সের PersonsService প্রোজেক্টে কোনো নতুন কোড মডিফিকেশন করার প্রয়োজন নেই, কারণ আমরা ইতোমধ্যেই SRP ফলো করে কোড লিখেছি। কীভাবে? চলুন দেখি:
- Business Logic Separation:
PersonsServiceশুধুমাত্র Business Conditions চেক করে (যেমন Argument null কি না, নতুন ID জেনারেট করা ইত্যাদি)। - Validation Separation: Data Annotations এর Validation গুলো
PersonsServiceএ না লিখে,ValidationHelperনামে একটি আলাদা Class তৈরি করে সেখানে লেখা হয়েছে। - Data Access Separation: Database এর সাথে সরাসরি যোগাযোগ Service ক্লাসে না করে, একটি আলাদা
Repositoryক্লাসে করা হয়েছে।PersonsServiceশুধু সেই Repository কে কল করে। - Logging Separation: Service নিজে কোনো File বা Database এ Log লেখার কোড হ্যান্ডেল করে না। এর বদলে আমরা globally Serilog কনফিগার করেছি এবং
ILoggerএরLogInformationমেথডকে শুধু কল করেছি।
Code Example from Project (Good Practice):
// ✅ Good Practice: PersonsService delegates responsibilities
public class PersonsService : IPersonsService
{
private readonly IPersonsRepository _personsRepository;
private readonly ILogger<PersonsService> _logger;
public PersonsService(IPersonsRepository personsRepository, ILogger<PersonsService> logger)
{
_personsRepository = personsRepository;
_logger = logger;
}
public void AddPerson(PersonAddRequest? personAddRequest)
{
// 1. Business condition
if (personAddRequest == null) throw new ArgumentNullException(nameof(personAddRequest));
// 2. Delegate Validation to ValidationHelper (SRP)
ValidationHelper.ModelValidation(personAddRequest);
// 3. Delegate Database access to Repository (SRP)
_personsRepository.AddPerson(personAddRequest.ToPerson());
// 4. Delegate Logging to ILogger (SRP)
_logger.LogInformation("Person added successfully");
}
}
🌟 Best Practices
- Class Naming Rule: Class এর নাম দেখেই যেন বোঝা যায় তার দায়িত্ব কী। যেমন
UtilityবাHelperএর মতো vague নাম ব্যবহার না করেValidationHelperবাEmailSenderএর মতো স্পেসিফিক নাম ব্যবহার করুন। - Check the “And” Word: যদি কোনো Class এর কাজ বোঝাতে গিয়ে আপনাকে “and” শব্দ ব্যবহার করতে হয় (যেমন: This class saves data and sends email), তবে বুঝবেন সেখানে SRP ব্রেক হচ্ছে।
🚀 .NET 10 Context
SRP ফলো করলে আপনাকে অনেকগুলো ছোট ছোট Class তৈরি করতে হবে এবং সেগুলোকে Dependency Injection (DI) এর মাধ্যমে একে অপরের সাথে যুক্ত করতে হবে। আগে অনেকগুলো Dependency Inject করতে গেলে Constructor-এ অনেক বয়লারপ্লেট কোড লিখতে হতো। .NET 10 (C# 14 / C# 12 Primary Constructors) এর সাহায্যে এই কাজ এখন অনেক সহজ এবং ক্লিন হয়ে গেছে।
.NET 10 Primary Constructor Example:
// Modern C# syntax makes injecting multiple single-responsibility classes much cleaner
public class PersonsService(IPersonsRepository personsRepository, ILogger<PersonsService> logger) : IPersonsService
{
// No explicit constructor body or private fields needed!
public void AddPerson(PersonAddRequest request)
{
// Direct access to parameters as private fields
logger.LogInformation("Processing person...");
personsRepository.AddPerson(request.ToPerson());
}
}