মূল বিষয়বস্তুতে যান
Clean Code Mastery

Remove Middle Man: পিয়ন শুধু ফরওয়ার্ড করলে, সরাসরি হেড স্যারের কাছে যাও

Remove Middle Man রিফ্যাক্টরিং শেখো একটা স্কুলের পিয়নের গল্প দিয়ে — যে প্রতিটা প্রশ্ন হেডমাস্টারের কাছে ফরওয়ার্ড করে, নিজে কিছু যোগ না করেই। যখন একটা class শুধু delegate-কে call ফরওয়ার্ড করে, তখন সেই ফরওয়ার্ডিং মুছে দাও আর client-দের সরাসরি delegate-এর সাথে কথা বলতে দাও। TypeScript আর C#-এ ধাপে ধাপে walkthrough।

19 মিনিট আপডেট: June 11, 2026beginner
refactoringsremove-middle-manmiddle-mandelegationmessage-chainscouplingtypescriptcsharp

পিয়ন জামাল ভাই, যে প্রতিটা প্রশ্ন হেড স্যারের কাছে পাঠায়

ধরো তোমার স্কুলে হেডমাস্টার স্যার তারিক সাহেবের রুমের বাইরে বসে আছেন জামাল ভাই, অফিস পিয়ন। তারিক স্যার ভালো মানুষ — দিনের বেশিরভাগ সময় দরজা খোলাই রাখেন।

ফাতেমা নামের একজন ছাত্রী এসে জিজ্ঞেস করলো: "ফি কাউন্টার কয়টায় বন্ধ হয়?" জামাল ভাই জানেন না। উঠে গেলেন, দরজা খুললেন, তারিক স্যারকে হুবহু একই কথায় জিজ্ঞেস করলেন, শুনলেন, ফিরে এসে ফাতেমাকে বললেন: "সাড়ে চারটা।" ফাতেমা আরেকটা জিজ্ঞেস করলো: "কাল কি ছুটি?" আবার জামাল ভাই দরজা খুললেন, একই প্রশ্ন নিয়ে গেলেন, একই উত্তর নিয়ে আসলেন। হুবহু। কিছু যোগ করলেন না। কিছু চেক করলেন না। ফাতেমার দুটো প্রশ্নও এক ট্রিপে নিলেন না।

একদিন ফাতেমা দাঁড়িয়ে দেখছে জামাল ভাই চতুর্থবার একই তিন পা হেঁটে যাচ্ছেন। মাথায় একটা চিন্তা আসলো: দরজাটা তো সামনেই। তারিক স্যার ফ্রি আছেন। আমি কেন একটা human photocopy machine-এর মাধ্যমে কথা বলছি? ফাতেমা ভদ্রভাবে চেয়ার পাশ কাটিয়ে, নক করে, সরাসরি প্রশ্ন করলো, আর অর্ধেক সময়ে উত্তর পেলো। তারিক স্যার হাসলেন — তিনি কখনো gatekeeper চাননি।

চিত্র ১: ফাতেমার প্রশ্ন, mood দিয়ে মাপা। পিয়নের মাধ্যমে: তিনটা ধীর hop। সরাসরি: একটা দ্রুত উত্তর।

এখন পিয়নদের সম্পর্কে সত্যি কথা বলা দরকার — অনেকেই সত্যিকারের কাজ করে। একজন ভালো office assistant visitor ফিল্টার করে, প্রশ্ন batch করে, হেড স্যারের সময় বাঁচায়, নিজেই common বিষয়ে উত্তর দেয়। সেই middle man তার চেয়ারের যোগ্য। সমস্যা শুধু pure forwarding করা জামাল ভাইয়ের মতো: প্রতিটা প্রশ্ন ঢুকলো, একই প্রশ্ন বেরোলো, মাঝখানে কিছু যোগ হলো না।

চিত্র ২: শুধু-ফরওয়ার্ডিং path। প্রতিটা message দুটো hop করে কিন্তু পথে কিছুই পায় না।

Code-এ ঠিক এই character-ই জন্মায়। একটা class সত্যিকারের কাজ করতে করতে শুরু করে। ধীরে ধীরে তার useful অংশ অন্য জায়গায় চলে যায়, অথবা Hide Delegate-এর অতি উৎসাহী ব্যবহারে একটা একটা করে forwarding method জমে। একদিন পড়তে গিয়ে দেখো দশটা method, আর নয়টাই এক লাইনের যেগুলো শুধু একই inner object-কে call করে। class টা হয়ে গেছে একটা free road-এর toll booth।

