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

Extract Interface: একটাই ফর্ম, কিন্তু হাজার রকমের কর্মী

Extract Interface রিফ্যাক্টরিং শেখো — রহিম ইলেকট্রিশিয়ান আর করিম প্লাম্বারের গেটের রেজিস্টারের গল্প দিয়ে। TypeScript আর C#-এ contract বের করা, test double বানানো, আর interface বনাম superclass কখন কোনটা বেছে নেবে সেই decision table।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringextract interfacecontractdecouplingdependency injectiontypescriptcsharp

🚪 ইলেকট্রিশিয়ান, প্লাম্বার, আর গেটের একটাই রেজিস্টার

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

একটু ভাবো এই দুজন কত আলাদা। আলাদা পেশা, আলাদা ট্রেনিং, আলাদা কোম্পানি, আলাদা যন্ত্রপাতি। কেউ বলবে না প্লাম্বার এক ধরনের ইলেকট্রিশিয়ান। আর কোনো সাধারণ "কর্মী পরিবার"ও নেই। যদি কেউ দুজনের কাজ একটা ট্রেনিং ম্যানুয়ালে লিখতে বসে, ফলটা হবে আজগুবি।

তবুও, সিকিউরিটি ডেস্কে সালাম ভাই দুজনকে হুবহু একইভাবে treat করেন। একটা রেজিস্টার খোলেন, দুজনকেই একই কথা বলেন: "নাম? কাজের উদ্দেশ্য? সাইন করো। যাওয়ার সময় সাইন আউট করো।" রেজিস্টারের তার বা পাইপে কোনো আগ্রহ নেই। তার আগ্রহ ঠিক দুটো জিনিসে: sign in করতে পারে, sign out করতে পারে। এই দুটো ক্ষমতা যার আছে — ইলেকট্রিশিয়ান, প্লাম্বার, কুরিয়ার বয়, স্কুল ইন্সপেক্টর — সবাই রেজিস্টারে fit।

আসলে রেজিস্টারটা কী? এটা কোনো পরিবার না যেখানে কর্মীরা পড়ে। এটা একটা ফর্ম — একটা ছোট তালিকা যেখানে লেখা প্রতিটা ভিজিটরকে কী কী করতে পারতে হবে। রহিম নিজের মতো পূরণ করে ("রহিম, ইলেকট্রিক্যাল মেরামত, ৯:৪০"), করিম তার মতো ("করিম, প্লাম্বিং, ১১:১৫")। ফর্মটা কী হবে ঠিক করে, কীভাবে হবে সেটা না।

চিত্র ১: রেজিস্টারের যেকোনো ভিজিটরের কাছে শুধু দুটো ক্ষমতা লাগে — sign in আর sign out

প্রোগ্রামিংয়ে এই ধারণাটার নাম interface: ক্ষমতার তালিকা সহ একটা চুক্তি, কিন্তু ভেতরে কোনো code নেই। আর যে refactoring এই চুক্তিটা আগে থেকে থাকা class-এ আবিষ্কার করে তার নাম Extract Interface — বিভিন্ন client বা বিভিন্ন class যে method-এর slice শেয়ার করছে সেটা বের করে একটা নাম দাও।

📋 Extract Interface কী জিনিস?

Interface হল এমন একটা type যেটা শুধু method signature declare করে — নাম, parameter, return type — এক লাইনও implementation নেই। যে class এটা implement করে সে compiler-কে প্রতিশ্রুতি দেয়: "এই তালিকার প্রতিটা method-এর জন্য আমার কাছে real body আছে।"

Extract Interface হল সেই refactoring যেখানে শুরু থেকে contract design না করে, তুমি বিদ্যমান code-এ সেটা আবিষ্কার করো:

  1. একটা class (বা বেশ কয়েকটা) দেখো — caller আসলে কোন method-এর cohesive slice use করে সেটা খোঁজো।
  2. শুধু সেই signature গুলো নিয়ে একটা interface declare করো।
  3. Class-টাকে সেটা implement করার চিহ্ন দাও। Compiler মিলিয়ে দেখে; সাধারণত কোনো method-ই পরিবর্তন হয় না।
  4. Client-দের concrete class-এর বদলে interface type-এর উপর নির্ভর করতে পরিবর্তন করো।

