SOLID প্রিন্সিপল — DIP
এই পাঠে যা শিখবেন
- DIP-এর সুনির্দিষ্ট সংজ্ঞা — কেন "নির্ভরতার দিক উল্টে দেওয়া" শব্দগুচ্ছটি সঠিক
- dependency injection টেকনিক — কনস্ট্রাক্টরের মাধ্যমে dependency ইনজেক্ট করা
- কীভাবে DIP একটি ক্লাসকে টেস্টেবল ও ফ্লেক্সিবল দুটোই বানায়, সত্যিকারের কোড দিয়ে প্রমাণসহ
- DIP কীভাবে L14-এর লো-কাপলিং ও L16-এর OCP-র সাথে সরাসরি যুক্ত তা বোঝা
১ · Dependency Inversion Principle (DIP) কী
Dependency Inversion PrincipleDIPউচ্চ-স্তরের মডিউল নিম্ন-স্তরের মডিউলের উপর নির্ভর করা উচিত নয় — দুটোই অ্যাবস্ট্রাকশনের উপর নির্ভর করা উচিত; অ্যাবস্ট্রাকশন বিস্তারিত বাস্তবায়নের উপর নির্ভর করা উচিত নয়, বরং বিস্তারিত বাস্তবায়ন অ্যাবস্ট্রাকশনের উপর নির্ভর করা উচিত। (DIP, SOLID-এর শেষ অক্ষর) বলে — উচ্চ-স্তরের মডিউল (যেমন বিজনেস-লজিক ক্লাস) নিম্ন-স্তরের মডিউলের (যেমন একটি নির্দিষ্ট ডেটাবেস ক্লাস) উপর সরাসরি নির্ভর করা উচিত নয়। বদলে, দুটোই একটি অ্যাবস্ট্র্যাক্ট ইন্টারফেসের উপর নির্ভর করা উচিত — কংক্রিট বাস্তবায়ন তখন বাইরে থেকে ইনজেক্ট করা হয়, ক্লাসের ভেতরে হার্ডকোড করা নয়।
"ইনভার্শন" (উল্টানো) শব্দটি এখানে সুনির্দিষ্ট কারণেই ব্যবহৃত — সাধারণত উচ্চ-স্তরের কোড সরাসরি নিম্ন-স্তরের
বিস্তারিত ক্লাস তৈরি করে ও তার উপর নির্ভর করে (নির্ভরতার দিক উপর থেকে নিচে)। DIP এই দিক উল্টে দেয় — এখন উভয়
পক্ষই একটি ভাগাভাগি করা অ্যাবস্ট্রাকশনের উপর নির্ভর করে, এবং নিম্ন-স্তরের কংক্রিট ক্লাস সেই অ্যাবস্ট্রাকশন
মেনে চলে, উচ্চ-স্তরের কোড তার নিজের প্রয়োজনে (L14-এর লো-কাপলিং লক্ষ্যের সরাসরি পেওফ, এবং L16-এর OCP-র সাথেও
সরাসরি যুক্ত — নতুন database ক্লাস যোগ করা মানে existing OrderProcessor-এ কোনো পরিবর্তন লাগে না)।
২ · Dependency Injection — DIP প্রয়োগের প্র্যাকটিক্যাল কৌশল
Dependency InjectionDIএকটি ক্লাসের প্রয়োজনীয় dependency ক্লাসের ভেতরে নিজে তৈরি না করে, বাইরে থেকে (সাধারণত কনস্ট্রাক্টর প্যারামিটার হিসেবে) পাস করে দেওয়ার টেকনিক। হলো DIP বাস্তবে প্রয়োগ করার স্ট্যান্ডার্ড টেকনিক — একটি ক্লাসের dependency নিজে ভেতরে তৈরি না করে, বাইরে থেকে (সাধারণত কনস্ট্রাক্টর প্যারামিটার হিসেবে) পাস করা। এর সরাসরি, বাস্তব সুবিধা দুটো: ক্লাসটি সহজেই টেস্টেবল হয়ে যায় (টেস্টে একটি real dependency-র বদলে একটি ফেক/মক dependency ইনজেক্ট করা যায় — M10/L47-এর মকিং পাঠের সরাসরি ভিত্তি), এবং ক্লাসটি ফ্লেক্সিবল হয় (একটি বাস্তবায়ন থেকে আরেকটিতে পাল্টাতে হাই-লেভেল ক্লাসের কোনো পরিবর্তন লাগে না, যতক্ষণ দুটোই একই অ্যাবস্ট্রাক্ট ইন্টারফেস মেনে চলে)।
from abc import ABC, abstractmethod
# --- DIP লঙ্ঘন: OrderProcessor সরাসরি কংক্রিট MySQLDatabase-এর উপর নির্ভরশীল ---
class MySQLDatabaseConcrete:
def save(self, record):
return f"[MySQL] সংরক্ষিত: {record}"
class OrderProcessorBad:
def __init__(self):
# কনস্ট্রাক্টরের ভেতরেই কংক্রিট ক্লাস তৈরি করে ফেলা -- hard dependency
self.database = MySQLDatabaseConcrete()
def process(self, order):
return self.database.save(order)
# --- DIP-কমপ্লায়েন্ট: একটি অ্যাবস্ট্র্যাকশনের উপর নির্ভরশীল, dependency injection দিয়ে ---
class Database(ABC):
@abstractmethod
def save(self, record):
...
class MySQLDatabase(Database):
def save(self, record):
return f"[MySQL] সংরক্ষিত: {record}"
class InMemoryFakeDatabase(Database):
def __init__(self):
self.records = []
def save(self, record):
self.records.append(record)
return f"[InMemory] সংরক্ষিত: {record} (মোট {len(self.records)}টি)"
class OrderProcessor:
def __init__(self, database: Database):
# dependency ইনজেক্ট করা হচ্ছে -- OrderProcessor জানেই না কোন কংক্রিট ক্লাস এটা
self.database = database
def process(self, order):
return self.database.save(order)
print("-- DIP লঙ্ঘন: OrderProcessorBad সবসময় MySQLDatabaseConcrete-এই আটকে --")
bad = OrderProcessorBad()
print(bad.process("অর্ডার #101"))
print("\n-- DIP-কমপ্লায়েন্ট: একই OrderProcessor ক্লাস, দুটি ভিন্ন database দিয়ে --")
real_db_processor = OrderProcessor(MySQLDatabase())
print(real_db_processor.process("অর্ডার #201"))
fake_db_processor = OrderProcessor(InMemoryFakeDatabase())
print(fake_db_processor.process("অর্ডার #202"))
print(fake_db_processor.process("অর্ডার #203"))
print("\nOrderProcessor ক্লাসের নিজের কোডে কোনো পরিবর্তন ছাড়াই দুই ধরনের database কাজ করল।")
print(f"real_db_processor.database আসলে MySQLDatabase? {isinstance(real_db_processor.database, MySQLDatabase)}")
print(f"fake_db_processor.database আসলে InMemoryFakeDatabase? {isinstance(fake_db_processor.database, InMemoryFakeDatabase)}")
OrderProcessor ক্লাসের সংজ্ঞায় কোথাও MySQLDatabase বা
InMemoryFakeDatabase নাম লেখা নেই, শুধু Database অ্যাবস্ট্রাকশন। তাই একই
OrderProcessor কোড দুই সম্পূর্ণ ভিন্ন বাস্তবায়নের সাথে কাজ করলো — এটাই DIP-এর ফ্লেক্সিবিলিটি
সুবিধার সরাসরি, চালানো প্রমাণ। OrderProcessorBad এই সুবিধা পায় না — তার কোড পরিবর্তন না করে
কখনো অন্য database ব্যবহার করা যাবে না।
SOLID-এর পাঁচটি অক্ষরই একটি সাধারণ লক্ষ্যের দিকে যায় — পরিবর্তন সহজ, পরীক্ষা সহজ, বোঝা সহজ এমন কোড। DIP এই লক্ষ্যের সবচেয়ে সরাসরি বাস্তবায়ন — নির্দিষ্ট বাস্তবায়নের বদলে অ্যাবস্ট্রাকশনের উপর নির্ভর করে, dependency injection দিয়ে। পরের পাঠে (L19) আমরা UML-এর মাধ্যমে এই ধরনের ক্লাস-সম্পর্ক ভিজ্যুয়ালি দেখানোর মান নোটেশন শিখব।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "Dependency Inversion" নামের "Inversion" (উল্টানো) শব্দটা ঠিক কী উল্টায় — সুনির্দিষ্টভাবে ব্যাখ্যা করুন।
স্বাভাবিক নির্ভরতার দিক উচ্চ-স্তর থেকে নিম্ন-স্তরের কংক্রিট বিস্তারিত ক্লাসের দিকে যায় (উচ্চ-স্তরের কোড নিম্ন-স্তরের ক্লাস সরাসরি জানে ও তৈরি করে)। DIP এই দিক উল্টে দেয় — এখন নিম্ন-স্তরের কংক্রিট ক্লাস একটি অ্যাবস্ট্রাকশনের (ইন্টারফেস) দিকে "নির্ভর করে" (অর্থাৎ সেই ইন্টারফেস মেনে চলে), এবং উচ্চ-স্তরের কোডও একই অ্যাবস্ট্রাকশনের দিকে নির্ভর করে — কেউই কারো কংক্রিট বিস্তারিত রূপ সরাসরি জানে না। নির্ভরতার তীর এখন দুই দিক থেকেই অ্যাবস্ট্রাকশনের দিকে, নিম্ন থেকে উচ্চের দিকে নয়।
প্র ০২
উপরের কোড সেলে OrderProcessorBad-কে টেস্ট করা কেন কঠিন, অথচ OrderProcessor-কে সহজ?
OrderProcessorBad-এর কনস্ট্রাক্টর নিজেই MySQLDatabaseConcrete() তৈরি করে ফেলে —
তাই একে টেস্ট করতে হলে হয় একটি সত্যিকারের MySQL সংযোগ লাগবে (ধীর, ভঙ্গুর, টেস্ট পরিবেশে অবাস্তব), নয়তো
কোনো উপায়ই নেই আলাদা করে যাচাই করার যে OrderProcessorBad-এর নিজস্ব লজিক সঠিক কিনা।
OrderProcessor-এ dependency ইনজেক্ট করা যায় — টেস্টে একটি সরল InMemoryFakeDatabase
(বা আরও ছোট একটি মক) পাস করে দিলেই, কোনো real database ছাড়াই, দ্রুত ও নির্ভরযোগ্যভাবে যাচাই করা যায়
OrderProcessor.process() ঠিকমতো save() ডাকছে কিনা।
প্র ০৩ DIP আর OCP (L16) — দুটোই "existing কোড পরিবর্তন না করে নতুন কিছু যোগ করা" নিয়ে কথা বলে। এই দুটোর সম্পর্ক কী?
DIP আসলে OCP অর্জনের একটি প্র্যাকটিক্যাল টুল। L16-এর OCP বলে entities "extension-এর জন্য open, modification-এর
জন্য closed" হওয়া উচিত। DIP এটি সম্ভব করে — OrderProcessor একটি অ্যাবস্ট্রাকশনের উপর নির্ভর
করার কারণেই, নতুন একটি PostgresDatabase ক্লাস যোগ করা যায় (extension) কোনো existing
OrderProcessor কোড পরিবর্তন (modification) ছাড়াই। DIP ছাড়া, নতুন database সাপোর্ট
করতে হলে OrderProcessorBad-এর ভেতরের কোডই পরিবর্তন করতে হতো — সরাসরি OCP লঙ্ঘন।
অনুশীলন
-
চিন্তা করুন: একটি
NotificationServiceক্লাস আছে যার কনস্ট্রাক্টরের ভেতরে সরাসরিEmailSender()তৈরি হয়। ভবিষ্যতে SMS ও push notification সাপোর্ট করতে হবে। DIP প্রয়োগ করে এই ক্লাসের ডিজাইন কীভাবে বদলাবেন তা লিখুন।একটি অ্যাবস্ট্র্যাক্ট
NotificationChannelইন্টারফেস তৈরি করতে হবে (যেমন একটিsend(message)মেথডসহ), তারপরEmailSender,SMSSender,PushSender— প্রতিটি এই ইন্টারফেস ইমপ্লিমেন্ট করবে।NotificationService-এর কনস্ট্রাক্টর একটিchannel: NotificationChannelপ্যারামিটার নেবে (dependency injection), নিজে কোনো কংক্রিট sender তৈরি করবে না। ফলে নতুন চ্যানেল যোগ করতেNotificationService-এর কোনো কোড পরিবর্তনের দরকার হবে না — ঠিক এই পাঠেরOrderProcessor-এর মতোই। -
পরীক্ষা করুন: উপরের কোড সেলে একটি তৃতীয়
Databaseবাস্তবায়ন যোগ করুন —class LoggingDatabase(Database), যারsave()শুধু একটি প্রিন্ট স্টেটমেন্ট দেয় ("[LOG] would save: ...") — এবং সেটিOrderProcessor-এ ইনজেক্ট করে চালিয়ে দেখুন।নতুন
LoggingDatabaseক্লাসটিDatabase-এরsave()কনট্র্যাক্ট মেনে চললেইOrderProcessor(LoggingDatabase())সরাসরি কাজ করবে —OrderProcessor-এর একটি লাইনও পরিবর্তন করতে হবে না। এটিই DIP-এর মূল সুবিধা বাস্তবে দেখা — নতুন বাস্তবায়ন যোগ করা মানে শুধু একটি নতুন ক্লাস লেখা, existing, ইতিমধ্যে-টেস্ট-করা কোড স্পর্শ করা নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M4-এর শেষ পাঠ (UML) ও M5-এর ডিজাইন প্যাটার্ন সহ পুরো কোর্স ম্যাপ।
- আগের পাঠ L17 SOLID প্রিন্সিপল — LSP ও ISP।
- পরের পাঠ L19 UML বেসিকস — ক্লাস ও সিকোয়েন্স ডায়াগ্রাম, M4-এর শেষ পাঠ।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design ও Software Engineering & Git — সব এক জায়গায়।