এর সমাধান হলো Remove Middle Man রিফ্যাক্টরিং: দরজা খোলো, client-দের inner object-এর সাথে সরাসরি কথা বলতে দাও, আর parrot method গুলো মুছে দাও।

Remove Middle Man আসলে কী?

Remove Middle Man হলো Fowler-এর catalog-এর একটা রিফ্যাক্টরিং। রেসিপি: যখন একটা class তার delegate-কে শুধু call ফরওয়ার্ড করা ছাড়া কিছুই করে না, তখন client-দের delegate-এ সরাসরি access দাও, call গুলো redirect করো, আর forwarding method গুলো মুছে দাও।

এটা হলো classic shape। একটা Customer তার Account-এ সব কিছু forward করছে:

class Account {
  balance(): number { /* real work */ return 5230; }
  interestRate(): number { return 0.04; }
  overdraftLimit(): number { return 10000; }
  monthlyStatement(): string { return "..."; }
}
 
class Customer {
  constructor(private account: Account) {}
 
  balance(): number { return this.account.balance(); }
  interestRate(): number { return this.account.interestRate(); }
  overdraftLimit(): number { return this.account.overdraftLimit(); }
  monthlyStatement(): string { return this.account.monthlyStatement(); }
}

চারটা method, চারটাই pure forward, added value শূন্য। যতবার Account নতুন কিছু শেখে, কাউকে Customer-কেও একই জিনিস শেখাতে হয় — চিরকালের জন্য। Remove Middle Man-এর পরে:

class Customer {
  constructor(public readonly account: Account) {}
  // only methods with REAL customer logic remain here
}
 
// Clients reach the account directly:
const limit = customer.account.overdraftLimit();

Customer ব্যাংক account সাজার ভান করা বন্ধ করে। যেসব client-এর account behavior দরকার তারা real account পায়, তার পূর্ণ interface সহ। আর Customer-এ শুধু যা সত্যিকারের customer বিষয়ক তাই থাকে।

চিত্র ৩: cleanup-এর আগে। Customer চারটা Account method mirror করছে আর নিজের মাত্র একটা real method আছে — চার-থেকে-এক parrot ratio।

সংখ্যায় কতটা খারাপ? class টা খুলে method গুলো দুটো bucket-এ ভাগ করো:

চিত্র ৪: Customer class-এর method audit। ফরওয়ার্ডিং slice এভাবে dominant হলে, class টা toll booth।
💡

দ্রুত smell test: class টা খুলে গুনে দেখো। কতগুলো method-এ শুধু return this.something.sameName() আছে, আর কিছু নেই? সেই সংখ্যা যদি class-এর অর্ধেকের বেশি হয়, তুমি Middle Man দেখছো। এক দুটো meaningful forward ঠিক আছে — wholesale parrot করা না।

কলেজ কর্নার: Middle man একবার তৈরি হলে কেন বাড়তেই থাকে? কারণ interface synchronisation cost। একটা pure-forwarding class C যেটা delegate D-কে wrap করে, সেটাকে D-এর প্রতিটা operation mirror করতে হয় — তাই C-এর interface-এর size client-এর D-এর উপর demand দিয়ে নিচের দিক থেকে bounded। D-এর প্রতিটা নতুন feature C-তে matching edit trigger করে: এটা design-এর মধ্যে Shotgun Surgery। Delegation-এর একটা ছোট runtime cost-ও আছে (একটা extra call frame, JIT সাধারণত inline করে দেয়) — কিন্তু যে cost সত্যিই matter করে সেটা cognitive: customer.balance() পড়া প্রতিটা মানুষকে আবিষ্কার করতে হয় যে real logic একটু গভীরে। Indirection শুধু তখনই দাম দেওয়ার যোগ্য যখন এটা কিছু abstract করে। ফরওয়ার্ডিং যেটা কিছু rename করে না আর কিছু decide করে না — সেটা abstraction ছাড়া indirection, যেটা হলো accidental complexity-এর textbook definition।

কখন এটা দরকার?