দুটো classic পরিস্থিতিতে এটা দরকার হয়। প্রথমত, সম্পর্কহীন class, একই operation: Electrician আর Plumber দুটোতেই signIn() আর signOut() আছে, আর গেটের রেজিস্টার যেকোনোটা handle করতে চায় কোনটা সেটা না জেনেই। দ্বিতীয়ত, client একটা বড় class-এর মাত্র একটা slice use করছে: একটা service শুধু save() আর load() call করে এমন একটা store-এ যেখানে আরো দশটা maintenance method আছে — ReportStore extract করলে dependency সংকুচিত হয়।

Extract Superclass-এর সাথে মূল পার্থক্যটা বোঝো: superclass আসল code শেয়ার করে — field আর method body যেটা children inherit করে। Interface শেয়ার করে শুধু একটা প্রতিশ্রুতি। কিছু reuse হয় না, সব কিছু guarantee হয়। সেজন্যই ভাষার নিয়মগুলো আলাদা: C#, Java, আর TypeScript-এ একটা class একটা base class extend করতে পারে কিন্তু অনেকগুলো interface implement করতে পারে।

সালাম ভাইয়ের পরিস্থিতিতে পছন্দের logic এভাবে এগোয়:

চিত্র ২: contract নাকি code? সিদ্ধান্তের প্রশ্ন — real method body কি duplicate হয়েছে?

রহিম আর করিম শূন্য method body শেয়ার করে — ইলেকট্রিশিয়ান তার licence number দিয়ে sign in করে, প্লাম্বার contractor card দিয়ে। শুধু role টা এক। সেই পথ সোজা interface-এ যায়।

💡

এক লাইনে সারমর্ম: Extract Interface বেশ কয়েকটা type যে role পালন করে তার নাম দেয় — code ছাড়া signature-এর একটা contract — যাতে caller role-এর উপর নির্ভর করতে পারে আর যে কোনো কর্মী ফর্ম পূরণ করতে পারলে তাকে use, swap, বা fake করা যায়।

🔔 কখন দরকার হয়?

এই মুহূর্তগুলোতে নজর রাখো:

  • সম্পর্কহীন class, একই operation। Electrician আর Plumber একটা parent শেয়ার করতে পারে না। জোর করে common superclass দিলে তাদের উপর inherited member চাপিয়ে দেওয়া হবে যেটা তারা reject করবে — Refused Bequest smell তৈরি হবে। Interface তাদের একটা contract দেয় জোর করে কোনো পারিবারিক সম্পর্ক ছাড়াই।
  • Real জিনিস ছাড়া test করা যাচ্ছে না। ReportService যদি concrete FileReportStore ধরে রাখে, প্রতিটা unit test disk ছুঁয়ে যাবে। IReportStore extract করো, আর test-গুলো in-memory fake pass করতে পারবে।
  • Caller মাত্র একটা slice use করছে। যখন Large Class-এর client-রা ত্রিশটার মধ্যে মাত্র দুই-তিনটা করে method call করছে, per-role interface extract করলে প্রতিটা dependency ছোট আর documented হয়।
  • Implementation swap করতে হবে। আজকে file, কাল database, পরের বছর cloud। Interface-এর বিপরীতে লেখা client কখনো পরিবর্তন হয় না।
  • Type-checking ladder। if (worker instanceof Electrician) ... else if (worker instanceof Plumber) ... প্রতিটা call site-এ বারবার লেখা — এটা decision-making-এর duplication, Duplicate Code-এর এক ধরনের আত্মীয়। একটা interface আর polymorphic call সেই ladder মুছে দেয়।

এক মাসে সালাম ভাইয়ের গেটে কত রকমের লোক আসে? অনেক — কিন্তু রেজিস্টার সব কিছু একটাই ফর্মে handle করে:

চিত্র ৩: এই মাসে গেটের ভিজিটর — চারটা সম্পর্কহীন পেশা, একটাই shared sign-in role

সেই pie-এর কোনো দুটো slice একই "পরিবার"-এর না, আর রেজিস্টারের সেটার দরকারও ছিল না কখনো। এটাই একটা contract-এর শক্তি: এটা সম্পর্কহীন type-এর মধ্যে scale করে, যেখানে inheritance একেবারেই যেতে পারে না।

সতর্কতাগুলো মনে রাখো: interface কোনো duplicate body সরায় না (সেটা superclass-এর কাজ); test বা decoupling ছাড়া এক-implementer interface হল Speculative Generality smell; আর পুরো class mirror করা interface শুধু একটা rename, role না।

এই সিদ্ধান্তটা সেই একই quadrant-এ যেটা আমরা Extract Superclass পোস্টে use করেছিলাম — কিন্তু এবার দেখো গেটের case কোথায় পড়ে:

