Adapter Pattern: চল্লিশ টাকার প্লাগ যা পুরনো আর নতুন কোডকে মিলিয়ে দেয়
একটা সহজ ৩-পিন প্লাগ আর ২-পিন সকেটের গল্পের মাধ্যমে Adapter pattern শিখো। কোনো পক্ষ না বদলেই পুরনো কোডকে নতুন কোডের সাথে কাজ করাও।
৩-পিন প্লাগ যেটা ঢুকলই না
রাহিমের গ্রীষ্মের ছুটির একটা ছোট্ট গল্প বলি। এই গল্পটা মাথায় রেখো — কারণ এই পুরো পোস্টে বারবার এতে ফিরে আসব।
রাহিমের বয়স চৌদ্দ। প্রতি গ্রীষ্মে সে দাদার পুরনো বাড়িতে যায় — ময়মনসিংহের কাছে একটা গ্রামে। বাড়িটা দারুণ — ঠান্ডা মাটির মেঝে, উঠানে একটা আমগাছ, আর একটা ঘড়ঘড়ে সিলিং ফ্যান যেটা হেলিকপ্টারের মতো শব্দ করে। কিন্তু বাড়িটা অনেক পুরনো — দেয়ালের প্রতিটা সকেট পুরনো ২-পিন সকেট।
প্রথম সন্ধ্যায় রাহিম ব্যাগ খুলে ল্যাপটপের চার্জার বের করল। চার্জারে আছে বড়, আধুনিক ৩-পিন প্লাগ। দেয়ালের কাছে বসে সে সেটা গোঁজার চেষ্টা করল। ঢুকল না। এদিক-ওদিক ঘোরাল। আরও জোরে চাপল। তবুও হলো না। তিন পিন দুই ছিদ্রে ঢোকে না। ল্যাপটপের ব্যাটারি ৯ শতাংশে নেমে আরও কমছে।
দাদা চেয়ার থেকে সব দেখে মুচকি হাসলেন। "বাবা, এই দেয়াল তোমার বাপের চেয়েও পুরনো। তোমার ল্যাপটপের জন্য সে বদলাবে না।"
এখন রাহিমের কাছে কী কী পথ ছিল, সেটা একজন programmer-এর মতো ভাবো।
- নতুন পক্ষ বদলাবে? দামি চার্জারের তার কেটে ২-পিন প্লাগ লাগাবে? না! এতে চার্জার নষ্ট হবে, আর নিজের বাসায় ফিরলে কাজ নাও করতে পারে।
- পুরনো পক্ষ বদলাবে? দাদার দেয়াল ভেঙে নতুন ৩-পিন সকেট লাগাবে? একদমই না! দাদা খুশি হবেন না, মিস্ত্রি দুই দিন লাগাবে — আর এত কিছু শুধু একটা চার্জারের জন্য?
- মাঝখানে একটা টুকরো লাগাবে? রাহিম বাসস্ট্যান্ডের কাছে বিদ্যুৎ ইলেকট্রিক্যালসে গিয়ে চল্লিশ টাকায় একটা ছোট প্লাগ adapter কিনল। Adapter-এর এক পাশে তিনটা ছিদ্র — সেখানে চার্জার ঢোকে। অন্য পাশে দুটো পিন — সেটা দাদার সকেটে ঢোকে। চার্জার adapter-এ, adapter দেয়ালে — ল্যাপটপ চার্জ হতে শুরু করল।
গল্পের সবচেয়ে গুরুত্বপূর্ণ অংশটা লক্ষ করো। কিছুই বদলানো হয়নি। চার্জার হুবহু আগের মতো। দেয়ালের সকেটও হুবহু আগের মতো। একটা ছোট্ট, সস্তা মধ্যবর্তী টুকরো সব মেলানোর কাজ করল।
এই একই ধারণার আরেকটা সংস্করণ আছে রাহিমের পরিবারে। তার চাচাতো বোন সুমাইয়া চট্টগ্রামে পড়ে আর বন্ধুদের সাথে চাটগাঁইয়া ভাষায় কথা বলে। দাদা শুধু ময়মনসিংহের আঞ্চলিক ভাষা বোঝেন। যখন সুমাইয়ার বান্ধবী ফাতেমা ময়মনসিংহে বেড়াতে এলো, দাদা আর ফাতেমা একদমই কথা বলতে পারছিলেন না — যতক্ষণ না সুমাইয়া মাঝখানে বসল। সুমাইয়া চাটগাঁইয়া ভাষায় শুনে ময়মনসিংহের ভাষায় বলছিল, আর ময়মনসিংহের ভাষায় শুনে চাটগাঁইয়ায় বলছিল। সে দাদাকে চাটগাঁইয়া শিখতে বলেনি। ফাতেমাকে ময়মনসিংহের ভাষা শিখতে বলেনি। সে মাঝখানে অনুবাদ করল, আর দুই ঘণ্টা ধরে আড্ডা চলল — বেশিরভাগটাই খাবার নিয়ে।
চল্লিশ টাকার প্লাগ আর চাচাতো বোন সুমাইয়া একই কাজ করছে। সফটওয়্যারে এই কাজের একটা নাম আছে: Adapter pattern।
কোনো code-এ যাওয়ার আগে, রাহিমের পুরো সন্ধ্যাটা একটা যাত্রা হিসেবে দেখো। দেখো adapter আসার সাথে সাথে মেজাজের স্কোর কীভাবে লাফ দেয়।
Adapter pattern কী?
এখানে একটা সহজ সংজ্ঞা যেটা সারাজীবন মনে থাকবে।
Adapter pattern হলো একটা structural design pattern যেটা incompatible interface থাকা দুটো class-কে একসাথে কাজ করতে দেয়। এটা মাঝখানে একটা translator class বসিয়ে কাজ করে। Translator সেই interface উপস্থাপন করে যেটা তোমার code চায়, আর ভেতরে সেই method গুলো call করে যেটা অন্য class-এ আসলে আছে।
Design pattern-এর বইতে রাহিমের গল্পের মানুষজনের বিশেষ নাম আছে। এই চারটা শব্দ ভালো করে শিখে নাও — প্রতিটা textbook, প্রতিটা interview, আর প্রতিটা team আলোচনায় এগুলো আসে।
| Pattern role | অর্থ | রাহিমের গল্পে |
|---|---|---|
| Client | তোমার বিদ্যমান code, যেটা কিছু call করতে চায় | রাহিমের ল্যাপটপ আর তার চার্জার |
| Target | Client যে interface আশা করে | ৩-পিনের আকৃতি |
| Adaptee | কাজের কিন্তু অসামঞ্জস্যপূর্ণ যেটা আগে থেকেই আছে | দাদার ২-পিন দেয়ালের সকেট |
| Adapter | মধ্যবর্তী টুকরো যেটা Target implement করে আর Adaptee-এর সাথে কথা বলে | বিদ্যুৎ ইলেকট্রিক্যালসের চল্লিশ টাকার প্লাগ |
Adapter pattern-কে Wrapper pattern-ও বলা হয়, কারণ adapter পুরনো object-টাকে একটা আবরণের মতো নিজের ভেতরে মুড়ে রাখে। দেয়ালের সকেট এখনো আছে — শুধু adapter-এর পেছনে লুকানো, যেভাবে সুমাইয়ার অনুবাদের পেছনে দাদার আঞ্চলিক ভাষা লুকানো ছিল।
Adapter pattern-এর সোনালি নিয়ম: দুই পক্ষ কখনো বদলিও না — শুধু মাঝখানে একটা টুকরো যোগ করো। Client সবসময় যে interface চেনে সেটাই call করতে থাকে। পুরনো class তার পুরনো method রেখে দেয়। সব রূপান্তরের কাজ একটা ছোট্ট adapter class-এর ভেতরে থাকে। আগামীকাল পুরনো class বদলে গেলে, শুধু adapter ঠিক করো — পুরো program নয়।
পুরো ধারণাটা একটা ছবিতে দেখো, যাতে এগিয়ে যাওয়ার আগে পুরো মানচিত্রটা বুঝতে পারো।
এটা কোন সমস্যার সমাধান করে
প্লাগ থেকে code-এ আসি, কিন্তু সমস্যার আকৃতি একই রাখি। ধরো রাহিমের স্কুল নোটিশ বোর্ডের screen-এর জন্য একটা ছোট আবহাওয়া display app বানাচ্ছে। রাহিম display code লিখেছে, যেটা তাপমাত্রা সংখ্যার একটা সাধারণ list হিসেবে পড়ে:
// The interface Aarav's display code already uses everywhere.
interface TemperatureSource {
readCelsiusValues(): number[];
}
function showWeather(source: TemperatureSource) {
const values = source.readCelsiusValues();
console.log("Today's readings:", values.join(", "));
}সব ঠিকঠাক চলছিল। তারপর বিজ্ঞানের স্যার বাইরে থেকে একটা ready-made sensor library কিনলেন। এটা দ্রুত আর ভালোমতো test করা, কিন্তু সম্পূর্ণ আলাদা style-এ data দেয় — আলাদা নামের একটা method থেকে XML string:
// Third-party class. Aarav CANNOT edit this file.
class XmlWeatherSensor {
pullReadingsXml(): string {
return "<readings><r>31</r><r>33</r><r>29</r></readings>";
}
}রাহিমের display চায় readCelsiusValues() যেটা সংখ্যা return করে। Sensor শুধু দেয় pullReadingsXml() যেটা XML text return করে। এগুলো মেলে না — তিন পিন, দুই ছিদ্র, আবার সেই একই গল্প। showWeather() হয়ে গেছে ল্যাপটপ চার্জার, আর XmlWeatherSensor হয়ে গেছে দাদার দেয়াল।
Adapter pattern ছাড়া, ছাত্ররা সাধারণত এই ফাঁদে পড়ে:
// BAD: conversion code copied at every place that needs the sensor.
const sensor = new XmlWeatherSensor();
const xml = sensor.pullReadingsXml();
const numbers = xml
.split("<r>")
.slice(1)
.map((part) => parseInt(part));
showWeather({ readCelsiusValues: () => numbers });
// ...and the same five lines appear again in ten other files!এটা তিনটা কারণে কষ্টকর। প্রথমত, একই parsing logic সর্বত্র copy হয়ে যায়, আর XML format বদলালে প্রতিটা copy খুঁজে বের করতে হয়। দ্বিতীয়ত, third-party library edit করার লোভ হতে পারে, কিন্তু সেটা তোমার না — পরের আপডেটে তোমার edit হারিয়ে যাবে, ভাড়ার দেয়ালে রং করার মতো। তৃতীয়ত, display code XML বোঝার জন্য rewrite করার কথা মাথায় আসতে পারে, কিন্তু তাহলে প্রতিটা data source-কেও XML হতে হবে। তিনটা পথই চার্জার কাটা বা দাদার দেয়াল ভাঙার coding সংস্করণ।
বেশিরভাগ ছাত্রকে অবাক করে এমন একটা সংখ্যা। বাস্তব integration project-এ, বিরক্তিকর রূপান্তরের কাজ — field rename করা, unit বদলানো, data পুনর্গঠন করা — আসল logic-এর চেয়ে চুপিচুপি বেশি সময় নেয়:
অর্ধেকেরও বেশি সময় শুধু অনুবাদে যায়। Adapter pattern এই কাজটা সরিয়ে দেয় না — অনুবাদ কোথাও না কোথাও হতেই হবে। কিন্তু এটা সবকিছু একটা ছোট্ট, নামকরণ করা জায়গায় জড়ো করে — প্রতিটা ফাইলে ছড়িয়ে যেতে দেওয়ার বদলে।
আর ছড়িয়ে পড়াটা দ্রুত বাড়ে। দেখো app-এ আরও আরও জায়গায় sensor দরকার হলে কী হয়:
বারগুলো হলো copy-paste পদ্ধতি: প্রতিটা call site-এ parsing logic-এর একটা copy। সমতল লাইনটা হলো adapter পদ্ধতি: app যতই বড় হোক সবসময় ঠিক একটাই। সেই সমতল লাইনটাই এই pattern-এর পুরো বিক্রির কথা।
এটা কীভাবে কাজ করে, ধাপে ধাপে
Adapter বানানো একটা সহজ recipe অনুসরণ করার মতো। এখানে ধাপগুলো একে একে দেওয়া হলো।
- Mismatch-টা সত্যিকারের কিনা নিশ্চিত করো। তোমার কাছে একটা client আছে যে একটা interface চায়, আর একটা কাজের class আছে যে আলাদা interface দেয়। তুমি কোনোটাই বদলাতে পারো না (বা উচিত নয়)।
- Target interface বেছে নাও। এটাই তোমার client সবসময় call করে। থাকলে সেটাই ব্যবহার করো। কারণ ছাড়া নতুন বানিও না।
- Adapter class তৈরি করো আর ঘোষণা করো যে এটা Target interface implement করে।
- Adapter-এ একটা field দাও যেটা adaptee object রাখবে। Constructor-এর মাধ্যমে adaptee pass করো। এটাকে বলে object adapter, আর এটাই সাধারণত ব্যবহার করা উচিত।
- Adapter-এর ভেতরে প্রতিটা Target method implement করো। প্রতিটা method adaptee-এর আসল method call করে আর data রূপান্তর করে — format বদলায়, ধারণার নাম বদলায়, argument-এর আকৃতি বদলায়, error অনুবাদ করে।
- Client-কে adapter দাও। Client শুধু Target interface দেখে। সে কখনো জানতে পারে না যে পেছনে একটা পুরনো class লুকিয়ে আছে।
কাঠামোটা এরকম দেখায়:
Diagram-টা বাম থেকে ডানে পড়ো। Client শুধু interface চেনে। Adapter সেই interface implement করে আর চুপচাপ অসামঞ্জস্যপূর্ণ sensor ধরে রাখে। Adapter থেকে sensor-এ তীরটা অনুবাদের সেতু — প্রতিটা প্রত্যাশিত call এক পাশ দিয়ে ঢোকে আর অন্য পাশ দিয়ে পুরনো style-এ বের হয়।
এখন দেখো একটা call layers-এর মধ্য দিয়ে কীভাবে যায়, দেয়াল থেকে চল্লিশ টাকার adapter হয়ে চার্জারে কারেন্ট যাওয়ার মতো:
এই sequence-এ তিনটা ছোট জিনিস লক্ষ করো। Client একটা স্বাভাবিক call করে আর কিছু বিশেষ হয়েছে তা জানে না। Adaptee তার নিজের পুরনো style-এ উত্তর দেয় আর কখনো বিরক্ত হয় না। আর একমাত্র চালাক ধাপটা — parsing — ঘটে adapter-এর ভেতরে, ঠিক একটা জায়গায়।
Adaptee-এর জীবনটাকে একটা ছোট্ট state machine হিসেবেও বলা যায়। Adapter তৈরির আগে, পুরনো class কাজের কিন্তু অপ্রাপ্য। Wrap করার পরে, প্রতিটা request প্রবাহিত হয়:
কলেজ কর্নার: Adapter বানানোর আসলে দুটো উপায় আছে, আর পরীক্ষার প্রশ্নে পার্থক্যটা জিজ্ঞেস করতে ভালোবাসে। Object adapter (আমরা উপরে যেটা বানালাম) adaptee-কে field হিসেবে রাখে — এটা composition, has-a সম্পর্ক। Class adapter adaptee থেকে inherit করে আর একই সাথে target interface implement করে — এটা inheritance, is-a সম্পর্ক। Class adapter-এ multiple class inheritance দরকার হয়, যেটা C++ পারে কিন্তু Java, C#, আর TypeScript পারে না (তারা শুধু multiple interface implement করতে পারে)। Object adapter আরও তিনটা কারণে ব্যবহারিকভাবে জেতে: এটা adaptee এবং তার সব subclass একসাথে adapt করতে পারে, কারণ যেকোনোটা constructor-এ pass করা যায়; এটা runtime-এ অন্য জায়গায় তৈরি object wrap করতে পারে; আর এটা loosely coupled রাখে। Class adapter-এর একমাত্র সুবিধা হলো adaptee-এর protected method override করতে পারা। এখানে তুলনামূলক table:
| প্রশ্ন | Object adapter | Class adapter |
|---|---|---|
| Adaptee-এর সাথে সংযোগ | Field হিসেবে রাখে (composition) | থেকে inherit করে (inheritance) |
| Java, C#, TypeScript-এ কাজ করে? | হ্যাঁ, সর্বত্র | না — multiple class inheritance দরকার (C++) |
| Adaptee-এর subclass adapt করতে পারে? | হ্যাঁ — যেকোনো subclass pass করো | না — একটা parent-এ আটকা |
| Runtime-এ তৈরি object wrap করতে পারে? | হ্যাঁ | না |
| Adaptee-এর protected behaviour override করতে পারে? | না | হ্যাঁ |
| প্রস্তাবিত? | ডিফল্ট পছন্দ | শুধু বিশেষ C++ ক্ষেত্রে |
বাস্তব code-এর উদাহরণ
এখন পুরো গল্পটা TypeScript-এ লিখি — আর হ্যাঁ, রাহিমের প্লাগের গল্পটা সরাসরি code-এ নিয়ে যাব। ল্যাপটপ হলো client। ৩-পিন সকেটের আকৃতি হলো target interface। দাদার পুরনো দেয়ালের সকেট হলো adaptee। আর আমাদের নায়ক, চল্লিশ টাকার adapter, হলো adapter class।
// ---------- TARGET: the interface the laptop expects ----------
// Modern laptops in our story charge only from 3-pin sockets.
interface ThreePinSocket {
// Returns power with three lines: live, neutral, and earth.
givePower(): { live: number; neutral: number; earth: number };
}
// ---------- CLIENT: the laptop ----------
// The laptop is written against ThreePinSocket only.
// It knows NOTHING about old 2-pin sockets.
class Laptop {
charge(socket: ThreePinSocket): void {
const power = socket.givePower();
if (power.earth === 0) {
console.log("Charging safely with earthing. Battery filling up!");
}
}
}
// ---------- ADAPTEE: Dadi's old wall socket ----------
// This class already exists. We must NOT change it.
// Note: different method name, different return shape. No earth line!
class OldTwoPinSocket {
supplyCurrent(): { live: number; neutral: number } {
return { live: 230, neutral: 0 };
}
}
// ---------- ADAPTER: the forty-rupee hero ----------
// It implements the NEW interface and wraps the OLD socket.
class TwoToThreePinAdapter implements ThreePinSocket {
// The adapter HOLDS the old socket. This is composition.
constructor(private oldSocket: OldTwoPinSocket) {}
givePower(): { live: number; neutral: number; earth: number } {
// 1. Call the old method with the old name.
const current = this.oldSocket.supplyCurrent();
// 2. Translate the old shape into the new shape.
// The adapter adds a safe earth connection of its own.
return { live: current.live, neutral: current.neutral, earth: 0 };
}
}
// ---------- WIRING IT TOGETHER ----------
const dadisSocket = new OldTwoPinSocket(); // old thing, untouched
const adapter = new TwoToThreePinAdapter(dadisSocket); // middle piece
const laptop = new Laptop(); // new thing, untouched
laptop.charge(adapter); // the laptop has no idea an old socket is behind!
// Output:
// Charging safely with earthing. Battery filling up!কী হলো সেটা মনোযোগ দিয়ে দেখো। Laptop class কখনো OldTwoPinSocket import করে না। OldTwoPinSocket class কখনো ThreePinSocket-এর কথা শোনেনি। শুধু TwoToThreePinAdapter দুই জগত চেনে — আর অনুবাদ ঠিক একটা জায়গায় থাকে: givePower()-এর ভেতরে।
XML sensor-এর জন্য weather উদাহরণটাও শেষ করতে চাও? এখানে সেই sensor-এর adapter, ছোট আর সুন্দর:
class SensorAdapter implements TemperatureSource {
constructor(private sensor: XmlWeatherSensor) {}
readCelsiusValues(): number[] {
const xml = this.sensor.pullReadingsXml();
// All XML parsing lives HERE and only here.
return xml.split("<r>").slice(1).map((p) => parseInt(p));
}
}
showWeather(new SensorAdapter(new XmlWeatherSensor()));
// Output:
// Today's readings: 31, 33, 29আগামী বছর sensor কোম্পানি তার XML format বদলালে, রাহিম একটা ফাইল খুলবে, একটা method ঠিক করবে, আর পুরো নোটিশবোর্ড app চালু থাকবে। একটা জায়গায় অনুবাদ রাখার শক্তি এটাই।
C# আর Python-এ একই ধারণা
প্রতিটা object-oriented ভাষায় pattern-টা প্রায় একইরকম দেখায়, কারণ ধারণাটা — নতুন implement করো, পুরনো ধরে রাখো — syntax-এর উপর নির্ভর করে না। এখানে C#-এ প্লাগের গল্পের একটা সংক্ষিপ্ত সংস্করণ:
// Target: what the client expects.
public interface IThreePinSocket
{
(int Live, int Neutral, int Earth) GivePower();
}
// Adaptee: the old class we cannot change.
public class OldTwoPinSocket
{
public (int Live, int Neutral) SupplyCurrent() => (230, 0);
}
// Adapter: implements the new, wraps the old.
public class TwoToThreePinAdapter : IThreePinSocket
{
private readonly OldTwoPinSocket _oldSocket;
public TwoToThreePinAdapter(OldTwoPinSocket oldSocket)
=> _oldSocket = oldSocket;
public (int Live, int Neutral, int Earth) GivePower()
{
var (live, neutral) = _oldSocket.SupplyCurrent();
return (live, neutral, 0); // translate old shape to new shape
}
}
// Client code:
IThreePinSocket socket = new TwoToThreePinAdapter(new OldTwoPinSocket());
var power = socket.GivePower();
Console.WriteLine($"Charging with live = {power.Live}V");
// Output: Charging with live = 230Vআর এখানে translator সংস্করণ — চাচাতো বোন সুমাইয়া নিজেই — Python-এ। Python-এ interface keyword নেই, কিন্তু duck typing একই ভূমিকা পালন করে: যার speak_sylheti() method আছে সে দাদার সাথে কথা বলতে পারবে।
# Adaptee: Fatema only speaks Chittagonian dialect. We cannot change her!
class ChittagongSpeaker:
def speak_chittagong(self) -> str:
return "Amar khabar khailam, boro moja asil!"
# Adapter: Sumaiya wraps Fatema and presents local dialect to Dada.
class SumaiyaTheTranslator:
def __init__(self, friend: ChittagongSpeaker):
self._friend = friend # composition: Sumaiya HOLDS her friend
def speak_mymensingh(self) -> str:
chittagong = self._friend.speak_chittagong()
# Translation logic lives here, and only here.
return "Amar khabar khaisi, boro moja asilo. (translated)"
# Client: Dada understands anyone who speaks Mymensingh dialect.
def dada_listens(speaker) -> None:
print("Dada hears:", speaker.speak_mymensingh())
dada_listens(SumaiyaTheTranslator(ChittagongSpeaker()))
# Output: Dada hears: Amar khabar khaisi, boro moja asilo. (translated)তিনটা ভাষায় একই recipe: client বিশ্বাস করে এমন একটা interface, private field হিসেবে রাখা একটা পুরনো object, আর অনুবাদ করা একটা method।
কলেজ কর্নার: লক্ষ করো adapter-এর runtime cost কত — প্রতিটা request-এ একটা বাড়তি object আর একটা বাড়তি method call। আধুনিক hardware-এ এটা nanoseconds, আর JIT compiler প্রায়ই delegation সম্পূর্ণ inline করে দেয়। তাই সাধারণ application code-এ কখনো "performance কারণে" adapter প্রত্যাখ্যান করো না। Adapter-এর আসল cost হলো ধারণাগত: আরেকটা class নামকরণ, test, আর document করার জন্য। সেই cost দেওয়া মূল্যবান যখন অনুবাদ logic অন্যথায় duplicate হতো, আর মূল্যবান নয় একটা জায়গায় একবার ব্যবহৃত এক-লাইনের রূপান্তরের জন্য।
বাস্তব software-এ এটা কোথায় দেখা যায়
Adapter pattern শুধু classroom-এর ধারণা নয়। বাস্তবের বিখ্যাত library প্রতিদিন এটা ব্যবহার করে।
- Java-র
InputStreamReader। Java-তে, একটাInputStreamraw bytes দেয়, কিন্তু অনেক APIReaderচায় যেটা character দেয়।java.io.InputStreamReaderএকটা textbook adapter: এটা একটাInputStreamwrap করে আরReaderinterface উপস্থাপন করে, ভেতরে bytes থেকে characters রূপান্তর করে। তার সহোদরOutputStreamWriterবিপরীত দিকে adapt করে। - Java-র
Arrays.asList()। এটা একটা সাধারণ array নেয় আরListinterface-এর মাধ্যমে উপস্থাপন করে — array যেন list-এর পোশাক পরেছে। - .NET-এর
StreamWriterএকটাStream-এর উপরে। C#-এ, একটাStreambytes নিয়ে কাজ করে, আর text-based codeTextWriterচায়।StreamWriterএকটা byte stream-কেTextWriterinterface-এ adapt করে, তাই text-এর জন্য লেখা code পার্থক্য না জেনেই file, network socket, বা memory-তে লিখতে পারে। - JavaScript HTTP wrapper। অনেক team
axiosবাfetch-এর চারপাশে একটা ছোট adapter লেখে যাতে বাকি appapi.get()আরapi.post()-এর মতো পরিষ্কার, stable interface call করে। Team পরে axios থেকে fetch-এ switch করলে, শুধু adapter ফাইল বদলায়। জনপ্রিয়axios-mock-adapterlibrary-র নামেই "adapter" আছে — এটা testing-এর জন্য যেখানে আসল HTTP layer ছিল সেখানে একটা fake layer বসায়। - Payment gateway। UPI, card, আর wallet গ্রহণ করা বাংলাদেশি app সাধারণত একটা internal
PaymentGatewayinterface আর প্রতিটা provider-এর জন্য আলাদা adapter লেখে। Provider বদলানো এক-ফাইলের কাজ হয়, মাসব্যাপী rewrite নয়। - পড়ার জন্য open-source উদাহরণ। iluwatar/java-design-patterns repository-তে একটা বন্ধুত্বপূর্ণ Adapter উদাহরণ আছে। design-patterns-for-humans repository একই ধারণা আরেকটা মজাদার গল্পে বোঝায়। দুটোই সপ্তাহান্তে পড়ার জন্য দারুণ।
তাই পরের বার Java বা C#-এ ফাইল পড়ার সময় মনে রেখো — একটা adapter সম্ভবত চুপচাপ কাজ করছে তোমার আঙুলের নিচে, ঠিক যেভাবে রাহিমের ল্যাপটপের পেছনে চল্লিশ টাকার প্লাগ চুপচাপ কাজ করছিল।
কখন ব্যবহার করবে, কখন করবে না
সেই গ্রীষ্মে রাহিম প্রতিটা সমস্যায় adapter কিনতে যায়নি। টর্চে নতুন ব্যাটারি দরকার হলে সে শুধু ব্যাটারি কিনেছিল। দক্ষতা হলো কখন মধ্যবর্তী টুকরোর কৌশলটা সঠিক সেটা জানা। এই table-টা ব্যবহার করো:
| পরিস্থিতি | Adapter ব্যবহার করবে? | কেন |
|---|---|---|
| Third-party library-তে দরকারী data আছে কিন্তু interface ভুল | হ্যাঁ | এটাই classic ক্ষেত্র — একটা জায়গায় অনুবাদ করো |
| পুরনো legacy code নতুন system-এর সাথে কাজ করতে হবে, আর পুরনো code বদলাতে পারবে না | হ্যাঁ | Adapter দুই পক্ষকে পরিবর্তন থেকে রক্ষা করে |
| Company একত্রীকরণের পরে দুই team-এর system যুক্ত করতে হবে | হ্যাঁ | প্রতিটা system নিজের style রাখে; adapter যুক্ত করে |
| একটা flaky, দ্রুত-পরিবর্তনশীল library অনেক ফাইলে ব্যবহৃত হয় | হ্যাঁ | একটা adapter-এর মাধ্যমে route করো যাতে ভবিষ্যতের ভাঙা একটা ফাইলে পড়ে |
| তুমি দুটো class-এর মালিক আর সরাসরি একটা method rename করতে পারো | না | সরাসরি code ঠিক করো — middle piece দরকার নেই |
| রূপান্তর একটা জায়গায় ব্যবহৃত এক ছোট্ট লাইন | না | এর জন্য বাড়তি class overkill |
| দুটো interface অর্থগতভাবে সম্পূর্ণ অসম্পর্কিত, শুধু আকৃতিতে নয় | না | Adapter অনুপস্থিত behaviour আবিষ্কার করতে পারে না, শুধু বিদ্যমান behaviour অনুবাদ করতে পারে |
| আসলে নতুন behaviour যোগ করতে চাও, interface বদলাতে চাও না | না | সেই কাজটা Decorator-এর, Adapter-এর নয় |
একই সিদ্ধান্ত একটা ছবিতে — তোমার পরিস্থিতি কোথায় পড়ে খোঁজো:
উপরের-ডান কোণ — বদলানো যাবে না এমন code, অনেক জায়গায় ব্যবহৃত — এটাই adapter-এর নিজস্ব এলাকা। নিচের-বাম — নিজের code, একবার ব্যবহৃত — সেখানে সরাসরি code edit করে এগিয়ে যাও।
ছাত্ররা যে সাধারণ ভুলগুলো করে
এক নম্বর ভুল: adapter-এর ভেতরে business logic রাখা। Adapter শুধু অনুবাদ করবে — নাম বদলানো, format রূপান্তর, data পুনর্গঠন। যখনই তোমার adapter "তাপমাত্রা ৪০-এর উপরে হলে alert পাঠাও" জাতীয় সিদ্ধান্ত নিতে শুরু করে, এটা adapter থাকে না — গুরুত্বপূর্ণ logic-এর লুকানোর জায়গা হয়ে যায় যেটা কেউ খুঁজে পাবে না। Adapter পাতলা, বিরক্তিকর, আর সৎ রাখো — সুমাইয়া দাদার কথা অনুবাদ করেছিল; নিজের মতামত যোগ করেনি।
এড়ানোর জন্য আরও কিছু ফাঁদ:
- Adaptee "সামান্য একটু" edit করা। ছাত্ররা কখনো কখনো কাজ সহজ করতে third-party class-এর একটা method বদলায়। পরের library আপডেটে, পরিবর্তনটা চুপচাপ হারিয়ে যায় আর app ভেঙে পড়ে। Adaptee-কে কখনো ছুঁয়ো না — দাদার দেয়াল যেমন আছে তেমনই থাকে।
- Client-কে adaptee দেখতে দেওয়া। যদি তোমার client code wiring spot-এর বাইরে কোথাও
new XmlWeatherSensor()লেখে, দুই জগতের মধ্যে দেয়াল ভেঙে গেছে। Client সবসময় শুধু Target interface দেখবে। - একটা বিশাল "universal adapter" বানানো। একটা adapter class যখন পাঁচটা আলাদা library adapt করার চেষ্টা করে, সেটা দানব হয়ে যায়। প্রতিটা adaptee-এর জন্য একটা ছোট adapter লেখো — প্রতিটা সকেটের ধরনের জন্য একটা প্লাগ।
- Adapter-কে Decorator-এর সাথে গুলিয়ে ফেলা। Wrapped object আর wrapper যদি একই method expose করে, তুমি Decorator লিখেছ, Adapter নয়। Adapter অবশ্যই interface বদলাবে।
- অনুবাদের সময় error লুকানো। Adaptee তার পুরনো style-এ error ছুঁড়লে, সেই error-ও অনুবাদ করো। Adapter-এর ভেতরে error গিলে ফেলা bug trace করা অসম্ভব করে দেয়।
আত্মীয়দের সাথে তুলনা
Wrapper পরিবারে চারটা বিখ্যাত আত্মীয় আছে, আর ছাত্ররা সবসময় এদের গুলিয়ে ফেলে। এখানে পারিবারিক album:
| Pattern | Caller যে interface দেখে | মূল উদ্দেশ্য | এক লাইনে মনে রাখার কৌশল |
|---|---|---|---|
| Adapter | Wrapped object-এর থেকে আলাদা | অসামঞ্জস্যপূর্ণ জিনিস মেলানো | "ট্রাভেল প্লাগ" |
| Decorator | Wrapped object-এর মতোই | বাড়তি behaviour যোগ (logging, caching) | "একই পিজ্জায় বাড়তি পনির" |
| Facade | অনেক object-এর উপরে নতুন, সহজ interface | একটা পুরো জটিল subsystem লুকানো | "হোটেলের reception desk" |
| Proxy | Wrapped object-এর মতোই | Access নিয়ন্ত্রণ (lazy load, security, remote) | "গেটের দারোয়ান" |
একটা দ্রুত পরীক্ষা: নিজেকে জিজ্ঞেস করো, "আমাকে কি এমন একটা interface মেলাতে বাধ্য হতে হয়েছে যেটা আগে থেকেই ছিল?" হ্যাঁ হলে Adapter। যদি নিজে থেকে অনেক class-এর জন্য একটা সুন্দর সহজ interface তৈরি করে থাকো, তাহলে Facade। Method একই থেকে বাড়তি কাজ পেলে, Decorator। Method একই থেকে access নিয়ন্ত্রণ হলে, Proxy।
আরেকটা আত্মীয় একটু বলে রাখা দরকার: Bridge। Bridge-ও একটা interface-এর মাধ্যমে দুই পক্ষকে সংযুক্ত করে, কিন্তু Bridge আগেভাগে পরিকল্পনা করে design করার সময়, আর Adapter একটা উদ্ধার কাজ পরে প্রয়োগ করে যখন দুটো তৈরি টুকরো মিলতে অস্বীকার করে। রাহিম adapter পরিকল্পনা করেনি — সে mismatch আবিষ্কার করেছিল আর দোকানে ছুটেছিল। পরিকল্পিত সংস্করণ, Bridge, পরের পোস্টে ঠিকমতো পরিচয় করিয়ে দেওয়া হবে।
কলেজ কর্নার: Adapter pattern একসাথে দুটো SOLID নীতি সম্মান করার প্রতিদিনের হাতিয়ার। এটা Open/Closed Principle সমর্থন করে, কারণ system বিদ্যমান client code পরিবর্তন না করেই একটা নতুন, অসামঞ্জস্যপূর্ণ class-এর সমর্থন পায় — একটা adapter যোগ করে প্রসারিত করো। আর এটা Dependency Inversion Principle সমর্থন করে, কারণ client একটা abstraction (Target interface)-এর উপর নির্ভর করে, concrete third-party class-এর উপর নয়। অধ্যাপক DIP-এর ব্যবহারিক উদাহরণ জিজ্ঞেস করলে, "vendor SDK-এর সামনে একটা adapter" নিখুঁত উত্তর।
দ্রুত revision box
+=====================================================================+
| ADAPTER PATTERN — REVISION CARD |
+=====================================================================+
| Type : Structural pattern |
| Nickname : Wrapper |
| Story : 3-pin plug + 2-pin socket + 40-rupee adapter |
| |
| Players : Client -> calls the Target interface |
| Target -> interface the client expects |
| Adaptee -> old/3rd-party class, wrong interface |
| Adapter -> implements Target, wraps Adaptee |
| |
| Rule : NEVER change the two sides. Only add a middle piece. |
| Best form : Object adapter (hold adaptee as a field) |
| Keep it : Thin! Only translation, no business logic. |
| |
| vs Decorator: same interface + extra behaviour (not Adapter) |
| vs Facade : new simple front for MANY classes (not Adapter) |
| vs Proxy : same interface + access control (not Adapter) |
| vs Bridge : Bridge is planned; Adapter is a rescue. |
+=====================================================================+অনুশীলনের exercise
এখন নিজে হাত দাও! কোনো সমাধান দেখার আগে এই কাজগুলো নিজে চেষ্টা করো।
-
Music player। তোমার app গান চালায়
interface AudioPlayer { playMp3(file: string): void }-এর মাধ্যমে। তোমার বন্ধু রুবেল তোমাকে একটা class দিয়েছেVlcEngineযার method হলোstartVlcPlayback(path: string, format: string)। একটাVlcAdapterলেখো যাতে তোমার app কোনো class না বদলেই.vlcফাইল চালাতে পারে। প্রতিটা layer থেকে একটা message print করো যাতে দেখতে পাও call কীভাবে adapter-এর মধ্য দিয়ে যায়, ঠিক চিত্র ৭-এর মতো। -
Payment gateway। একটা online shop call করে
interface PaymentGateway { pay(amountInTaka: number): string }। ব্যাংকের পুরনো SDK-তে আছেclass LegacyBankApi { makeTransaction(paisa: number): { code: number } }— লক্ষ করো এটা পয়সা চায়, টাকা নয়, আর message-এর বদলে code return করে। একটা adapter লেখো যেটা টাকাকে ১০০ দিয়ে গুণ করে, পুরনো API call করে, আর code0-কে"SUCCESS"আর অন্য যেকোনোটাকে"FAILED"-এ রূপান্তর করে। -
Code করার আগে আঁকো। Exercise ২-এর জন্য আগে কাগজে sequence diagram আঁকো: shop, adapter, bank SDK। চিহ্নিত করো কোথায় টাকা পয়সায় পরিণত হয় আর কোথায় code message হয়। যদি দুটো রূপান্তরই adapter-এর ভেতরে থাকে আর আর কোথাও না, তোমার design সঠিক।
-
Adapter খোঁজো। তুমি যেকোনো project খোলো — নিজের লেখা বা পছন্দের কোনো open-source repo — আর দুটো জায়গা খোঁজো যেখানে একটা class আরেকটা class wrap করে। প্রতিটার জন্য সৎভাবে সিদ্ধান্ত নাও: এটা কি Adapter (interface বদলেছে), Decorator (behaviour যোগ হয়েছে), নাকি Facade (অনেক class সহজ করা হয়েছে)? তোমার উত্তরের প্রমাণে এক বাক্য লেখো।
চারটা শেষ করতে পারলে, তুমি সত্যিই Adapter pattern বুঝেছ। রাহিমের ল্যাপটপ চার্জ হচ্ছে, দাদার দেয়াল অক্ষত আছে, আর চল্লিশ টাকার নায়ক মাঝখানে বসে চুপচাপ কাজ করছে। এখন যাও আর কিছু adapt করো!
সচরাচর জিজ্ঞাসা
- সহজ কথায় Adapter pattern কী?
- Adapter pattern হলো একটা মধ্যবর্তী class, যেটা দুইটা কোডের মাঝখানে বসে কাজ করে — যারা নিজেরা সরাসরি কথা বলতে পারে না। এটা নতুন কোডের পছন্দমতো style-এ call গ্রহণ করে, সেটা রূপান্তর করে, আর পুরনো কোডে পাঠিয়ে দেয়। কোনো পক্ষই বদলায় না — শুধু adapter দুই style জানে।
- Adapter pattern-কে Wrapper-ও কেন বলা হয়?
- কারণ adapter আক্ষরিক অর্থেই পুরনো, অসামঞ্জস্যপূর্ণ object-টাকে নিজের ভেতরে মুড়ে রাখে। বাইরে থেকে caller শুধু নতুন interface দেখে। ভেতরে adapter চুপচাপ মোড়ানো object-এর পুরনো method call করে আর data রূপান্তর করে।
- Object adapter আর class adapter-এর মধ্যে পার্থক্য কী?
- Object adapter পুরনো object-টাকে একটা field হিসেবে ধরে রাখে এবং সেখানে call delegate করে (composition)। Class adapter পুরনো class থেকে inherit করে এবং একই সাথে নতুন interface implement করে (inheritance)। Object adapter বেশি পছন্দের, কারণ এটা বেশি flexible আর সব ভাষায় কাজ করে।
- কখন Adapter pattern এড়িয়ে চলা উচিত?
- এড়িয়ে চলো যখন দুটো class-এর যেকোনো একটা নিজেই বদলাতে পারো, অথবা যখন রূপান্তরটা এত ছোট যে মাত্র একটা জায়গায় এক লাইনেই কাজ হয়। এত ছোট কাজের জন্য আলাদা adapter class বানালে শুধু বাড়তি ফাইল আর জটিলতা তৈরি হয়।
- Adapter আর Decorator কি একই জিনিস?
- না। দুটোই object wrap করে, কিন্তু Decorator একই interface রেখে logging বা caching-এর মতো বাড়তি কাজ যোগ করে। Adapter interface বদলে দেয় যাতে অসামঞ্জস্যপূর্ণ object ব্যবহার করা যায়। একই method কিন্তু বাড়তি কাজ মানে Decorator; আলাদা method মানে Adapter।
আরো দেখো
সম্পর্কিত পাঠ
Bridge Pattern: একটা রিমোট, অনেক ডিভাইস — subclass বিস্ফোরণ থামাও
টিভি আর রিমোটের গল্প দিয়ে Bridge pattern শেখো। একটা বড় class কে দুই ভাগে ভেঙে আলাদাভাবে বাড়াও — বাড়তি subclass-এর ঝামেলা আর নেই।
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 ছোট আর পরিষ্কার থাকে।