এই signal গুলো খোঁজো:

  1. Class টা বেশিরভাগ এক লাইনের forward। এটাই textbook Middle Man smell — আর Remove Middle Man হলো তার সরাসরি সমাধান।
  2. রক্ষণাবেক্ষণের treadmill। Delegate-এ প্রতিটা নতুন method server-এ matching method বানাতে বাধ্য করে। তুমি এমন encapsulation-এর জন্য ভাড়া দিচ্ছো যেটা কেউ ব্যবহার করে না।
  3. অতিরিক্ত Hide Delegate। কেউ (হয়তো তোমার আগের সংস্করণ!) Message Chains মেটাতে প্রতিটা hop wrap করেছে, আর wrap করাটা বেশি দূর চলে গেছে।
  4. ফরওয়ার্ডিং আসলে কিছু লুকাচ্ছে না। Server আর delegate-এর মধ্যে সম্পর্ক সবার জানা, stable, আর অবাক করার মতো না।

আর যেগুলোকে একা রেখে দাও:

  • Middle man ইচ্ছাকৃতভাবে আছে। Proxy, Decorator, আর Facade হলো deliberate middle man যাদের কাজ আছে: lazy loading, added behavior, simplified interface। এদের সরালে একটা feature সরে যাবে।
  • শুধু কয়েকটা method forward করে, আর সেগুলো এমন সম্পর্ক লুকাচ্ছে যেটার উপর client-দের depend করা উচিত না।
  • Delegate-এর interface unstable। সেটা expose করলে প্রতিটা client তার পরিবর্তনে couple হয়ে যাবে।
পরিস্থিতিসিদ্ধান্ত
দশের নয়টা method এক লাইনের forwardMiddle man সরাও
প্রতিটা sprint-এ matching parrot method যোগ হচ্ছেMiddle man সরাও
Wrapper validate, combine, বা cache করেরাখো — এটা তার চেয়ার earn করেছে
Wrapper হলো Proxy, Decorator, বা Facadeরাখো — এটা একটা feature
Delegate-এর API প্রতি মাসে বদলায়এখনের জন্য লুকিয়ে রাখো

Signal 2-এর treadmill একটা chart-এ দেখা সহজ। দেখো একটা wrapped class-এ তার delegate-এর তিনটা release জুড়ে কী হয়:

চিত্র ৫: রক্ষণাবেক্ষণের treadmill। প্রতিটা delegate release wrapper-এ matching forwarding method জোর করে, আর parrot count বাড়তে থাকে।

আগে আর পরে এক নজরে

// ---------- BEFORE: Customer is a toll booth ----------
class Customer {
  constructor(private account: Account) {}
 
  balance(): number { return this.account.balance(); }
  interestRate(): number { return this.account.interestRate(); }
  overdraftLimit(): number { return this.account.overdraftLimit(); }
  monthlyStatement(): string { return this.account.monthlyStatement(); }
 
  greetingName(): string { return `Shri ${this.name}`; } // the ONE real method
  private name = "Mehta";
}
 
// Client:
const limit = customer.overdraftLimit();
// ---------- AFTER: door open, parrots deleted ----------
class Customer {
  constructor(public readonly account: Account) {}
 
  greetingName(): string { return `Shri ${this.name}`; } // real value stays
  private name = "Mehta";
}
 
// Client:
const limit = customer.account.overdraftLimit();

Call site-এ একটা extra dot এলো — customer.account. — আর বিনিময়ে চারটা maintenance-only method গেল, এবং এদের কখনো sibling লাগবে না।

চিত্র ৬: আগে, প্রতিটা client call forwarding layer পার হয়। পরে, client-রা সরাসরি Account-এ পৌঁছায় আর Customer শুধু তার real কাজ রাখে।

ধাপে ধাপে, নিরাপদ উপায়ে

ধাপ ১ — diagnosis নিশ্চিত করো। Class-এর method গুলো দুটো column-এ রাখো: "value যোগ করে" আর "pure forward।" শুধু তখনই এগোও যখন pure forward বেশি, আর class টা deliberate Proxy/Decorator না।

ধাপ ২ — delegate expose করো। একটা accessor যোগ করো (বা visibility বাড়াও) যাতে client-রা delegate-এ পৌঁছাতে পারে। আর কিছু বদলাবে না:

class Customer {
  constructor(private _account: Account) {}
 
  get account(): Account {        // new door, nothing removed yet
    return this._account;
  }
 
  balance(): number { return this._account.balance(); } // still here
  // ...other forwards still here
}

Compile করো, test চালাও। সবুজ — কারণ এখনো কিছু সরেনি।

ধাপ ৩ — একটা forwarding method-এর caller redirect করো। balance() বেছে নাও। তার caller খোঁজো। প্রতিটা customer.balance()-কে customer.account.balance()-এ বদলাও, একটা call site একসময়ে, মাঝে মাঝে test করতে করতে।