চিত্র ৪: গেটের case বাম-নিচে পড়ে — সম্পর্কহীন class শুধু contract শেয়ার করছে — পরিষ্কারভাবে interface এলাকা

কলেজ কর্নার — একসাথে দুটো SOLID অক্ষর: Extract Interface একসাথে দুটো principle-এর কাজের অংশ। Interface Segregation Principle (ISP) বলে client-দের কখনো এমন method-এর উপর নির্ভর করতে বাধ্য করা উচিত না যেটা তারা call করে না — সেজন্যই আমরা পুরো class না বের করে slice বের করি। Dependency Inversion Principle (DIP) বলে high-level policy (রেজিস্টার) low-level detail-এর (ইলেকট্রিশিয়ানের যন্ত্রপাতির বাক্স) উপর নির্ভর করা উচিত না; দুটোরই একটা abstraction-এর উপর নির্ভর করা উচিত (ভিজিটর ফর্ম)। যখন তোমার architecture textbook একটা module boundary-তে arrow flip করে দেখায়, Extract Interface হল সেই refactoring যেটা physically সেই flip করে।

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

TypeScript-এ সিকিউরিটি ডেস্ক। আগে — রেজিস্টার প্রতিটা trade ব্যক্তিগতভাবে চেনে:

// BEFORE: the register depends on each concrete worker type
class Electrician {
  constructor(public name: string) {}
  signIn(time: string): string { return `${this.name} (electrician) in at ${time}`; }
  signOut(time: string): string { return `${this.name} out at ${time}`; }
  testCircuit(): string { return "Circuit tested"; }       // trade-specific
}
 
class Plumber {
  constructor(public name: string) {}
  signIn(time: string): string { return `${this.name} (plumber) in at ${time}`; }
  signOut(time: string): string { return `${this.name} out at ${time}`; }
  fixLeak(): string { return "Leak fixed"; }               // trade-specific
}
 
class GateRegister {
  logEntry(worker: Electrician | Plumber, time: string): void {
    console.log(worker.signIn(time));   // a union that grows with every new trade!
  }
}

Extract Interface-এর পরে — রেজিস্টার শুধু ফর্মের উপর নির্ভর করে:

// AFTER: one contract; the register never learns trades
interface Visitor {
  signIn(time: string): string;
  signOut(time: string): string;
}
 
class Electrician implements Visitor {
  constructor(public name: string) {}
  signIn(time: string): string { return `${this.name} (electrician) in at ${time}`; }
  signOut(time: string): string { return `${this.name} out at ${time}`; }
  testCircuit(): string { return "Circuit tested"; }
}
 
class Plumber implements Visitor {
  constructor(public name: string) {}
  signIn(time: string): string { return `${this.name} (plumber) in at ${time}`; }
  signOut(time: string): string { return `${this.name} out at ${time}`; }
  fixLeak(): string { return "Leak fixed"; }
}
 
class GateRegister {
  logEntry(visitor: Visitor, time: string): void {
    console.log(visitor.signIn(time));  // works for any future trade, unchanged
  }
}
চিত্র ৫: Extract Interface — Electrician আর Plumber সম্পূর্ণ সম্পর্কহীন class-ই থাকে, কিন্তু দুটোই সেই Visitor contract-এ সাইন করে যেটার উপর রেজিস্টার নির্ভর করে

diagram-এ arrow-এর style-টা লক্ষ্য করো: dashed arrow (<|..) মানে "contract implement করছে", solid arrow হতো "code inherit করছে"। Electrician আর Plumber একে অপরের কাছে অপরিচিতই থাকে — শুধু ফর্মটাই তাদের যুক্ত করে। পরের মাসে যখন CourierBoy class আসবে, সে Visitor implement করবে আর GateRegister-এ একটা character-ও পরিবর্তন হবে না।

শেষ বাক্যটাই হল পরিমাপযোগ্য জয়। প্রতিবার বিল্ডিংয়ে নতুন ধরনের ভিজিটর আসলে GateRegister-এর কতটুকু line ছুঁতে হয় গুনে দেখো:

চিত্র ৬: প্রতিটা নতুন visitor type-এর জন্য GateRegister-এর edited line — union ladder চিরকাল বাড়তে থাকে; interface কখনো বাড়ে না

