Bridge Pattern: একটা রিমোট, অনেক ডিভাইস — subclass বিস্ফোরণ থামাও
টিভি আর রিমোটের গল্প দিয়ে Bridge pattern শেখো। একটা বড় class কে দুই ভাগে ভেঙে আলাদাভাবে বাড়াও — বাড়তি subclass-এর ঝামেলা আর নেই।
📺 রিমোটে ভরা দেরাজ
ধরো তারিকদের বাড়িতে গ্যাজেটের ছড়াছড়ি। ড্রয়িং রুমে টিভি। শোবার ঘরে রেডিও — দাদা প্রতিদিন সকালে শোনেন। গত ঈদে আব্বু একটা সেট-টপ বক্সও নিয়ে এলেন। আর প্রতিটা ডিভাইসের সাথে এলো... একটা করে রিমোট।
টিভির রিমোট। রেডিওর রিমোট। সেট-টপ বক্সের রিমোট। তারপর পুরনো টিভির রিমোট নষ্ট হলো, আব্বু কিনলেন নতুন "অ্যাডভান্সড" রিমোট। এখন টিভির নিচের দেরাজটা রিমোটে ঠাসা। প্রতিদিন সন্ধ্যায় একই ঝামেলা: "রুবেল! কোন রিমোট কোন জিনিসের?"
এক শুক্রবারে তারিকের মামা জামাল সাহেব ঢাকা থেকে আসলেন একটা উপহার নিয়ে — একটা ইউনিভার্সাল রিমোট। একটাই রিমোট, সহজ বোতাম: পাওয়ার, ভলিউম আপ, ভলিউম ডাউন। পাশে একটা ছোট সুইচ — যেটা দিয়ে ঠিক করা যায় কোন ডিভাইসে কথা বলবে। টিভির দিকে তাক করলে টিভি চলে। সুইচ পাল্টাও, রেডিওর দিকে তাক করো, রেডিও চলে। দাদা একদিন সন্দেহ করলেন, তারপর পুরনো সব রিমোট "নিরাপত্তার জন্য" আলমারিতে ঢুকিয়ে দিলেন।
একটু ভাবো এটা কীভাবে কাজ করে। ইউনিভার্সাল রিমোট জানে না টিভির ভেতরে কী হয়। জানে না রেডিওর ভেতরে কী হয়। সে শুধু একটা ছোট সাধারণ ভাষা জানে: "চালু করো", "বন্ধ করো", "ভলিউম বাড়াও", "ভলিউম কমাও"। প্রতিটা ডিভাইস প্রতিশ্রুতি দেয় — এই ছোট ভাষা বুঝবে। রিমোট ভাষায় কথা বলে, ডিভাইস আসল কাজটা করে।
এখন দুটো দিক আলাদাভাবে বাড়তে পারে:
- রিমোট কোম্পানি বেসিক রিমোট, বাচ্চাদের লক বোতামওয়ালা রিমোট, আর প্রিমিয়াম মিউট বোতামওয়ালা রিমোট বাজারে আনতে পারে। টিভি খুলতে হবে না।
- ডিভাইস কোম্পানিগুলো নতুন টিভি, রেডিও, প্রজেক্টর, সাউন্ডবার বাজারে আনতে পারে। রিমোট ডিজাইন করতে হবে না।
কেউ কখনো "AdvancedRemoteForSonyTV" নামে জোড়া লাগানো জিনিস বানায় না। যেকোনো রিমোট যেকোনো ডিভাইস চালাতে পারে, কারণ মাঝখানে একটা ছোট, সম্মত interface আছে। সেই সংযোগকারী লিংকটাই হলো bridge — আর এটাই ঠিক Bridge pattern।
রুবেলের পুরো সেই শুক্রবারটা একটা যাত্রা হিসেবে দেখো। লক্ষ্য করো ইউনিভার্সাল রিমোট আসার পর মেজাজ কীভাবে বদলে যায়:
🌉 Bridge pattern কী?
সহজ ভাষায় সংজ্ঞাটা এরকম।
Bridge pattern হলো একটা structural design pattern যেটা একটা বড় class (বা ঘনিষ্ঠভাবে যুক্ত class-এর সেট) কে দুটো আলাদা hierarchy-তে ভেঙে দেয় — একটা abstraction (উঁচু স্তরের নিয়ন্ত্রণের দিক) আর একটা implementation (নিচু স্তরের কাজের দিক) — যেগুলো একটা সহজ object reference দিয়ে যুক্ত, যেন দুটো দিক স্বাধীনভাবে পরিবর্তন হতে আর বাড়তে পারে।
দুটো বিশেষ শব্দ বুঝে নাও, কারণ প্রথমে সবাই এগুলোতে গুলিয়ে ফেলে:
| Pattern-এর ভূমিকা | মানে | তারিকদের গল্পে |
|---|---|---|
| Abstraction | যে দিকে ব্যবহারকারী কথা বলে; বন্ধুসুলভ, উঁচু স্তরের operation দেয়; নিজে আসল কাজটা করে না | ইউনিভার্সাল রিমোট |
| Refined Abstraction | abstraction-এর একটু উন্নত সংস্করণ | মিউট বোতামওয়ালা প্রিমিয়াম রিমোট |
| Implementation | নিচু স্তরের operation-এর ছোট সম্মত interface | সাধারণ ভাষা: চালু, বন্ধ, ভলিউম সেট |
| Concrete Implementation | একটা আসল কর্মী যে তার নিজস্ব উপায়ে interface পূরণ করে | টিভি, রেডিও, সেট-টপ বক্স |
abstraction একটা implementation object-এর reference ধরে রাখে আর সেটার মাধ্যমে কাজ ফরওয়ার্ড করে। সেই reference — has-a লিংকটাই — bridge। রিমোট একটা ডিভাইস ধরে রাখে; সে কখনো একটা ডিভাইসের প্রকার নয়।
সাবধান! Bridge pattern-এ "implementation" মানে "abstraction-এর subclass" নয়। দুটো দিক হলো সমান্তরাল পরিবার — নদীর দুই পাড়ের মতো পাশাপাশি দাঁড়িয়ে। রিমোট কখনো একটা ডিভাইসের প্রকার নয়, আর ডিভাইস কখনো রিমোটের প্রকার নয়। এরা শুধু bridge reference দিয়ে যুক্ত। শুধু এই একটা বাক্য মনে রাখলে pattern-এর অর্ধেক বোঝা হয়ে গেছে।
পুরো ধারণাটা এক পাতায়:
💥 এটা কোন সমস্যা সমাধান করে
আমাদের এটা কেন দরকার? দেখো Bridge ছাড়া কী হয় — আর এবার ক্ষতিটা ঠিকমতো হিসাব করো।
ধরো রুবেল সাধারণ inheritance দিয়ে রিমোটের software লিখতে শুরু করে। সে প্রতিটা রিমোট-ডিভাইস জোড়ার জন্য একটা class বানায়:
// BAD: one class per combination. Watch what happens...
class BasicRemoteForTv {
/* basic buttons + TV wiring code */
}
class BasicRemoteForRadio {
/* SAME basic buttons + radio wiring code */
}
class AdvancedRemoteForTv {
/* extra buttons + SAME TV wiring code */
}
class AdvancedRemoteForRadio {
/* extra buttons + SAME radio wiring code */
}২টা রিমোট আর ২টা ডিভাইসেই ৪টা class — আর প্রচুর কপি করা কোড। এবার আব্বু একটা সেট-টপ বক্স কিনলেন। লাগবে BasicRemoteForSetTopBox আর AdvancedRemoteForSetTopBox। ছয়টা class। তারপর কোম্পানি বাচ্চাদের রিমোট ডিজাইন করল। নয়টা class! প্রতিটা নতুন রিমোটের ধরন প্রতিটা ডিভাইসের সাথে গুণ হয়, আর প্রতিটা নতুন ডিভাইস প্রতিটা রিমোটের সাথে গুণ হয়।
হিসাব সবসময় রিমোট × ডিভাইস:
2 remotes × 2 devices = 4 classes
3 remotes × 3 devices = 9 classes
4 remotes × 5 devices = 20 classes!এই লাগামছাড়া বৃদ্ধিকে বলে Cartesian explosion (বা class explosion)। এটা হয় কারণ আমরা পরিবর্তনের দুটো স্বাধীন মাত্রা — কী ধরনের রিমোট আর কী ধরনের ডিভাইস — একটাই inheritance tree-তে গুঁজে দিচ্ছি। Inheritance শুধু একটা মাত্রা পরিষ্কারভাবে model করতে পারে। দ্বিতীয়টা জোর করে ঢুকিয়ে দিলে এক অক্ষের প্রতিটা নতুন মান অন্য অক্ষের প্রতিটা মানের সাথে গুণ হয়ে যায়।
Bridge বলে: গুণ করা বন্ধ করো, যোগ করো। রিমোট এক পরিবারে রাখো, ডিভাইস আরেক পরিবারে রাখো, আর reference দিয়ে যুক্ত করো। তাহলে ৩ রিমোট + ৩ ডিভাইস = মাত্র ৬টা class — ৯টার বদলে। আর ৪ রিমোট + ৫ ডিভাইস = ৯টা class — ২০টার বদলে। দুটো পদ্ধতির পার্থক্য অবিশ্বাস্য দ্রুত বাড়ে — দেখো বারগুলো লাইন থেকে কত দূরে চলে যাচ্ছে:
কলেজ কর্নার: এটা হলো discrete maths-এর Cartesian product software-এর পোশাক পরে। যদি R সেটে রিমোটের ধরন থাকে আর D সেটে ডিভাইসের ধরন, তাহলে inheritance তোমাকে R × D-এর প্রতিটা উপাদানের জন্য একটা class লিখতে বাধ্য করে, যার আকার m × n। Bridge এই গুণফলকে disjoint union দিয়ে প্রতিস্থাপন করে: তুমি mটা abstraction class আর nটা implementation class লেখো, মোট m + n। complexity ভাষায়, class সংখ্যা O(m·n) থেকে O(m + n)-এ নেমে আসে। এই কৌশলটা সাধারণ: তিনটা স্বাধীন মাত্রায় (যেমন message type × channel × language), naive inheritance-এ m·n·k class লাগে, composition-এ লাগে m + n + k। যখনই design-এ গুণ দেখবে, সেটাকে যোগে পরিণত করার উপায় খোঁজো — যেকোনো একটা pattern মুখস্থ করার চেয়ে এই স্বজ্ঞাটা বেশি মূল্যবান।
এখানে আরেকটা যন্ত্রণা লুকিয়ে আছে: duplication। BasicRemoteForTv আর AdvancedRemoteForTv-এর ভেতরের TV-নিয়ন্ত্রণের কোড একদম একই। একটায় bug ঠিক করলে অন্যটায়ও ঠিক করতে মনে রাখতে হবে। এভাবেই bug টিকে থাকে। একটা সাধারণ বিস্ফোরিত পরিবারে বেশিরভাগ কোডই কপি:
প্রতিটা class-এ মাত্র প্রায় এক-চতুর্থাংশ কোড আসলে নতুন। Bridge ঠিক সেই চতুর্থাংশটা রেখে বাকিটা মুছে দেয়।
🛠️ ধাপে ধাপে এটা কীভাবে কাজ করে
এখানে Bridge তৈরির রেসিপি, ধাপে ধাপে।
- দুটো স্বাধীন মাত্রা খুঁজে বের করো। জিজ্ঞেস করো: "এই class পরিবারটা বাড়তে থাকার দুটো আলাদা কারণ কী?" আমাদের গল্পে: রিমোটের feature বাড়ে, আর ডিভাইসের ধরন বাড়ে। বাস্তব project-এ সাধারণ জোড়া: app logic বনাম operating system, GUI বনাম platform, notification type বনাম delivery channel।
- কোন দিকটা Implementation হবে ঠিক করো। নিচু স্তরের, বেশি "hardware-ish" দিকটা বেছে নাও। এখানে, ডিভাইস। অন্য দিকটা (রিমোট) হবে Abstraction।
- Implementation interface লেখো। ছোট আর primitive রাখো:
enable(),disable(),getVolume(),setVolume()। abstraction এই ছোট ইটগুলো দিয়ে বড় আচরণ তৈরি করবে। - Abstraction class লেখো Implementation type-এর একটা field সহ। প্রতিটা উঁচু স্তরের method শুধু সেই field-এর মাধ্যমে কাজ করবে।
- Refined Abstraction যোগ করো — abstraction-এর subclass, উন্নত variant-এর জন্য (যেমন
mute()বোতামওয়ালা advanced remote)। - Concrete Implementation লেখো — প্রতিটা ডিভাইসের জন্য একটা class, যেটা তার নিজস্ব উপায়ে interface পূরণ করে।
- Client-এ সংযুক্ত করো: একটা ডিভাইস তৈরি করো, সেটা রিমোটের constructor-এ দাও, আর রিমোট ব্যবহার করো।
diagram-এর আকারটা দেখো? দুটো টাওয়ার, একটা পাতলা লিংক। বাম টাওয়ার (রিমোট) নতুন রিমোটের ধরন দিয়ে নিচে বাড়তে পারে। ডান টাওয়ার (ডিভাইস) নতুন ডিভাইসের ধরন দিয়ে নিচে বাড়তে পারে। দুটোকে শুধু মাঝখানের ছোট Device interface মানতে হবে।
এখন একটা বোতাম চাপার পুরো পথটা bridge জুড়ে দেখা যাক। রুবেল রিমোটে "volume up" চাপে, আর কাজটা reference পেরিয়ে যে ডিভাইস সংযুক্ত সেখানে পৌঁছে যায়:
রিমোট কখনো "TV" বলে না। সে interface-এর পেছনে যা আছে তাকে getVolume() আর setVolume() বলে। TV-কে radio দিয়ে বদলে দাও, রিমোটের দিকের একটাও তীর বদলাবে না।
ডিভাইস নিজে একটা সহজ জীবন কাটায়, যেটা state হিসেবে আঁকা যায়। রিমোটের বোতামগুলো শুধু event যেগুলো ডিভাইসকে এই state-গুলোর মধ্যে নাড়াচাড়া করায়:
একটা সুন্দর জিনিস লক্ষ্য করো: এই state chart টিভি, রেডিও, আর ভবিষ্যতের যেকোনো প্রজেক্টরের জন্যও সত্যি। state-গুলো implementation দিকের, বোতাম চাপা আসে abstraction দিক থেকে, আর bridge event বহন করে নিয়ে যায়।
💻 বাস্তব কোডের উদাহরণ
রুবেলের ড্রয়িং রুম TypeScript-এ code করা যাক। সাবধানে দেখো দুটো মাত্রা কীভাবে দুটো আলাদা জায়গায় থাকে, আর কীভাবে যেকোনো remote যেকোনো device চালাতে পারে।
// =======================================================
// DIMENSION 1: the IMPLEMENTATION side (devices)
// Small, primitive operations only.
// =======================================================
interface Device {
isEnabled(): boolean;
enable(): void;
disable(): void;
getVolume(): number;
setVolume(percent: number): void;
getName(): string;
}
class Tv implements Device {
private on = false;
private volume = 30;
isEnabled() { return this.on; }
enable() { this.on = true; console.log("TV: screen lights up"); }
disable() { this.on = false; console.log("TV: screen goes dark"); }
getVolume() { return this.volume; }
setVolume(percent: number) {
this.volume = Math.max(0, Math.min(100, percent));
console.log(`TV: volume is now ${this.volume}`);
}
getName() { return "TV"; }
}
class Radio implements Device {
private on = false;
private volume = 50;
isEnabled() { return this.on; }
enable() { this.on = true; console.log("Radio: crackles to life"); }
disable() { this.on = false; console.log("Radio: goes silent"); }
getVolume() { return this.volume; }
setVolume(percent: number) {
this.volume = Math.max(0, Math.min(100, percent));
console.log(`Radio: volume is now ${this.volume}`);
}
getName() { return "Radio"; }
}
// =======================================================
// DIMENSION 2: the ABSTRACTION side (remotes)
// High-level buttons, built from the device primitives.
// =======================================================
class RemoteControl {
// THE BRIDGE: the remote holds a device by reference.
constructor(protected device: Device) {}
togglePower(): void {
if (this.device.isEnabled()) this.device.disable();
else this.device.enable();
}
volumeUp(): void {
this.device.setVolume(this.device.getVolume() + 10);
}
volumeDown(): void {
this.device.setVolume(this.device.getVolume() - 10);
}
}
// A refined abstraction: extra feature, but it still talks
// ONLY through the Device interface. It has no idea whether
// a TV or a radio is on the other side of the bridge.
class AdvancedRemote extends RemoteControl {
mute(): void {
console.log(`AdvancedRemote: muting the ${this.device.getName()}`);
this.device.setVolume(0);
}
}
// =======================================================
// CLIENT: mix ANY remote with ANY device.
// =======================================================
const tv = new Tv();
const basicRemote = new RemoteControl(tv);
basicRemote.togglePower();
basicRemote.volumeUp();
const radio = new Radio();
const smartRemote = new AdvancedRemote(radio);
smartRemote.togglePower();
smartRemote.mute();
// Same advanced remote class, pointed at the TV instead:
const smartTvRemote = new AdvancedRemote(tv);
smartTvRemote.mute();
// Output:
// TV: screen lights up
// TV: volume is now 40
// Radio: crackles to life
// AdvancedRemote: muting the Radio
// Radio: volume is now 0
// AdvancedRemote: muting the TV
// TV: volume is now 0class গণনা করো: ২ remote + ২ device = ৪টা class, আর ৪টা combination বিনামূল্যে পেয়ে গেলে। inheritance পদ্ধতিতে ইতিমধ্যে ৪টা combination class লাগত, আর পরের ধাপে ৯টা।
তিনটা সুন্দর জিনিস লক্ষ্য করো:
AdvancedRemote.mute()একবার লেখা হয়েছে আর তাৎক্ষণিকভাবে সব device-এ কাজ করে — বর্তমান ও ভবিষ্যত। আগামীকালেরProjectorclassDeviceimplement করার মুহূর্তেই mute support পেয়ে যাবে।- remote-কে runtime-এ পুনরায় তাক করা যায় — আমরা radio-র জন্য একবার আর TV-র জন্য একবার
AdvancedRemoteতৈরি করেছি। তুমি এমনকিsetDevice()method যোগ করতে পারো আর program চলার সময় device বদলাতে পারো, ঠিক জামাল সাহেবের ইউনিভার্সাল রিমোটের ছোট সুইচের মতো। - প্রতিটা দিক আলাদাভাবে test করা যায়। remote-কে একটা নকল device দাও, আর কোনো আসল TV code ছাড়াই সব বোতাম test করো।
🌐 C# আর Python-এ একই ধারণা
একই ড্রয়িং রুম, সংক্ষেপে, C#-এ:
// Implementation side
public interface IDevice
{
bool IsEnabled { get; }
void Enable();
void Disable();
int Volume { get; set; }
}
public class Tv : IDevice
{
public bool IsEnabled { get; private set; }
public int Volume { get; set; } = 30;
public void Enable() { IsEnabled = true; Console.WriteLine("TV on"); }
public void Disable() { IsEnabled = false; Console.WriteLine("TV off"); }
}
// Abstraction side — holds the bridge reference
public class RemoteControl
{
protected readonly IDevice Device;
public RemoteControl(IDevice device) => Device = device;
public void TogglePower()
{
if (Device.IsEnabled) Device.Disable();
else Device.Enable();
}
}
public class AdvancedRemote : RemoteControl
{
public AdvancedRemote(IDevice device) : base(device) { }
public void Mute() { Device.Volume = 0; Console.WriteLine("Muted"); }
}
// Client
var remote = new AdvancedRemote(new Tv());
remote.TogglePower(); // Output: TV on
remote.Mute(); // Output: Mutedআর ড্রয়িং রুমের বাইরে pattern দেখাতে, এখানে বিখ্যাত notification system সংস্করণটা Python-এ — abstraction দিকে message type, implementation দিকে delivery channel। এই design অগণিত বাস্তব backend-এ দেখা যায়:
# Implementation side: delivery channels (the bricks)
class EmailChannel:
def deliver(self, text: str) -> None:
print(f"EMAIL: {text}")
class SmsChannel:
def deliver(self, text: str) -> None:
print(f"SMS: {text}")
# Abstraction side: notification types (the walls)
class Notification:
def __init__(self, channel):
self._channel = channel # THE BRIDGE
def send(self, text: str) -> None:
self._channel.deliver(text)
class UrgentNotification(Notification):
def send(self, text: str) -> None:
for _ in range(3): # urgent means three times!
self._channel.deliver(f"URGENT: {text}")
# Client: any type rides any channel
UrgentNotification(SmsChannel()).send("School closed tomorrow")
Notification(EmailChannel()).send("PTM on Friday")
# Output:
# SMS: URGENT: School closed tomorrow
# SMS: URGENT: School closed tomorrow
# SMS: URGENT: School closed tomorrow
# EMAIL: PTM on Fridayপ্রতিটা ভাষায় একই ছবি: নিয়ন্ত্রণ পরিবার আর কর্মী পরিবার আলাদা দাঁড়িয়ে, আর একটা constructor-এ inject করা reference তাদের যুক্ত করে।
কলেজ কর্নার: C++ programmers Bridge-কে দুটো পুরনো নামে চেনে — Handle/Body আর Pimpl (pointer to implementation)। Pimpl-এ, একটা class-এর header শুধু একটা পাতলা handle expose করে, আর একটা pointer লুকানো body class-এ নিয়ে যায় যেখানে সব private member থাকে। এটা compile-time dependency কমায়: body বদলালেও সব ফাইল যেগুলো header include করে সেগুলো recompile হতে বাধ্য হয় না। এটা আমাদের remote আর device-এর মতোই আকৃতি — স্থিতিশীল সামনে, পরিবর্তনযোগ্য পেছনে, একটা pointer দিয়ে যুক্ত। আরও লক্ষ্য করো Bridge কীভাবে dependency injection-এর সাথে সম্পর্কিত: device-টা remote-এর constructor-এ দেওয়াটাই constructor injection। DI framework এই কাজটাকেই শিল্পমাত্রায় করে, তাই ভালোভাবে design করা DI-heavy codebase-গুলো নীরবে bridge-এ ভরপুর।
🌍 বাস্তব software-এ কোথায় দেখা যায়
Bridge শেখার পর সর্বত্র দেখতে পাবে।
- Java-তে JDBC drivers। তোমার Java program উঁচু স্তরের
java.sqlinterface-এর (abstraction) সাথে কথা বলে: connection খোলো, query চালাও, result পড়ো। পেছনে, MySQL, PostgreSQL, বা Oracle-এর জন্য JDBC driver (implementation) database-নির্দিষ্ট কাজটা করে। database বদলালেও তোমার code বদলায় না — শুধু ভিন্ন driver লাগাও। InformIT-এর "A Classic Example of Bridge: Drivers" আর্টিকেলটা ঠিক এটাই ব্যাখ্যা করে, আর C#-এ ADO.NET providersDbConnectionআর provider-নির্দিষ্ট implementation দিয়ে একই আকৃতি অনুসরণ করে। - Operating system-এ device driver। OS একটা "printer" বা "disk"-এর (abstraction) একটা স্থিতিশীল ধারণা expose করে। প্রতিটা manufacturer নির্দিষ্ট hardware-এর জন্য সেই contract পূরণ করে এমন একটা driver (implementation) পাঠায়। প্রতি মাসে নতুন printer model আসে, তবু Windows-কে নতুন করে লিখতে হয় না — এটাই দুটো মাত্রা স্বাধীনভাবে বাড়া।
- GUI toolkit। প্রাথমিক Java AWT "peer" class ব্যবহার করত: এক দিকে cross-platform component API, অন্য দিকে প্রতিটা operating system-এর জন্য আসল drawing করা peer। আধুনিক graphics layer drawing API আর platform back-end-এর মধ্যে একই বিভাজন রাখে।
- Notification system। interview-র classic যেটা তুমি এইমাত্র Python-এ code করলে: abstraction দিকে message type (alert, reminder, promotion), implementation দিকে delivery channel (email, SMS, WhatsApp, push)। Bridge ছাড়া: type × channel class। Bridge দিয়ে: type + channel।
- Game engine। একটা renderer abstraction (sprite আঁকো, mesh আঁকো) DirectX, Vulkan, বা Metal back-end-এ bridge করে। gameplay team আর graphics team নদীর বিপরীত পাড়ে কাজ করে, খুব কমই একে অপরকে আটকায়।
- পড়ার জন্য open-source উদাহরণ। iluwatar/java-design-patterns Bridge example অস্ত্রকে enchantment-এর সাথে bridge করে। Refactoring.Guru-র Bridge page ঠিক সেই remote-and-device উদাহরণটা নিয়ে আলোচনা করে যেটা তুমি আজ শিখলে।
✅ কখন ব্যবহার করবে আর কখন করবে না
জামাল সাহেব তার প্রতিবেশীকে ইউনিভার্সাল রিমোট উপহার দেননি যে মাত্র একটা ফ্যান আর একটা সুইচ ব্যবহার করে। এই pattern তখনই কাজে লাগে যখন উভয় মাত্রা সত্যিই পরিবর্তিত হয়। এই টেবিলের বিপরীতে তোমার পরিস্থিতি মিলিয়ে দেখো:
| পরিস্থিতি | Bridge ব্যবহার করবে? | কেন |
|---|---|---|
| তোমার class পরিবার দুটো স্বাধীন দিকে বাড়ছে (shape × colour, remote × device, message × channel) | হ্যাঁ | এটাই classic trigger — গুণফল মেরে ফেলো |
RedCircle, BlueCircle, RedSquare নামের subclass দেখা যাচ্ছে | হ্যাঁ | Cartesian explosion ইতিমধ্যে শুরু হয়ে গেছে |
| runtime-এ নিচু স্তরের অংশ বদলাতে চাও (database বদলাও, renderer বদলাও) | হ্যাঁ | implementation একটা held reference, তাই live বদলানো যায় |
| দুটো team দুটো দিকে কাজ করতে চায় একে অপরের জন্য অপেক্ষা না করে | হ্যাঁ | প্রতিটা team একটা stable interface-এর পেছনে একটা hierarchy-র মালিক |
| client code কখনো platform বিবরণ দেখুক না চাও | হ্যাঁ | Client শুধু abstraction-এর interface-এর উপর নির্ভর করে |
| তোমার class শুধু এক দিকে পরিবর্তিত হয় | না | সাধারণ inheritance বা একটা সহজ strategy যথেষ্ট — Bridge অকারণ জটিলতা যোগ করে |
| "দুটো মাত্রা" আসলে ঘনিষ্ঠভাবে জড়িত আর সবসময় একসাথে বদলায় | না | ভাগ করলে দুটো ফাইল হয় যেগুলো সবসময় একসাথে edit করতে হয় — একটার চেয়ে খারাপ |
| project ছোট আর ছোটই থাকবে | না | বাড়তি interface আর indirection এই ক্ষেত্রে লাভের চেয়ে বেশি খরচ করে |
একই সিদ্ধান্ত একটা ছবিতে — উপরে-ডানের কোণটাই Bridge country:
⚠️ শিক্ষার্থীরা যে সাধারণ ভুল করে
সবচেয়ে সাধারণ ভুল: উঁচু স্তরের logic implementation-এ ঠেলে দেওয়া। Device interface-এ শুধু ছোট, primitive operation থাকা উচিত যেমন setVolume()। যেই মুহূর্তে তুমি muteAndShowMutedIcon() Device interface-এ যোগ করো, প্রতিটা device-কে সেই combined logic কপি করতে হয়, আর abstraction তার কাজ হারায়। মূলনীতি: implementation দেয় ইট, abstraction তৈরি করে দেয়াল।
এড়ানোর মতো আরো ফাঁদ:
- ভাবা যে abstraction = interface আর implementation = class। Bridge-এ, এই শব্দগুলো মানে "নিয়ন্ত্রণের দিক" আর "কাজের দিক"। abstraction সাধারণত একটা সাধারণ class (রিমোট), language
interfaceনয়। শব্দগুলোর দৈনন্দিন অর্থ তোমাকে বিপথে নিয়ে যেতে দিও না। - দুটো দিক inheritance দিয়ে যুক্ত করা। তোমার remote যদি
Tvextends করে, তুমি bridge বন্ধ করে দিয়েছ। সংযোগটা অবশ্যই একটা field (has-a) হতে হবে, কখনো parent (is-a) নয়। - Bridge তৈরি করে কিন্তু মাত্র একটা জোড়া ব্যবহার করা। যদি ঠিক একটা remote আর ঠিক একটা device থাকে, আর দ্বিতীয়টার পরিকল্পনাও না থাকে, তুমি শুকনো নদীতে সেতু বানিয়েছ। দ্বিতীয় মাত্রাটা সত্যিই দেখা না দেওয়া পর্যন্ত অপেক্ষা করো।
- Abstraction-কে concrete device দেখতে দেওয়া। remote-এর ভেতরে
if (device instanceof Tv)এর মতো code পুরো সুবিধাটা নষ্ট করে। abstraction শুধু interface-এর মাধ্যমে কাজ করতে পারবে। - Implementation interface মোটা করা।
Device-এ বিশটা method মানে প্রতিটা নতুন device-কে বিশটা method লিখতে হবে। ইটের সেট ছোট রাখো; remote-কে ইট জোড়া দিতে দাও।
👪 Bridge-এর নিকটাত্মীয়দের সাথে তুলনা
Bridge-এর সাথে অন্য যেকোনো কিছুর চেয়ে Adapter আর Strategy নিয়ে বেশি গুলিয়ে ফেলা হয়। এখানে পাশাপাশি দেখো:
| প্রশ্ন | Bridge | Adapter | Strategy |
|---|---|---|---|
| কখন লাগানো হয়? | আগে থেকে, design করার সময় | পরে, উদ্ধার হিসেবে | design করার সময়, algorithm-এর জন্য |
| লক্ষ্য কী? | দুটো মাত্রাকে স্বাধীনভাবে বাড়তে দাও | দুটো বিদ্যমান, বেমানান interface মেলাও | runtime-এ algorithm বদলাও |
| টুকরোগুলো মেলার জন্য design করা হয়েছিল? | হ্যাঁ, ইচ্ছে করে | না — এটাই পুরো সমস্যা | হ্যাঁ |
| আলাদা করা দিকটা কত বড়? | implementation-এর পুরো hierarchy | সাধারণত একটা wrapped adaptee | বিনিময়যোগ্য algorithm-এর পরিবার |
| মনে রাখার কৌশল | "নদীতে পরিকল্পিত সেতু" | "জরুরি অবস্থায় কেনা ট্রাভেল অ্যাডাপ্টার" | "স্কুলে যাওয়ার পথ বেছে নাও" |
ব্যাপারটা সহজ কথায়: Bridge পরিকল্পিত; Adapter উদ্ধারকারী। Bridge দিয়ে, তুমি তৈরি করার আগে বসে বলো, "রিমোট আর ডিভাইস দুটোই বাড়তে থাকবে — এখনই আলাদা করি।" Adapter দিয়ে, charger আর socket ইতিমধ্যে বিদ্যমান, মেলে না, আর তুমি দোকানে দৌড়াও একটা সমাধানের জন্য। আগের পোস্টের রহিমের কথা মনে আছে? সে কখনো চল্লিশ টাকার adapter পরিকল্পনা করেনি। জামাল সাহেবের ইউনিভার্সাল রিমোট, অন্যদিকে, শুরু থেকেই অনেক device চালানোর জন্য design করা হয়েছিল।
Strategy-ও একটু বলা দরকার: এর structure (একটা held interface-এ delegate করা একটা class) Bridge-এর মতোই দেখায়। পার্থক্য হলো intent — Strategy একটা algorithm বদলায় (যেমন বিভিন্ন sorting পদ্ধতি), আর Bridge একটা পুরো implementation dimension আলাদা করে। তোমার "strategy" দিক যদি নিজস্ব সমৃদ্ধ hierarchy তৈরি করতে শুরু করে, অভিনন্দন — তুমি একটা Bridge আবিষ্কার করেছ।
কলেজ কর্নার: GoF বইটা Bridge-কে একটা এক-লাইনের সারাংশ দিয়েছে যা পরীক্ষায় উদ্ধৃত করার যোগ্য — "decouple an abstraction from its implementation so that the two can vary independently." এটা "favour composition over inheritance" design নীতিরও সবচেয়ে পরিষ্কার উদাহরণ: জোড়া লাগানো inheritance design compile time-এ remote–device জোড়া hard-code করে, আর composed design সেটা runtime-এ defer করে একটা constructor argument হিসেবে। Bridge একসাথে দুটো অক্ষে open/closed principle-ও সক্ষম করে — device না ছুঁয়ে remote বাড়ানো যায় আর remote না ছুঁয়ে device বাড়ানো যায়।
📦 দ্রুত রিভিশন বক্স
+=====================================================================+
| BRIDGE PATTERN — REVISION CARD |
+=====================================================================+
| Type : Structural pattern |
| Nicknames : Handle/Body, Pimpl (C++) |
| Story : Universal remote + TV / radio / set-top box |
| |
| Players : Abstraction -> remote (high-level buttons) |
| RefinedAbstraction -> advanced remote (mute) |
| Implementation -> Device interface (primitives) |
| ConcreteImpl -> Tv, Radio, SetTopBox |
| |
| The bridge : a has-a REFERENCE from remote to device |
| Big win : remotes + devices, NOT remotes x devices |
| Maths : O(m+n) classes instead of O(m*n) |
| Runtime : implementation can be swapped on a live object |
| |
| Remember : Implementations give BRICKS, |
| abstractions build WALLS. |
| vs Adapter : Bridge is planned; Adapter is a rescue. |
+=====================================================================+🏋️ অনুশীলন
তোমার পালা! প্রথমে কাগজে কাজ করো, তারপর code করো।
-
যান আর চালক। দুটো মাত্রা model করো: যান (
Car,Bus) আর চালানোর ধরন (LearnerDriverধীরে চালায় আর বাঁক নেওয়ার আগে হর্ন দেয়,ExpertDriverস্বাভাবিক গতিতে চালায়)।Driver-কে abstraction বানাও যেটাaccelerate(kmph),steer(direction),honk()এর মতো primitive সহVehicleimplementation ধরে রাখে। দেখাও যে দুজন চালকই দুটো যান চালাতে পারে — চারটা class থেকে চারটা জোড়া। তারপর একটাTruckযোগ করো আর গণনা করো কতটা নতুন class লাগল — উত্তর ঠিক একটা হওয়া উচিত। -
Message আর channel। এই পোস্টের Python notification system প্রসারিত করো: একটা
WhatsAppChannelআর একটাReminderNotificationযোগ করো যেটা প্রতিটা message-এ "Reminder:" prefix যোগ করে। WhatsApp-এ একটা reminder আর email-এ একটা urgent alert পাঠাও। তারপর এক লাইনে উত্তর দাও: Bridge সহ ৪টা notification type আর ৫টা channel-এর জন্য কতটা class লাগবে, আর Bridge ছাড়া কতটা? চিত্র ৪-এর বিপরীতে তোমার উত্তর যাচাই করো। -
State chart আঁকো। তোমার যান exercise-এর জন্য যানটার একটা state diagram আঁকো যেমন চিত্র ৮ (থামা, চলা, হর্ন দেওয়া)। তারপর mark করো কোন দিক state-এর মালিক (implementation) আর কোন দিক event ছোড়ে (abstraction)। তোমার drawing সেই বিভাজন মেলালে, তোমার bridge সুস্থ।
-
Explosion খুঁজে বের করো। তোমার নিজের code-এ (বা যেকোনো project-এ) দুটো ধারণা জোড়া লাগানো নামের class খোঁজো —
PdfInvoiceExporter,CsvReportExporter,AdminWindowsMenuএর মতো নাম। এরকম একটা পরিবারের জন্য কাগজে sketch করো কীভাবে এটাকে abstraction আর implementation-এ ভাগ করতে। refactor করতে হবে না — শুধু দুটো টাওয়ার আর bridge reference আঁকাটাই আসল শিক্ষা।
"plus, not multiply" কথাটা পরেরবার কোনো class পরিবার দ্বিগুণ হতে শুরু করলেই মাথায় আসলে — Bridge pattern আনুষ্ঠানিকভাবে তোমার হয়ে গেছে। রুবেলের দেরাজ খালি, দাদার আলমারি পুরনো remote পাহারা দিচ্ছে, আর একটা ইউনিভার্সাল remote পুরো বাড়ি চালাচ্ছে। এগিয়ে যাও, তুমি দারুণ করছ!
সচরাচর জিজ্ঞাসা
- সহজ ভাষায় Bridge pattern কী?
- Bridge pattern একটা বড় class পরিবারকে দুটো আলাদা পরিবারে ভেঙে দেয় — দুটো পরিবার নিজেরাই বাড়তে পারে। একটা পরিবার হলো উঁচু স্তরের নিয়ন্ত্রণের দিক (abstraction, যেমন রিমোট), আর অন্যটা হলো নিচু স্তরের কাজের দিক (implementation, যেমন টিভি)। এরা একটা reference দিয়ে যুক্ত — inheritance দিয়ে নয় — তাই এক দিকে পরিবর্তন করলে অন্য দিকে কোনো সমস্যা হয় না।
- এটাকে bridge বলা হয় কেন?
- Bridge হলো সেই reference (has-a লিংক) যেটা abstraction object-কে implementation object-এর সাথে যুক্ত করে। রিমোট একটা ডিভাইসের reference ধরে রাখে আর সব আসল কাজ সেই লিংকের মাধ্যমে পাঠায় — ঠিক যেমন নদীর দুই পাড়ের মধ্যে সেতু দিয়ে যানবাহন চলে।
- Bridge pattern কোন সমস্যা সমাধান করে?
- এটা subclass-এর Cartesian বিস্ফোরণ থামায়। ধরো তোমার ৩ ধরনের রিমোট আর ৪ ধরনের ডিভাইস আছে — inheritance দিয়ে ১২টা combined class লাগবে। Bridge দিয়ে শুধু ৩ + ৪ = ৭টা class লাগবে, আর যেকোনো রিমোট যেকোনো ডিভাইস চালাতে পারবে।
- Bridge আর Adapter-এর পার্থক্য কী?
- সময় আর উদ্দেশ্য। Bridge আগে থেকে পরিকল্পনা করা হয় — তুমি ইচ্ছে করে দুটো আলাদা hierarchy ডিজাইন করো যেন স্বাধীনভাবে বাড়তে পারে। Adapter পরে লাগানো হয় — দুটো আগে থেকে তৈরি, বেমানান interface-কে একসাথে কাজ করাতে।
- Bridge দিয়ে কি runtime-এ implementation বদলানো যায়?
- হ্যাঁ। abstraction যেহেতু implementation-কে একটা সাধারণ object reference হিসেবে ধরে রাখে, তুমি প্রোগ্রাম চলার সময়ও ভিন্ন implementation object দিতে পারো — ঠিক যেমন একই রিমোট অন্য একটা ডিভাইসের দিকে তাক করা।
আরো দেখো
সম্পর্কিত পাঠ
Adapter Pattern: চল্লিশ টাকার প্লাগ যা পুরনো আর নতুন কোডকে মিলিয়ে দেয়
একটা সহজ ৩-পিন প্লাগ আর ২-পিন সকেটের গল্পের মাধ্যমে Adapter pattern শিখো। কোনো পক্ষ না বদলেই পুরনো কোডকে নতুন কোডের সাথে কাজ করাও।
Composite Pattern: বাক্সের ভেতরে বাক্স — একটা জিনিস আর অনেক জিনিসকে একই চোখে দেখো
কুরিয়ারের পার্সেলে বাক্সের ভেতরে বাক্সের উদাহরণ দিয়ে Composite pattern শেখো। একটা আইটেম আর পুরো গ্রুপকে একই interface দিয়ে ট্রিট করো, আর সহজ recursion দিয়ে মোট হিসাব বের করো।
Decorator Pattern: Object-এ একটা একটা করে Layer চাপাও
চায়ের দোকানের এক মজার গল্প দিয়ে Decorator pattern শেখো। নতুন Subclass না বানিয়েই Runtime-এ Object-এ নতুন Behaviour যোগ করো — একটার পর একটা Layer Wrap করে।
Facade Pattern: একটা ফোন কলেই পুরো জটিল ট্রিপ বুক
ট্রাভেল এজেন্টের গল্প দিয়ে Facade pattern শেখো। অনেকগুলো জটিল subsystem-কে একটা সহজ method-এর পেছনে লুকিয়ে রাখো, যাতে client code ছোট আর পরিষ্কার থাকে।