ধাপ ৪ — empty forward মুছে দাও। যখন balance()-এর কোনো caller নেই, সেটা মুছে দাও। Compile আর test করো। Compiler নিশ্চিত করে কেউ এটা miss করছে না।

ধাপ ৫ — প্রতিটা forwarding method-এর জন্য repeat করো। interestRate(), তারপর overdraftLimit(), তারপর monthlyStatement()। প্রতিটা loop একই: redirect, verify, মুছো।

ধাপ ৬ — যা বেঁচে গেছে সেটা reassess করো। দেখো Customer-এ এখন কী আছে। যদি সত্যিকারের দায়িত্ব এখনো থাকে, কাজ শেষ। যদি এখন প্রায় খালি — একটা Lazy Class — তাহলে Inline Class দিয়ে সেটা সম্পূর্ণ fold করে দেওয়ার কথা ভাবো।

এই surgery-র সময় class টা নিজেই clear state-এর মধ্য দিয়ে যায়, আর প্রতিটা state থামার নিরাপদ জায়গা:

চিত্র ৭: মেরামতের মধ্যে middle man-এর অবস্থাগুলো। প্রতিটা arrow হলো একটা ছোট, tested step; তুমি যেকোনো state-এ থেমে ship করতে পারো।
⚠️

সব forwarding method আগে মুছে পরে caller ঠিক করো না। এতে একটা বিশাল broken build হবে যেখানে প্রতিটা error তোমার মনোযোগের জন্য লড়াই করবে। নিরাপদ rhythm হলো per-method: তার caller redirect করো, test চালাও, তারপর মুছো। প্রতিটা সবুজ test run হলো একটা save point।

একটা বড় real-life উদাহরণ

ধরো একটা food-delivery app-এ DeliveryAgent class আছে। একসময় এটা নিজেই routing math করতো। তারপর একটা Route class extract করা হলো — ভালো কাজ! — কিন্তু DeliveryAgent সবকিছু forward করতেই থাকলো, তাই extraction কখনো শেষ হলো না:

class Route {
  constructor(private stops: string[]) {}
 
  totalDistanceKm(): number { return this.stops.length * 1.8; }
  estimatedMinutes(): number { return this.totalDistanceKm() * 4; }
  nextStop(): string { return this.stops[0]; }
  remainingStops(): number { return this.stops.length; }
}
 
class DeliveryAgent {
  constructor(
    public name: string,
    private route: Route,
    private rating: number
  ) {}
 
  // --- pure forwards: the middle-man layer ---
  totalDistanceKm(): number { return this.route.totalDistanceKm(); }
  estimatedMinutes(): number { return this.route.estimatedMinutes(); }
  nextStop(): string { return this.route.nextStop(); }
  remainingStops(): number { return this.route.remainingStops(); }
 
  // --- the only real agent logic ---
  isTopRated(): boolean { return this.rating >= 4.5; }
}
 
// Clients:
console.log(`${agent.name} reaches in ${agent.estimatedMinutes()} min`);
console.log(`Next stop: ${agent.nextStop()}`);

চারটা forward বনাম একটা real method। প্রতিটা sprint-এ Route নতুন feature পায় (tollCost(), trafficDelay()...), আর প্রতিটা sprint-এ কেউ না কেউ যত্ন করে DeliveryAgent-এ matching parrot যোগ করে। Middle man সরানোর সময় এসেছে:

class DeliveryAgent {
  constructor(
    public name: string,
    public readonly route: Route,   // the door is open
    private rating: number
  ) {}
 
  isTopRated(): boolean { return this.rating >= 4.5; }
}
 
// Clients talk to the object that actually knows:
console.log(`${agent.name} reaches in ${agent.route.estimatedMinutes()} min`);
console.log(`Next stop: ${agent.route.nextStop()}`);

পরের sprint-এ Route যখন tollCost() পাবে, client-রা প্রথম দিনেই agent.route.tollCost() call করতে পারবে। কোনো parrot দরকার নেই। আর DeliveryAgent এখন সত্যিকারভাবে পড়তে যায়: এটা একটা মানুষ যার নাম, rating, আর assigned route আছে — কোনো fake routing engine না।

একটা সূক্ষ্ম বিষয় রাখার মতো। ধরো app টা customer screen-এ "agent কাছাকাছি" দেখায়, আর কাছাকাছি মানে agent-specific কিছু (ধরো, ২-এর কম remaining stop AND top rated)। সেই combination logic হলো agent-এরই:

class DeliveryAgent {
  // ...
  isNearby(): boolean {
    return this.route.remainingStops() < 2 && this.isTopRated();
  }
}

এটা parrot না — এটা delegate data-কে নিজের data-র সাথে combine করে। Remove Middle Man echo মুছে, কখনো brain নয়।

চিত্র ৮: cleanup-এর পরে, app screen সরাসরি Route-কে route তথ্যের জন্য জিজ্ঞেস করে আর DeliveryAgent-কে শুধু genuine agent decision-এর জন্য।

C#-এ একই রিফ্যাক্টরিং

C# version প্রায় একইভাবে পড়া যায়। আগে:

public class Customer
{
    private readonly Account _account;
    public Customer(Account account) => _account = account;
 
    public decimal Balance => _account.Balance;
    public decimal InterestRate => _account.InterestRate;
    public decimal OverdraftLimit => _account.OverdraftLimit;
    public string MonthlyStatement() => _account.MonthlyStatement();
}
 
// Client:
var limit = customer.OverdraftLimit;

পরে — delegate-কে read-only property হিসেবে expose করো আর echo গুলো মুছে দাও:

public class Customer
{
    public Account Account { get; }
    public Customer(Account account) => Account = account;
 
    // only genuinely customer-flavored members remain
}
 
// Client:
var limit = customer.Account.OverdraftLimit;

C#-এর একটা practical tip: একটা একটা করে forwarding member মুছো আর build করো। প্রতিটা compile error হলো একটা caller যেটা customer.Account.X-এ redirect হওয়ার অপেক্ষায়। Build সবুজ হলে সেই member সম্পূর্ণ migrate হয়েছে — পরেরটায় যাও। readonly/get-only exposure দিয়ে তুমি দরজা খোলো কিন্তু কাউকে customer-এর নিচ থেকে account swap করতে দাও না।

Python-এ একটু দেখি

Python-এ middle man প্রায়ই tiny def এক-লাইনারের stack হিসেবে লুকিয়ে থাকে। Cleanup একই: delegate expose করো, echo গুলো মুছো।

class Customer:
    def __init__(self, account):
        self.account = account          # the open door (was _account)
 
    def greeting_name(self):            # real customer logic stays
        return f"Shri {self._name}"
 
# Clients go direct:
limit = customer.account.overdraft_limit()

Python-এর একটা নির্দিষ্ট trap আছে: কিছু middle man __getattr__-এর পিছনে লুকিয়ে থাকে, সবকিছু dynamically forward করে। এটা explicit parrot-এর চেয়েও খারাপ — reader দেখতেই পায় না class টা কী offer করে, আর IDE autocomplete করতে পারে না। যদি এরকম কিছু একটা stable delegate wrap করছে দেখো, সেটা সরালে সাধারণত clarity আর tooling দুটোই একসাথে ভালো হয়।

এই class কি থাকবে নাকি যাবে? Dial পড়ো

দুটো ছবি তোমাকে mood-এর বদলে ঠান্ডা মাথায় decide করতে সাহায্য করবে। প্রথমে suspect class টাকে decision map-এ রাখো:

চিত্র ৯: Middle man-এর দিক থেকে decision map। Cleanup-এর আগে DeliveryAgent remove-the-middle-man quadrant-এ গভীরে।

দ্বিতীয়ত, নিজের class-এ method audit চালাও (চিত্র ৪ style)। ফরওয়ার্ডিং slice যদি অর্ধেকের বেশি হয়, cleanup schedule করো। যদি সামান্য হয়, class টাকে শান্তিতে রাখো — এক দুটো meaningful forward হলো সুস্থ hiding, রোগ নয়।

Seesaw: Remove Middle Man বনাম Hide Delegate

এই রিফ্যাক্টরিং হলো Hide Delegate-এর সরাসরি mirror। মিলে এরা একটা dial তৈরি করে দুই প্রান্তে দুটো failure mode নিয়ে:

  • Client-এ chain ভরা (a.b.c.d সব জায়গায়) — এরা বেশি structure জানে — Hide Delegate দিয়ে dial ঘোরাও।
  • Class-এ pure forwarding ভরা — toll ছাড়া toll booth — Remove Middle Man দিয়ে dial পিছনে ঘোরাও।
চিত্র ১০: একটা dial, দুটো বিপরীত রিফ্যাক্টরিং। প্রতিটা সেই failure mode সারায় যেটা অন্যটা over-apply হলে তৈরি করতে পারে।