Union type-এর সাথে, প্রতিটা নতুন trade মানে union edit করা, প্রায়ই instanceof ladder, আর পুরনো union mention করা প্রতিটা test — গড়ে প্রায় ছয়টা line। Interface-এর সাথে: শূন্য। এটাই Open-Closed Principle সালাম ভাইয়ের রেজিস্টারে ধরা পড়ছে: নতুন visitor-এর জন্য open, modification-এর জন্য closed।

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

রিফ্যাক্টরিংটা compile-green state-এ এগোয়। শুরুর ধাপগুলো অবিশ্বাস্যভাবে কম ঝুঁকির:

চিত্র ৭: concrete coupling থেকে swappable contract পর্যন্ত — declare করা আর implement করা কোনো behaviour পরিবর্তন করে না
  1. Role খোঁজো। Caller সত্যিই কোন method invoke করে সেটা list করো। রেজিস্টারের জন্য এটা signIn আর signOuttestCircuit না, fixLeak না। Role হল client-দের যা দরকার তার intersection, class কী offer করে তার union না।

  2. Interface declare করো। শুধু signature। একটা role-name বেছে নাও — Visitor, ReportStore, Notifier এরকম। Implementation নাম না। C# convention-এ I prefix দেয় (IVisitor); TypeScript আর Java সাধারণত দেয় না।

  3. Class-কে সেটা implement করাও। implements Visitor যোগ করো। Compiler প্রতিটা signature মিলিয়ে দেখে। Class body পরিবর্তন হয় না — এটাই সব refactoring-এর মধ্যে সবচেয়ে নিরাপদ ধাপ।

// INTERMEDIATE STEP: contract declared, clients not yet migrated
interface Visitor {
  signIn(time: string): string;
  signOut(time: string): string;
}
 
class Electrician implements Visitor { /* unchanged body */ }
 
class GateRegister {
  logEntry(worker: Electrician | Plumber, time: string): void { /* still the old union */ }
}
  1. Client-দের একটা একটা করে migrate করো। প্রতিটা parameter, field, আর return type concrete class থেকে interface-এ পরিবর্তন করো। প্রতিটা site-এর পরে compile আর test করো। প্রতিটা migration independently নিরাপদ।

  2. দ্বিতীয় implementer যোগ করো। Plumber আর যেকোনো future class-কেও interface implement করাও, তারপর যে union type আর instanceof ladder সরানো যায় সেগুলো delete করো।

  3. Contract মোটা করার প্রলোভন এড়াও। যখন কেউ জিজ্ঞেস করে "Visitor-এর কি toolboxWeight() ও থাকা উচিত না?", চেক করো: রেজিস্টার client-দের কি এটা দরকার? শুধু একটা trade-এর থাকলে ফর্মের বাইরেই থাকে।

⚠️

Slice extract করো, পুরো class না। যদি প্রতিটা public method interface-এ tick করো, তুমি এমন একটা fat contract বানিয়েছ যেটা future implementer-কে পুরোপুরি satisfy করতে হবে — test-এর একটা fake-এর ত্রিশটা dummy method লাগবে। Interface-কে role যতটুকু সেটুকুই রাখো; দরকারে দ্বিতীয় role-এর জন্য আরেকটা interface extract করো।

🧮 একটা বড় বাস্তব উদাহরণ

ধরো স্কুলের notice board software নোটিস file-এ রাখে। Service class ডিস্কের সাথে জুড়ে আছে, তাই প্রতিটা test real file লেখে:

// BEFORE: welded to the file system
class FileNoticeStore {
  save(notice: { id: number; text: string }): void { /* write to disk */ }
  load(id: number): string { /* read from disk */ return "..."; }
  compactFolder(): void { /* disk-only maintenance */ }
}
 
class NoticeService {
  private store = new FileNoticeStore();          // cannot swap, cannot fake
 
  publish(id: number, text: string): void {
    this.store.save({ id, text: text.trim() });
  }
}

NoticeService শুধু save আর load use করে — কখনো compactFolder না। ঠিক সেই slice extract করো:

// AFTER: the service depends on the role, not the disk
interface NoticeStore {
  save(notice: { id: number; text: string }): void;
  load(id: number): string;
}
 
class FileNoticeStore implements NoticeStore {
  save(notice: { id: number; text: string }): void { /* write to disk */ }
  load(id: number): string { return "..."; }
  compactFolder(): void { /* still exists — just not part of the contract */ }
}
 
class NoticeService {
  constructor(private store: NoticeStore) {}      // injected; any implementer fits
 
  publish(id: number, text: string): void {
    this.store.save({ id, text: text.trim() });
  }
}
 