কতটুকু hiding সঠিক? Fowler-এর সৎ উত্তর: সময়ের সাথে বদলায়। পাঁচ বছর আগে একটা wrapper volatile relationship রক্ষা করতো — এখন সেই relationship আর নড়েনি, তাই এটা শুধু toll। সময়ে সময়ে পুনর্বিবেচনা করো। Refactoring কোনো one-way street না; এটা steering।

চিত্র ১১: একটা mind map-এ পুরো judgement — কী মুছবে, কী রাখবে, আর কী কখনো ছোঁওয়া যাবে না।

কলেজ কর্নার: "Never touch" list-টার আরো sharper statement দরকার। Proxy, Decorator, আর Facade হলো contract দিয়ে middle man: একটা Proxy access control করে (lazy loading, permissions, remoting), একটা Decorator interface preserve করতে করতে behavior layer করে, একটা Facade subsystem-কে simple front-এ compress করে। এদের forwarding হলো mechanism, smell নয় — added value থাকে কখন আর কীভাবে forward করে তাতে, message বদলানোতে নয়। Code review-এ distinguishing test: জিজ্ঞেস করো layer সরিয়ে দিলে কী ভাঙে। সত্যিকারের middle man-এর জন্য উত্তর: "কিছুই না, caller শুধু এক level গভীরে dot করবে।" Proxy-র জন্য উত্তর হতে পারে: "startup-এ আমরা ১০,০০০ image eagerly load করি।" Layer সরালে যদি কোনো behavior সরে যায়, এটা কখনো middle man ছিল না।

সুবিধা আর ঝুঁকি

সুবিধাকেন গুরুত্বপূর্ণ
Dead-weight method মিলিয়ে যায়কম code পড়তে, test করতে, আর রক্ষণাবেক্ষণ করতে হয়
Treadmill থামেনতুন delegate feature-এ matching server method লাগে না
Client-রা পূর্ণ delegate API পায়কোনো intermediary filtering বা পিছিয়ে পড়া নেই
Server-এর সত্যিকারের কাজ দৃশ্যমান হয়কখনো কখনো দেখা যায় এটা ছোট — আরো cleanup-এর দিকে আমন্ত্রণ
ঝুঁকিকীভাবে handle করবে
Call site-এ message chain ফিরে আসেStable relationship-এর জন্য জেনে-বুঝে মেনে নাও; structure বদলালে আবার hide করো
Delegate public API হয়ে যায়এর পরিবর্তন এখন সরাসরি client-এ লাগে — শুধু stable delegate expose করো
Over-removal meaningful wrapper মুছে দেয়যেসব forward validate, combine, বা genuinely volatile structure লুকায় সেগুলো রাখো
Deliberate middle man ধ্বংস হয়কখনো designed কাজ করছে এমন Proxy, Decorator, বা Facade সরাবে না

কোন smell সারায়?

SmellRemove Middle Man কীভাবে সাহায্য করে
Middle Manসরাসরি সমাধান — pure-forwarding layer মুছে দেয়
Lazy Classপ্রায়ই follow-up: কিছু না থাকা stripped server inline করে দেওয়া হয়
Speculative Generality"just in case" indirection সরায় যেটা কেউ কখনো দরকার পায়নি
Message Chainsএটা সারায় না — Remove Middle Man over-apply করলে তৈরি হয়

IDE support