// In tests: a fake implementer — the second "worker" signing the same form
class InMemoryNoticeStore implements NoticeStore {
  private notices = new Map<number, string>();
  save(n: { id: number; text: string }): void { this.notices.set(n.id, n.text); }
  load(id: number): string { return this.notices.get(id) ?? ""; }
}

এখন একটা unit test new NoticeService(new InMemoryNoticeStore()) দিয়ে তৈরি হয় আর microsecond-এ চলে — কোনো disk ছাড়াই। পরের বছর, CloudNoticeStore একই contract implement করবে আর NoticeService অপরিবর্তিত থাকবে। এই জুটি — interface extract করো, তারপর dependency inject করো — এটাই testable design-এর রোজকার backbone।

চিত্র ৮: NoticeService শুধু NoticeStore contract-এর উপর নির্ভর করে; disk, memory, আর cloud implementation স্বাধীনভাবে interchange করা যায়

দেখো সালাম ভাইয়ের কর্মজীবন একটা যাত্রা হিসেবে — কারণ রেজিস্টারের গল্প আর notice-store-এর গল্প আসলে একই গল্প:

চিত্র ৯: গেটে নতুন trade আসে — per-trade রেজিস্টারে বিশৃঙ্খলা, একটা visitor form-এ শান্তি

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

C#-এ গেটের রেজিস্টার, I-prefix convention সহ। আর দেখো একটা class একসাথে দুটো form sign করছে:

public interface IVisitor
{
    string SignIn(TimeOnly time);
    string SignOut(TimeOnly time);
}
 
public interface ISafetyTrained          // a second, separate role
{
    string ShowSafetyCard();
}
 
public class Electrician : IVisitor, ISafetyTrained   // many interfaces — fine!
{
    public string Name { get; }
    public Electrician(string name) => Name = name;
 
    public string SignIn(TimeOnly time)  => $"{Name} (electrician) in at {time}";
    public string SignOut(TimeOnly time) => $"{Name} out at {time}";
    public string ShowSafetyCard()       => "High-voltage safety card, valid 2027";
    public string TestCircuit()          => "Circuit tested";
}
 
public class Plumber : IVisitor
{
    public string Name { get; }
    public Plumber(string name) => Name = name;
 
    public string SignIn(TimeOnly time)  => $"{Name} (plumber) in at {time}";
    public string SignOut(TimeOnly time) => $"{Name} out at {time}";
}
 
public class GateRegister
{
    public void LogEntry(IVisitor visitor, TimeOnly time)
        => Console.WriteLine(visitor.SignIn(time));
}

Electrician দুটো interface implement করছে আবার যেকোনো দিন একটা base class extend করার স্বাধীনতাও রাখছে — class Electrician : Contractor, IVisitor, ISafetyTrained legal, single base class প্রথমে listed। এটাই interface-এর structural সস্তাতা: প্রতিটা নতুন role inheritance budget থেকে কিছুই নেয় না।

কলেজ কর্নার — default method আর তারা কী পারে না: আধুনিক C# (8+) default interface member allow করে আর Java (8+) default method allow করে — interface-এর ভেতরে method body। এটা কি abstract class-এর সাথে পার্থক্য ঝাপসা করে? যতটা মনে হয় তার চেয়ে কম। কোনো ভাষাই interface-এ instance field allow করে না, তাই interface এখনো state রাখতে পারে না — shared state সবসময় abstract class দরকার। Default method মূলত interface evolution-এর জন্য: যে একশো class ইতোমধ্যে implement করছে সেগুলো না ভেঙে published interface-এ নতুন method যোগ করা। এই কাজে use করো, sneaky multiple-inheritance trick হিসেবে না।

🐍 Python-এ একই ধারণা — Protocol

Python-এর twist টা মজার: typing.Protocol দিয়ে class-দের এমনকি ঘোষণা করতে হয় না যে তারা contract implement করছে। Method-গুলো মিললেই type checker accept করে — structural typing, যাকে সিটবেল্ট সহ duck typing-ও বলা যায়:

from typing import Protocol
 
 
class Visitor(Protocol):
    def sign_in(self, time: str) -> str: ...
    def sign_out(self, time: str) -> str: ...
 
 
class Electrician:                      # note: no explicit "implements"!
    def __init__(self, name: str) -> None:
        self.name = name
 
    def sign_in(self, time: str) -> str:
        return f"{self.name} (electrician) in at {time}"
 
    def sign_out(self, time: str) -> str:
        return f"{self.name} out at {time}"
 
 
class GateRegister:
    def log_entry(self, visitor: Visitor, time: str) -> None:
        print(visitor.sign_in(time))    # mypy verifies the shape matches

TypeScript গোপনে একইভাবে কাজ করে — এর type system structural, তাই matching signIn/signOut method সহ একটা class Visitor-এ assignable implements clause ছাড়াও। তবে clause রাখাটা এখনও ভালো অভ্যাস: এটা intent দৃশ্যমান করে আর signature drift-কে call site-এর বদলে class-এই error করে। C# আর Java nominal: class-কে explicitly interface-এর নাম দিতে হবে। তোমার ভাষা কোন দলে সেটা জানা একটা favourite interview প্রশ্ন।

🛠️ IDE সাপোর্ট

Extract Interface সব জায়গায় সবচেয়ে ভালো automated refactoring-গুলোর একটা:

  • Visual Studio (C#): class name-এ cursor রাখো, Ctrl+. চাপো আর Extract interface... বেছে নাও (Edit → Refactor-এর নিচেও আছে)। Dialog interface-এর নাম, destination file, আর কোন public member include করবে সেটার checkbox দেখায়; OK করলে automatically interface তৈরি হয় আর class-এর base list-এ যোগ হয়।
  • ReSharper / JetBrains Rider (C#): Refactor This → Extract Interface member বাছার dialog খোলে; class আগে থেকেই অন্য interface implement করলে সেগুলো নতুনটায় fold করা যায়, আর Rider নিরাপদ হলে class-এর usage interface-এ update করতে পারে।
  • IntelliJ IDEA (Java) / WebStorm (TypeScript): Refactor → Extract → Interface... member tick করতে দেয় আর "Replace class references with interface where possible" offer করে — tool পুরো project-এ আমাদের ধাপ ৪ এক shot-এ করে।

একটা জিনিস কোনো tool তোমার হয়ে decide করতে পারবে না: কোন member-গুলো মিলে role তৈরি করে। Dialog-গুলো আনন্দের সাথে সব tick করতে দেয়; একজন thoughtful মানুষ সেই ছোট cohesive slice tick করে যেটা client সত্যিই use করে।

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

প্রতিটা student-এর মুখস্থ রাখা উচিত এই decision table — superclass কী শেয়ার করে বনাম interface কী শেয়ার করে:

প্রশ্নExtract InterfaceExtract Superclass
কী শেয়ার হয়?শুধু একটা প্রতিশ্রুতি — signature, কোনো body না (contract)আসল কোড — field, method body, constructor (implementation)
Duplicate body সরায়?না — প্রতিটা implementer নিজের লেখেহ্যাঁ — একটা copy parent-এ চলে যায়
প্রতি class কতটা (C#/Java/TS)?অনেক interface স্বাধীনভাবেমাত্র একটা base class
কোন সম্পর্ক assert করে"can-do" — type একই role পালন করে"is-a" — children একই ধরনের জিনিস
সম্পর্কহীন class-এর জন্য কাজ করে?হ্যাঁ — রহিম আর করিম অপরিচিতই থাকেনা — এমন পরিবার force করে যেটা নাও থাকতে পারে
State (field) রাখতে পারে?না — default method দিয়েও নাহ্যাঁ — private machinery সহ
গল্পের versionযেকোনো visitor পূরণ করতে পারে এমন sign-in formউভয় lab অনুসরণ করে এমন একটা Lab Rulebook
কখন সেরাSwapping, mocking, decoupling, multiple roleSibling সত্যিই কীভাবে কাজ করে তা duplicate করলে
খরচশূন্য structural খরচ; শূন্য code reuseSingle inheritance slot খরচ হয়

আর interface extract করার সামগ্রিক trade-off:

দিকসুবিধাঝুঁকি / খরচ
CouplingClient concrete class-এর বদলে role-এর উপর নির্ভর করেআরেকটা type নামকরণ, file করা, আর maintain করতে হয়
TestingFake আর mock trivially contract implement করেFat interface প্রতিটা fake-কে কষ্টের করে
SubstitutionFile / memory / cloud implementation স্বাধীনভাবে swap হয়Single-implementer "just in case" interface clutter
DuplicationCall site-এ instanceof/union ladder delete করেDuplicate method body সরায় না
Inheritance budgetকিছুই নেয় না; base class-এর সাথে stack হয়

Extract Superclass পোস্টের composition trick মনে রেখো: দুটো refactoring প্রতিদ্বন্দ্বী না, সহযোগী। Shared machinery base class-এ যায়; client যে slim contract দেখে সেটা interface-এ যায়; একটা class দুটোই রাখতে পারে — class ScienceLab extends Lab implements IBookableRoom

🧪 এটা কোন smell সারায়?

SmellExtract Interface কীভাবে সাহায্য করে
Alternative Classes with Different Interfacesদেখতে একরকম class-গুলোকে একটা agreed contract দেয়, তাই caller প্রতিটাকে আলাদা treat করা বন্ধ করে
Duplicate CodeCall site-এ বারবার type-checking ladder সরায় (duplicate body না — সেটার জন্য superclass)
Large ClassPer-role interface একটা বড় class-এর surface ছোট documented slice-এ কেটে দেয় যেটার উপর client নির্ভর করতে পারে
Refused Bequestএটা রোধ করে — সম্পর্কহীন type একটা role শেয়ার করে এমন member inherit না করেই যেটা তাদের refuse করতে হতো

🧠 পুরো ধারণা একটা map-এ

চিত্র ১০: Extract Interface revision map — form, slice, আর এর পেছনের দুটো SOLID principle

📦 দ্রুত revision বাক্স

+--------------------------------------------------------------+
|                     EXTRACT INTERFACE                        |
+--------------------------------------------------------------+
| Problem : Unrelated classes share a ROLE (same operations), |
|           or clients use only a slice of a big class,        |
|           or you cannot swap/fake a dependency.              |
| Story   : Electrician & plumber - totally different          |
|           workers, but the gate register needs only          |
|           "sign in, sign out" -> one common FORM             |
|           (contract), NOT a common family.                   |
| Fix     : 1. Find the cohesive slice clients really use      |
|           2. Declare interface (signatures only)             |
|           3. Class implements it (body unchanged)            |
|           4. Migrate clients to the interface type           |
|           5. Second implementer signs the same form          |
| Shares  : CONTRACT (promise only) - vs superclass,           |
|           which shares IMPLEMENTATION (real code).           |
| Budget  : Many interfaces per class; only ONE base class.    |
| Beware  : Fat interfaces; one-implementer "just in case".    |
+--------------------------------------------------------------+

✍️ Practice exercise

ধরো একটা হাসপাতাল app-এ এই দুটো সম্পর্কহীন class আছে:

class AmbulanceDriver {
  constructor(public name: string) {}
  startShift(time: string): string { return `${this.name} on duty at ${time}`; }
  endShift(time: string): string { return `${this.name} off duty at ${time}`; }
  refuelVehicle(): string { return "Ambulance refuelled"; }
}
 
class WardNurse {
  constructor(public name: string) {}
  startShift(time: string): string { return `Nurse ${this.name} on duty at ${time}`; }
  endShift(time: string): string { return `Nurse ${this.name} off duty at ${time}`; }
  checkMedicineStock(): string { return "Stock checked"; }
}
 
class DutyRoster {
  markPresent(staff: AmbulanceDriver | WardNurse, time: string): void {
    console.log(staff.startShift(time));
  }
}

তোমার কাজ:

  1. DutyRoster আসলে কোন role-এর উপর নির্ভর করছে সেটা চিহ্নিত করো। Contract-এ কোন দুটো method থাকবে? কোন দুটো method বাইরে থাকতে হবে, আর কেন? (Hint: slice-এর warning callout আবার পড়ো।)
  2. একটা OnDutyStaff interface extract করো আর DutyRoster-কে union type থেকে সরিয়ে নাও, নিরাপদ ধাপ অনুসরণ করে — Figure 7-এর state diagram-এর বিপরীতে নিজেকে চেক করো।
  3. একটা ছোট FakeStaff লেখো interface implement করে, যাতে unit test real driver বা nurse ছাড়াই DutyRoster verify করতে পারে।
  4. সিদ্ধান্তের প্রশ্ন: একজন teammate suggest করছে "বরং HospitalStaff-কে abstract base class বানাও, কারণ দুটোতেই startShift/endShift আছে।" দুই method-এর body আলাদা (nurse version-এ "Nurse " যোগ আছে)। একটা superclass কি এখানে কোনো real duplication সরাবে? Figure 4-এর quadrant-এ case-টা রাখো, তারপর দুই বাক্যে interface বা superclass বেছে নেওয়ার পক্ষে যুক্তি দাও।
  5. College-level bonus: হাসপাতাল চায় DutyRoster security guard-দেরও mark করুক — কিন্তু guard class আসে third-party package থেকে যেটা তুমি edit করতে পারবে না, আর সেখানে ইতোমধ্যে matching startShift/endShift method আছে। বুঝিয়ে বলো কেন TypeScript বা Python-এ এটা trivial (structural typing / Protocol) আর nominal language যেমন C#-এ কী extra ধাপ লাগবে (hint: guard-এর পক্ষে form-এ sign করা একটা small adapter class)।

যদি DutyRoster কোনো union type, কোনো instanceof ছাড়া compile করে, আর তোমার fake test pass করে — তোমার গেটের ফর্ম কাজ করছে। আর সালাম ভাই আগামীকাল যেকোনো নতুন কর্মীকে ঢুকতে দেবেন একটাও line না বদলিয়ে।

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

interface আসলে কী শেয়ার করে, যদি কোড শেয়ার না করে?
শুধু একটা প্রতিশ্রুতি — method signature-এর একটা তালিকা যেটা প্রতিটা implementer পূরণ করবে। কোনো field নেই, কোনো method body নেই। রহিম ইলেকট্রিশিয়ান আর করিম প্লাম্বার দুজনেই নিজেদের মতো করে সাইন করে; FORM শুধু গ্যারান্টি দেয় যে দুজনেই sign in আর sign out করতে পারবে। শেয়ার করা কোড দরকার হলে Extract Superclass লাগবে।
একটা class কেন অনেক interface implement করতে পারে কিন্তু একটাই class extend করতে পারে?
কারণ interface-এ কোনো state বা implementation নেই, তাই একটা class একসাথে অনেকগুলো নিলেও কোনো ঝামেলা হয় না — এগুলো শুধু স্তুপ করা প্রতিশ্রুতি। কিন্তু base class-এ field আর body থাকে, দুটো parent থেকে সেগুলো inherit করলে conflict হয়, তাই C#, Java, আর TypeScript মাত্র একটা base class allow করে।
কখন superclass-এর বদলে interface extract করব?
যখন class-গুলো শুধু একটা role শেয়ার করে, কোড না — যেমন রহিম ইলেকট্রিশিয়ান আর করিম প্লাম্বার সম্পূর্ণ আলাদা কর্মী কিন্তু দুজনেই sign in আর sign out করে। এছাড়া যখন class-গুলো আগে থেকেই অন্য কিছু extend করছে, যখন swappable implementation দরকার, বা যখন test-এর জন্য fake দরকার। যদি method body duplicate হয়, interface কাজে আসবে না — superclass extract করো।
মাত্র একটা implementing class সহ interface বানানো কি খারাপ?
বেশিরভাগ ক্ষেত্রে হ্যাঁ — এটা Speculative Generality smell, মানে 'যদি পরে লাগে' মনে করে বানানো interface। কিন্তু দুটো ব্যতিক্রম আছে — testing আর decoupling: test-এর fake implementation যদি দ্বিতীয় implementer হয়, বা module boundary-তে dependency invert করতে হলে, interface তার জায়গা বুঝিয়ে নিচ্ছে।
interface কি class-এর প্রতিটা public method কপি করা উচিত?
না। শুধু সেই cohesive slice extract করো যেটা client সত্যিই use করে — role-টাকে। পুরো class mirror করা fat interface শুধু সেটার নাম পরিবর্তন করে, কোনো role বোঝায় না। ছোট focused interface Interface Segregation Principle মেনে চলে: client-দের এমন method-এর উপর নির্ভর করতে বাধ্য করা উচিত না যেগুলো তারা কখনো call করে না।

আরো দেখো

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

Refused Bequest: যে ছেলে মিষ্টির দোকানের রেসিপি নিতে চায়নি

Refused Bequest কোড স্মেল শেখো একটা পারিবারিক মিষ্টির দোকানের গল্পের মাধ্যমে — TypeScript ও C#-এ Liskov লঙ্ঘন আর delegation দিয়ে সমাধান ধাপে ধাপে।

আরও পড়ুন

Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা

বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।

আরও পড়ুন

Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে

Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।

আরও পড়ুন

Extract Superclass: দুটো যমজ class-এর জন্য একটাই সাধারণ নিয়মের বই

Extract Superclass refactoring শেখো সায়েন্স ল্যাব আর কম্পিউটার ল্যাবের গল্পের মাধ্যমে — TypeScript ও C#-এ pull-up move, superclass বনাম interface তুলনা টেবিল, এবং কীভাবে এটা Duplicate Code দূর করে।

আরও পড়ুন