এই রিফ্যাক্টরিং অস্বাভাবিক ভালো automation পায়:

  • IntelliJ IDEA / JetBrains family: একটা dedicated Remove Middleman রিফ্যাক্টরিং ship করে। Caret delegate field-এ রাখো, invoke করো, আর IDE সব delegating method-এর call সরাসরি delegate-এ replace করে, চাইলে unused forward এক shot-এ মুছে দেয়।
  • Rider / ReSharper (C#): কোনো single-named command নেই, কিন্তু প্রতিটা forwarding member-এ Inline Method একই কাজ করে — প্রতিটা caller underlying _account.X call-এ rewrite করে member সরিয়ে দেয়। Method by method করতে Find Usages সাথে combine করো।
  • Visual Studio: প্রতিটা forwarder-এ built-in Inline Method (Quick Actions, Ctrl+.) ব্যবহার করো, অথবা delete-and-follow-compile-errors করো Find All References map হিসেবে রেখে।
  • VS Code (TypeScript): এখনো file জুড়ে automated inline-method নেই; Find All References-এর উপর নির্ভর করো, call site হাতে edit করো, আর tsc প্রতিটা deleted forward-এর surviving caller নেই তা confirm করুক।

যে tool-ই হোক, per-method rhythm রাখো: একটা forwarder inline বা redirect করো, test চালাও, মুছো, repeat।

Quick revision box

+----------------------------------------------------------------+
|                REMOVE MIDDLE MAN — CHEAT SHEET                 |
+----------------------------------------------------------------+
| Smell to spot : class where most methods just forward          |
| Move          : expose the delegate via an accessor            |
| Then          : redirect callers to delegate, method by method |
| Finally       : delete each forwarding method when unused      |
| Keep          : wrappers that add checks, meaning, or combos   |
| Never touch   : deliberate Proxy / Decorator / Facade          |
| Danger        : over-removal -> Message Chains come back       |
| Opposite move : Hide Delegate (same dial, other end)           |
| Follow-up     : nearly-empty class? consider Inline Class      |
+----------------------------------------------------------------+

Practice exercise

এখানে একটা hostel management class আছে যেটা toll-booth life-এ drift করে গেছে। এটা রিফ্যাক্টর করো।

class MessMenu {
  breakfast(): string { return "Poha and chai"; }
  lunch(): string { return "Dal, rice, sabzi"; }
  dinner(): string { return "Roti, paneer"; }
  isSpecialDay(): boolean { return new Date().getDay() === 0; }
}
 
class Warden {
  constructor(private menu: MessMenu, private name: string) {}
 
  breakfast(): string { return this.menu.breakfast(); }
  lunch(): string { return this.menu.lunch(); }
  dinner(): string { return this.menu.dinner(); }
  isSpecialDay(): boolean { return this.menu.isSpecialDay(); }
 
  noticeBoardMessage(): string {
    return `Warden ${this.name}: lights off at 10 pm.`;
  }
}
 
// Clients:
console.log(warden.breakfast());
console.log(warden.lunch());
console.log(warden.isSpecialDay() ? "Sunday special!" : "Regular day");

তোমার tasks:

  1. প্রতিটা Warden method classify করো: pure forward নাকি real value? নিজে চিত্র ৪-এর মতো pie আঁকো — কত fraction forward করে?
  2. menu নিরাপদে expose করো (read-only), তারপর একটা forwarding method সম্পূর্ণ migrate করো — তার caller redirect করো, test করো, মুছো। বাকিগুলোর জন্য repeat করো, চিত্র ৭-এর প্রতিটা state-এ থামতে থামতে।
  3. Cleanup-এর পরে Warden-এর একটাই real method আছে। Warden কি class হিসেবে রাখার যোগ্য, নাকি inline করে দেওয়া উচিত? এক লাইনে তোমার verdict লেখো।
  4. Twist: hostel এখন sundayFeast() চায় — যেটা dinner menu plus "kheer" return করে শুধু isSpecialDay() true হলে। এই method কোথায় থাকবে: MessMenu-তে, নাকি Warden-এ? Menu call করলেও এটা কেন middle-man method নয়?
  5. Pattern check: ধরো hostel একটা CachedMessMenu যোগ করে যেটা MessMenu wrap করে আর আজকের উত্তরগুলো মনে রাখে যাতে cook-কে একবারই জিজ্ঞেস করা হয়। এটা প্রতিটা method forward করে। তুমি কি এটা সরাবে? College corner-এর what-breaks-if-it-disappears test ব্যবহার করো।

যদি task 4-এর উত্তর দাও "কারণ এটা echo করার বদলে combine আর decide করে," আর task 5-এর উত্তর দাও "না — caching একটা কাজ, এটা একটা Proxy," তাহলে তুমি parrot আর brain-এর পার্থক্য আয়ত্ত করেছো — এটাই এই রিফ্যাক্টরিং-এর পুরো শিল্প। জামাল ভাইয়ের চেয়ার, বলতে গেলে, খালি থাকেনি: তারিক স্যার তাকে একটা notice board, একটা question diary, আর common প্রশ্নের উত্তর নিজে দেওয়ার authority দিলেন। সে হয়ে গেল একটা Facade। এখন কেউ তাকে সরাতে চায় না।

সচরাচর জিজ্ঞাসা

Remove Middle Man রিফ্যাক্টরিং আসলে কী?
Remove Middle Man একটা class থেকে pass-through method গুলো মুছে দেয় যেগুলো শুধু ফরওয়ার্ড করতে করতে জমে গেছে। তুমি একটা accessor যোগ করো যেটা delegate expose করে, প্রতিটা caller-কে সরাসরি delegate ব্যবহার করতে redirect করো, তারপর মরে যাওয়া forwarding method গুলো মুছে দাও। class টা আবার শুধু তার আসল কাজেই ফিরে যায়।
কীভাবে বুঝবো একটা class Middle Man হয়ে গেছে?
এর method গুলো গুনে দেখো। যদি অর্ধেক বা বেশি method এক লাইনের হয় যেগুলো শুধু একই delegate-কে call করে result return করে, আর delegate-এর প্রতিটা নতুন feature আরেকটা forwarding method বানাতে বাধ্য করে — তাহলে class টা toll booth। Fowler-এর সহজ নিয়ম: বেশিরভাগ method যখন delegate করছে, middle man সরিয়ে দাও।
Remove Middle Man কি Hide Delegate-এর উল্টো?
হ্যাঁ, একদম। Hide Delegate forwarding method যোগ করে যাতে client-রা আর chain হাঁটতে না হয়। Remove Middle Man forwarding method মুছে দেয় যখন সেগুলো noise হয়ে জমে যায়। এরা একই dial দুই দিকে ঘোরায়, আর সুস্থ code দুই extreme-এর মাঝখানে থাকে।
Middle man সরালে কি message chain ফিরে আসবে?
আসতে পারে — এটা তুমি জেনে-বুঝে যে trade করছো। delegate expose করলে client-রা আবার server.delegate.method() লিখবে। যখন সম্পর্কটা stable আর client-দের সত্যিই delegate-এর rich interface দরকার, তখন এটা ঠিকই আছে। কিন্তু যদি chain গুলো এমন structure leak করতে থাকে যেটা প্রায়ই বদলায়, তাহলে Hide Delegate দিয়ে dial পিছনে ঘোরাও।
সব forwarding method কি সবসময় সরিয়ে দেওয়া উচিত?
না। Partial removal-ই প্রায়ই সবচেয়ে ভালো: বেশি ব্যবহৃত call গুলোর জন্য delegate expose করো, কিন্তু যে কয়েকটা wrapper সত্যিকারের meaning, check, বা convenience যোগ করে সেগুলো রাখো। আর ইচ্ছাকৃতভাবে যেসব middle man আছে সেগুলো কখনো সরিও না — Proxy আর Decorator pattern হলো deliberate middle man যাদের একটা কাজ আছে।

আরো দেখো

সম্পর্কিত পাঠ

Middle Man: যে helper শুধু তোমার message পৌঁছে দেয়, নিজে কিছু করে না

Middle Man code smell টা বোঝো একটা school-এর সেই পিয়নের গল্প দিয়ে — যে শুধু চিরকুট বহন করে, নিজে কিছু যোগ করে না। যখন একটা class শুধু সব call forward করে, সেটা সরিয়ে দাও। কিন্তু Proxy, Facade, আর Adapter কেন জেনেশুনে middle man হয় — সেটাও জানো।

আরও পড়ুন

Message Chains: বন্ধুকে জিজ্ঞেস করো, সে কাজিনকে জিজ্ঞেস করে, কাজিন চাচাকে জিজ্ঞেস করে

Message Chains code smell শেখো একটা মজার গল্পের মাধ্যমে — রুটি আছে কিনা জানতে চারজন মানুষের মধ্য দিয়ে যেতে হয়। a.getB().getC().getD() লিখলে caller পুরো রাস্তার সাথে coupled হয়ে যায়। Law of Demeter কী, আর Hide Delegate দিয়ে কীভাবে chain ঠিক করতে হয় সেটা শেখো।

আরও পড়ুন

Hide Delegate: মনিটরকে জিজ্ঞেস করো, মনিটর নিজেই দৌড়াবে

Hide Delegate রিফ্যাক্টরিং শেখো একটা মজার গল্পের মাধ্যমে। employee.department.manager-এর মতো chain লেখা বন্ধ করো — প্রথম object-কে একটা সহজ method দাও আর ভেতরের জার্নি লুকিয়ে রাখো। TypeScript আর C#-এ ধাপে ধাপে উদাহরণসহ।

আরও পড়ুন

Inline Class: যে Class কিছুই করে না, তাকে মিলিয়ে দাও

Inline Class refactoring শেখো একটা school committee-র গল্পের মাধ্যমে। যে class কিছুই করে না তাকে তার user-এর সাথে মিলিয়ে দাও আর অকারণ layer মুছে ফেলো।

আরও পড়ুন