Abstract Factory Pattern: এক অর্ডার, এক ম্যাচিং থালি
বাংলাদেশি বিয়ের ক্যাটারিং গল্পের মাধ্যমে Abstract Factory design pattern বুঝে নাও — সহজ TypeScript ও Python কোড, diagram, আর practice সহ।
ফাতেমার বিয়ের ক্যাটারিং-এর গল্প
ধরো, মিরপুরের একটা ব্যস্ত বিয়ের সকাল।
সুমাইয়া আপা চালান স্বাদ ক্যাটারার্স। পরিবারগুলো বিয়ের জন্য বুকিং দেয়, আর উনি পুরো meal style অফার করেন। কাস্টমার বাংলাদেশি থালি অথবা মোগলাই থালি বেছে নিতে পারে। যেটাই বেছে নেওয়া হোক, প্রতিটা অতিথির প্লেটে তিনটা জিনিস থাকে: একটা starter, একটা main course, আর একটা মিষ্টি।
তার রান্নাঘরের সবচেয়ে গুরুত্বপূর্ণ নিয়ম: এক প্লেটের সব কিছু একই style-এর হতে হবে। বাংলাদেশি থালি বুক করলে প্লেটে আসবে শিঙাড়া, তারপর ভাত-ভর্তা-তরকারি, শেষে রসমালাই। মোগলাই বুক করলে আসবে চিকেন কাবাব, তারপর বিরিয়ানি, শেষে ফালুদা।
এখন একটা বিপদের কথা ভাবো। ফাতেমার বিয়ে। পাঁচশো অতিথি। সুমাইয়া আপার নতুন সহকারী, ভয়ে কাঁপতে থাকা ছেলে রুবেল, প্লেট নিয়ে কাউন্টারে কাউন্টারে দৌড়াচ্ছে। তাড়াহুড়োয় রুবেল বাংলাদেশি থালির প্লেটে মোগলাই ফালুদা দিয়ে দিল। বরের নানি — সবাই তাকে নানিআম্মা বলে — দুই সেকেন্ডেই ধরে ফেললেন। "বাবা, এটা তো থালির সাথে মিলছে না!" রুবেলের মুখ লাল। ভুলটা ছোট, কিন্তু ক্যাটারিং-এ matching জরুরি। পুরো set-টা একসাথে থাকতে হবে।
সেই রাতে সুমাইয়া আপার ঘুম আসে না। ভাবেন: সমস্যা রুবেল না। সমস্যা হলো আমার রান্নাঘর mixing-এর সুযোগ দিচ্ছে। তাই একটা চালাক system বানালেন। সহকারীদের খোলা tray থেকে এক এক করে dish বেছে নিতে দেন না আর। বরং প্রতিটা বিয়েতে একটাই counter থাকে — হয় বাংলাদেশি counter, নয় মোগলাই counter। সহকারী শুধু সেই counter-এ যায় আর চায়: "একটা starter দাও", "একটা main দাও", "একটা মিষ্টি দাও"। সহকারী কখনো dish-এর নাম বলে না। Counter নিজেই গ্যারান্টি দেয় যে সে যা দেবে সব একটাই matching style-এর।
Counter একবার বেছে নাও, আর হাজার প্লেট perfectly match করে আসবে। Style mix করা অসম্ভব হয়ে যায়, কারণ ঘরে শুধু একটাই counter। রুবেলও এখন আর ফালুদার ভুল করতে পারবে না — বাংলাদেশি counter-এ মোগলাই ফালুদা নেই।
Counter হলো একটা factory যেটা পুরো family matching product তৈরি করে। এটাই Abstract Factory pattern। এই ক্যাটারিং গল্পটা মনে রাখো — সুমাইয়া আপা, রুবেল, আর নানিআম্মা প্রতিটা section-এ আমাদের সাথে থাকবেন, আর আমরা শীঘ্রই তাদের রান্নাঘর কোডে লিখব।
Abstract Factory pattern কী?
Abstract Factory হলো একটা creational design pattern। এর কাজ: main code-এ concrete class-এর নাম না লিখেই সম্পর্কিত object-এর family তৈরি করা।
চলো রান্নাঘর সামনে রেখে ধীরে ধীরে বুঝি:
- Product kind হলো প্লেটের একটা role: starter, main course, মিষ্টি।
- Family (যাকে variantও বলে) হলো একটা style: বাংলাদেশি বা মোগলাই।
- Abstract factory হলো একটা interface যেটা বলে: "যেকোনো counter starter, main, আর মিষ্টি দিতে পারতে হবে।" এটা কী বানানো যাবে সেটার প্রতিশ্রুতি দেয়, কোন version সেটা না।
- Concrete factory সেই interface একটা family-র জন্য implement করে। বাংলাদেশি counter দেয় শিঙাড়া, ভাত-ভর্তা-তরকারি, আর রসমালাই। মোগলাই counter দেয় চিকেন কাবাব, বিরিয়ানি, আর ফালুদা।
তোমার main code (client — এটা রুবেল) শুধু abstract factory-র reference ধরে। সে "একটা starter" চায় আর উত্তরে বিশ্বাস রাখে। সে কখনো কোথাও new Shingara() লেখে না।
এই pattern-টাও Gang of Four বই থেকে (১৯৯৪), যেখানে এটাকে কখনো Kit বা Toolkit বলা হয়, কারণ factory তোমাকে matching part-এর একটা পুরো kit দিয়ে দেয়।
এক লাইনে সারকথা: Abstract Factory = family একবার বেছে নাও, আর একটা factory object সেই family-র প্রতিটা matching member দিয়ে দেবে। রান্নাঘরের ভাষায়: counter একবার বেছে নাও, আর প্রতিটা প্লেট match করবে।
আগে পড়া Factory Method pattern-এর সাথে তুলনা করো: সেখানে একটা method-এর মাধ্যমে একটা product তৈরি হতো। সুমাইয়া আপার counter তৈরি করে পুরো একটা set product যেগুলো একসাথে fit করতে হবে। জোর দেওয়ার জায়গাটা হলো family শব্দটায়।
কলেজ corner: GoF ভাষায়, Abstract Factory হলো Factory Method-এর এক ধাপ উপরে abstraction। Factory Method inheritance দিয়ে একটা object বদলায় — subclass একটা method override করে। Abstract Factory object composition দিয়ে একটা সংগতিপূর্ণ set বদলায় — client একটা factory object দিয়ে configure হয়। Matching guarantee type system enforce করে: একটা BangladeshiThaliFactory-তে এমন কোনো code path নেই যেটা মোগলাই product return করতে পারে, তাই cross-family invariant statically hold করে, runtime discipline দিয়ে নয়।
এটা কোন সমস্যা সমাধান করে
আগে ব্যথাটা অনুভব করি। ধরো সুমাইয়া আপার ভাতিজা pattern ছাড়াই স্বাদ ক্যাটারার্স code করল। প্রতিটা dish decision হয়ে গেল style-এর উপর if-else:
// BAD CODE: style checks scattered through the kitchen
function serveGuestPlate(style: string): void {
// starter
if (style === "gujarati") {
console.log("Serving dhokla");
} else {
console.log("Serving medu vada");
}
// main course
if (style === "gujarati") {
console.log("Serving thepla with shaak");
} else {
console.log("Serving dosa with sambar");
}
// sweet
if (style === "gujarati") {
console.log("Serving shrikhand");
} else {
console.log("Serving payasam");
}
}দুটো style আর তিনটা dish-এই এটা repetitive দেখাচ্ছে। এখন বাস্তব জীবনের মতো scale করো:
- নতুন family আসে। সুমাইয়া আপা একটা চট্টগ্রামি থালি যোগ করলেন। পুরো codebase-এ প্রতিটা
if-elseখুঁজে বের করে একটা branch যোগ করতে হবে। এক জায়গা miss হলে চট্টগ্রামি বিয়েতে ভুল dish চলে যাবে — ঠিক আমাদের গল্পের mixed-plate বিপদের মতো, শুধু এখন code-এ লুকিয়ে। - Matching কিছুই enforce করে না। Compiler পুরোপুরি খুশি থাকবে যদি এক function বাংলাদেশি starter serve করে আর আরেক function মোগলাই মিষ্টি একই অতিথিকে দেয়। Bug-টা শুধু বিয়েতে, runtime-এ, নানিআম্মার সামনে ধরা পড়বে।
- Creation logic, business logic দূষিত করে। যে code অতিথিদের serve করার কথা, সেটা কোন dish কোন style-এর সিদ্ধান্তে ভরে গেছে — সর্বত্র বারবার।
এই code বড় হলে ব্যথাটা মূলত দুটো জায়গায় লাগে: প্রতিটা পরিবর্তনে style check খোঁজা, আর mixed-family bug firefighting যেটা শুধু runtime-এ ধরা পড়ে।
আমরা আসলে চাই একটাই decision point: booking সময়ে একবার style বেছে নাও, আর এরপর তৈরি প্রতিটা dish স্বয়ংক্রিয়ভাবে সঠিক, পরস্পর-matching family-র হবে। এটাই Abstract Factory-র প্রতিশ্রুতি — সুমাইয়া আপার এক-counter নিয়ম, type-এ লেখা।
Abstract Factory কীভাবে কাজ করে, ধাপে ধাপে
চলো ধীরে ধীরে solution বানাই। পরিকল্পনার জন্য একটা চমৎকার কৌশল আছে: আগে একটা matrix (table) আঁকো। সুমাইয়া আপার রান্নাঘরের দেয়ালে আসলে এই matrix আঁকা আছে:
| বাংলাদেশি family | মোগলাই family | |
|---|---|---|
| Starter | শিঙাড়া | চিকেন কাবাব |
| Main course | ভাত-ভর্তা-তরকারি | বিরিয়ানি |
| মিষ্টি | রসমালাই | ফালুদা |
সারিগুলো হলো product kind। Column-গুলো হলো family। প্রতিটা cell একটা concrete class। এখন এই ধাপগুলো follow করো:
- প্রতিটা সারির জন্য একটা interface। Abstract product তৈরি করো:
Starter,MainCourse,Sweet। সেই সারির প্রতিটা dish তার সারির interface implement করে। - প্রতিটা সারির জন্য একটা method সহ একটা abstract factory।
ThaliFactoryতৈরি করোcreateStarter(),createMainCourse(),createSweet()দিয়ে। প্রতিটা method abstract product type return করে। - প্রতিটা column-এর জন্য একটা concrete factory।
BangladeshiThaliFactoryবাংলাদেশি cell-গুলো return করে;MoghlaiThaliFactoryমোগলাই cell-গুলো return করে। প্রতিটা concrete factory ঠিক একটা column থেকে dish তৈরি করে — এভাবেই matching guarantee হয়। - Client শুধু abstraction দিয়ে লেখো। Serving code
ThaliFactoryপায় (সাধারণত constructor-এর মাধ্যমে) আর শুধুStarter,MainCourse, আরSweetreference দিয়ে কাজ করে। রুবেল শুধু role জানে, কখনো dish-এর নাম না। - একবার concrete factory বেছে নাও। Booking সময়ে (program-এর শুরু), কাস্টমারের পছন্দ পড়ো আর matching factory তৈরি করো। নিচে pass করো। Downstream আর কেউ জানবে না কোন family ব্যবহার হচ্ছে।
(MainCourse সারিটা diagram-এ বাদ দেওয়া হয়েছে শুধু পড়ার সুবিধার জন্য — এটা ঠিক একই রকম।)
গ্যারান্টিটা দেখো: একটা BangladeshiThaliFactory object-এ কোনো code path নেই যেটা মোগলাই ফালুদা তৈরি করতে পারে। Family মেশানো এখন আর শুধু discipline-এর ব্যাপার না — structurally impossible হয়ে গেছে। Type system পুলিশিং করে, তাই রুবেল এখন নিশ্চিন্ত থাকতে পারে।
একজন অতিথিকে serve করার দৃশ্য, সময়ের মধ্য দিয়ে একটা কথোপকথন হিসেবে। রুবেল (client) শুধু counter-এর সাথে কথা বলে, আর counter matching dish দিয়ে জবাব দেয়:
কলেজ corner: factory-র প্রতিটা method (createStarter()) নিজেই একটা Factory Method। তাই Abstract Factory সবচেয়ে ভালো বোঝা যায় এক invariant-এর নিচে group করা Factory Method-এর একটা bundle হিসেবে: "আমার সব output একটা variant-এর।" যখন দেখবে একটা class-এর একমাত্র কাজ createX() method-এর একটা সংগতিপূর্ণ set — তুমি এই pattern-ই দেখছ, ভলে কেউ নাম দিয়েছে কি না।
বাস্তব জীবনের code উদাহরণ
এখন TypeScript-এ স্বাদ ক্যাটারার্স পুরোপুরি লিখে ফেলি। দেয়ালের matrix হুবহু mirror করে section by section যাব।
Section ১: abstract products। আমাদের matrix-এর প্রতিটা সারির জন্য একটা interface।
// ---------- ABSTRACT PRODUCTS (the rows) ----------
interface Starter {
name: string;
serve(): void;
}
interface MainCourse {
name: string;
serve(): void;
}
interface Sweet {
name: string;
serve(): void;
}Section ২: concrete products। Matrix-এর প্রতিটা cell একটা ছোট class হয়।
// ---------- GUJARATI FAMILY ----------
class Dhokla implements Starter {
name = "dhokla";
serve(): void {
console.log(" Starter: soft dhokla with green chutney.");
}
}
class TheplaShaak implements MainCourse {
name = "thepla with shaak";
serve(): void {
console.log(" Main: thepla with bateta nu shaak.");
}
}
class Shrikhand implements Sweet {
name = "shrikhand";
serve(): void {
console.log(" Sweet: chilled kesar shrikhand.");
}
}
// ---------- SOUTH INDIAN FAMILY ----------
class MeduVada implements Starter {
name = "medu vada";
serve(): void {
console.log(" Starter: crispy medu vada with coconut chutney.");
}
}
class DosaSambar implements MainCourse {
name = "dosa with sambar";
serve(): void {
console.log(" Main: paper dosa with hot sambar.");
}
}
class Payasam implements Sweet {
name = "payasam";
serve(): void {
console.log(" Sweet: warm semiya payasam.");
}
}Section ৩: abstract factory আর concrete factory। এটা pattern-এর হৃদয় — আমাদের গল্পের counter।
// ---------- ABSTRACT FACTORY ----------
// "Any counter must be able to give these three things."
interface ThaliFactory {
styleName: string;
createStarter(): Starter;
createMainCourse(): MainCourse;
createSweet(): Sweet;
}
// ---------- CONCRETE FACTORIES (the columns) ----------
class GujaratiThaliFactory implements ThaliFactory {
styleName = "Gujarati";
createStarter(): Starter {
return new Dhokla();
}
createMainCourse(): MainCourse {
return new TheplaShaak();
}
createSweet(): Sweet {
return new Shrikhand();
}
}
class SouthIndianThaliFactory implements ThaliFactory {
styleName = "South Indian";
createStarter(): Starter {
return new MeduVada();
}
createMainCourse(): MainCourse {
return new DosaSambar();
}
createSweet(): Sweet {
return new Payasam();
}
}GujaratiThaliFactory আবার ধীরে পড়ো। প্রতিটা method একই column থেকে dish return করে। এই class থেকে payasam বের হওয়ার কোনো উপায় নেই। এটাই matching guarantee, সাধারণ code-এ লেখা। নানিআম্মা এই class পড়ে হাসতেন।
Section ৪: client। Serving crew শুধু abstraction দিয়ে কাজ করে।
// ---------- CLIENT ----------
class WeddingService {
// The factory comes in through the constructor.
// The crew never knows WHICH concrete factory it is.
constructor(private factory: ThaliFactory) {}
serveGuest(guestName: string): void {
console.log(`Plate for ${guestName} (${this.factory.styleName} thali):`);
const starter = this.factory.createStarter();
const main = this.factory.createMainCourse();
const sweet = this.factory.createSweet();
starter.serve();
main.serve();
sweet.serve();
}
}serveGuest কত পরিষ্কার দেখো। কোনো if নেই। কোনো style string নেই। কোনো dish-এর নাম নেই। শুধু role: starter, main, sweet। এটা নতুন system-এর পরের রুবেল — শান্ত, দ্রুত, আর ফালুদার ভুল করতে অক্ষম।
Section ৫: booking desk — একটাই decision point।
// ---------- COMPOSITION ROOT ----------
// The ONLY place that knows concrete factories.
function bookWedding(style: "gujarati" | "south-indian"): WeddingService {
const factory: ThaliFactory =
style === "gujarati"
? new GujaratiThaliFactory()
: new SouthIndianThaliFactory();
return new WeddingService(factory);
}
const shahWedding = bookWedding("gujarati");
shahWedding.serveGuest("Dadi ji");
shahWedding.serveGuest("Rohan");
const iyerWedding = bookWedding("south-indian");
iyerWedding.serveGuest("Paati");এটা চালালে print হয়:
Plate for Dadi ji (Gujarati thali):
Starter: soft dhokla with green chutney.
Main: thepla with bateta nu shaak.
Sweet: chilled kesar shrikhand.
Plate for Rohan (Gujarati thali):
Starter: soft dhokla with green chutney.
Main: thepla with bateta nu shaak.
Sweet: chilled kesar shrikhand.
Plate for Paati (South Indian thali):
Starter: crispy medu vada with coconut chutney.
Main: paper dosa with hot sambar.
Sweet: warm semiya payasam.প্রতিটা প্লেট perfectly match করেছে। নানিআম্মা খুশি। রুবেল বেতন বৃদ্ধি পেল।
এখন যাদুর মুহূর্ত: সুমাইয়া আপা চট্টগ্রামি থালি launch করলেন। পুরনো code-এ কী বদলায়? কিছুই না। আমরা শুধু একটা নতুন column যোগ করি:
class PaneerTikka implements Starter {
name = "paneer tikka";
serve(): void {
console.log(" Starter: smoky paneer tikka.");
}
}
class CholeBhature implements MainCourse {
name = "chole bhature";
serve(): void {
console.log(" Main: chole with fluffy bhature.");
}
}
class GajarHalwa implements Sweet {
name = "gajar halwa";
serve(): void {
console.log(" Sweet: hot gajar ka halwa with ghee.");
}
}
class PunjabiThaliFactory implements ThaliFactory {
styleName = "Punjabi";
createStarter(): Starter {
return new PaneerTikka();
}
createMainCourse(): MainCourse {
return new CholeBhature();
}
createSweet(): Sweet {
return new GajarHalwa();
}
}WeddingService এক অক্ষরও বদলায়নি। শুধু booking desk-এ একটা নতুন option যোগ হলো। এটাই Open/Closed Principle: নতুন family = নতুন code, edited code নয়।
Python-এ একই আইডিয়া
Pattern একটা ভাষায় বাঁধা নয়। একই রান্নাঘরের compact Python version:
from abc import ABC, abstractmethod
# ----- abstract products -----
class Starter(ABC):
@abstractmethod
def serve(self) -> None: ...
class Sweet(ABC):
@abstractmethod
def serve(self) -> None: ...
# ----- concrete products -----
class Dhokla(Starter):
def serve(self) -> None:
print(" Starter: dhokla")
class MeduVada(Starter):
def serve(self) -> None:
print(" Starter: medu vada")
class Shrikhand(Sweet):
def serve(self) -> None:
print(" Sweet: shrikhand")
class Payasam(Sweet):
def serve(self) -> None:
print(" Sweet: payasam")
# ----- abstract factory -----
class ThaliFactory(ABC):
@abstractmethod
def create_starter(self) -> Starter: ...
@abstractmethod
def create_sweet(self) -> Sweet: ...
# ----- concrete factories -----
class GujaratiThaliFactory(ThaliFactory):
def create_starter(self) -> Starter:
return Dhokla()
def create_sweet(self) -> Sweet:
return Shrikhand()
class SouthIndianThaliFactory(ThaliFactory):
def create_starter(self) -> Starter:
return MeduVada()
def create_sweet(self) -> Sweet:
return Payasam()
# ----- client -----
def serve_guest(factory: ThaliFactory) -> None:
factory.create_starter().serve()
factory.create_sweet().serve()
serve_guest(GujaratiThaliFactory()) # dhokla + shrikhand, always matched
serve_guest(SouthIndianThaliFactory()) # medu vada + payasam, always matchedএকই আকার, আলাদা ভাষা: সারির জন্য interface, প্রতিটা column-এর জন্য একটা factory class, শুধু abstraction দেখে এমন একটা client।
আর একটা ছোট C# variation যেটা real team-রা ভালোবাসে — testing family। Production-এ app real factory পায়; test-এ পায় fake factory যার পুরো family-ই harmless fake দিয়ে তৈরি:
public interface IKitchenFactory
{
IOven CreateOven();
IBillPrinter CreatePrinter();
}
// Production column: real machines.
public class RealKitchenFactory : IKitchenFactory
{
public IOven CreateOven() => new GasOven();
public IBillPrinter CreatePrinter() => new ThermalPrinter();
}
// Test column: everything fake, everything safe.
public class FakeKitchenFactory : IKitchenFactory
{
public IOven CreateOven() => new RecordingFakeOven();
public IBillPrinter CreatePrinter() => new InMemoryPrinter();
}
// The client never knows which world it lives in:
var kitchen = new KitchenService(new FakeKitchenFactory());একটা factory swap করো, আর client যে পুরো দুনিয়ায় বাস করে সেটাই swap হয় — real বা fake, কিন্তু সবসময় internally consistent। এটাই family guarantee testing tool হিসেবেও কাজ করছে।
বাস্তব software-এ কোথায় দেখা যায়
Abstract Factory চুপচাপ অনেক বিখ্যাত software-এ শক্তি জোগাচ্ছে:
- Java-র
DocumentBuilderFactory.newInstance()javax.xml.parserspackage-এ পাঠ্যবই উদাহরণ। Abstract factory-কেDocumentBuilderচাও, আর সে তোমার পরিবেশের জন্য সঠিক concrete XML-parsing family return করে — তোমার code কখনো parser implementation-এর নাম জানে না। এর সহোদরTransformerFactory.newInstance()একইভাবে কাজ করে। - .NET-এর
DbProviderFactory(System.Data.Common-এ) একটা সুন্দর family factory। একটা factory object তোমাকেCreateConnection(),CreateCommand(), আরCreateDataAdapter()দেয় — আর সবগুলো একই database family-র (সব SQL Server piece, বা সব MySQL piece)। SQL Server connection-এর সাথে MySQL command pair করা বিপদজনক হতো; factory সেটা অসম্ভব করে দেয়, ঠিক আমাদের থালি counter-এর মতো। - Cross-platform UI toolkit look-and-feel-এর জন্য pattern ব্যবহার করে: একবার platform family বেছে নাও, আর এরপর তৈরি প্রতিটা widget — button, checkbox, scrollbar — স্বয়ংক্রিয়ভাবে সেই platform-এর style-এ match করে। এটাই GoF বইয়ের original motivating উদাহরণ ছিল।
- Test code বনাম production code। অনেক app production-এ "real services" factory আর test-এ "fake services" factory pass করে, যেমন আমাদের C# snippet-এ। একই client code, দুটো consistent family।
গভীরে পড়ার জন্য দেখো iluwatar/java-design-patterns-এর worked উদাহরণ, faif/python-patterns-এর ছোট Python version, আর Refactoring Guru-এর illustrated guide।
কখন ব্যবহার করবে, কখন না
প্রথমে এই map-এ তোমার project খুঁজে নাও। স্বাদ ক্যাটারার্স উপরে-ডানে আছে: কয়েক ধরনের product যেগুলো match করতে হবে, আর নতুন family আসতেই থাকে।
তারপর table দিয়ে confirm করো:
| পরিস্থিতি | Abstract Factory ব্যবহার করবে? | কেন |
|---|---|---|
| কয়েকটা product আছে যেগুলো matching set হিসেবে একসাথে ব্যবহার করতে হবে | হ্যাঁ | Factory family মেশানো অসম্ভব করে দেয় |
| নতুন family আসবে বলে আশা করছ (নতুন theme, নতুন platform, নতুন menu style) | হ্যাঁ | নতুন family = শুধু একটা নতুন factory class আর তার product |
| সব concrete-class জ্ঞান একটা startup জায়গায় রাখতে চাও | হ্যাঁ | শুধু composition root concrete factory-র নাম জানে |
| Testing-এর জন্য real বনাম fake object set দরকার | হ্যাঁ | একটা factory swap করো, পুরো দুনিয়া swap হয় |
| মাত্র একটা product kind আছে | না | একটা Factory Method যথেষ্ট — কোনো family-ই নেই |
| Product kind প্রায়ই বদলায় (matrix-এ নতুন সারি) | না | প্রতিটা নতুন kind সব concrete factory-তে edit করতে বাধ্য করে |
| Program ছোট আর variant আসবে না কখনো | না | অতিরিক্ত interface আর class শুধু overhead |
দ্বিতীয় "না" সারিটা মনোযোগ দেওয়ার দাবি রাখে। দেয়ালের matrix মনে রাখো: pattern নতুন column (family) পছন্দ করে কিন্তু নতুন সারি (product kind) অপছন্দ করে। নিচের chart তিনটা family সহ আমাদের রান্নাঘরের দুটো দিক দেখাচ্ছে:
Pattern দিয়ে চট্টগ্রামি family যোগ করতে একটা existing file ছুঁয়েছিল (booking desk)। কিন্তু একটা createDrink() সারি যোগ করলে বাংলাদেশি, মোগলাই, আর চট্টগ্রামি factory — আর interface-ও — একসাথে সব বদলাতে হবে। শুরুতেই সারি পরিকল্পনা করো, যেভাবে সুমাইয়া আপা রান্নাঘর খোলার আগেই দেয়াল matrix এঁকেছিলেন।
কলেজ corner: এই asymmetry হলো expression problem-এর একটা classic উদাহরণ: class hierarchy-তে নতুন variant যোগ করা সহজ কিন্তু নতুন operation যোগ করা কঠিন, functional sum type উল্টো করে। Abstract Factory ইচ্ছাকৃতভাবে variant axis optimize করে। তোমার domain যদি kind axis-এ বাড়ে, visitor-style design বিবেচনা করো, বা ব্যথা কমাতে default implementation সহ periodic interface churn মেনে নাও।
একটা বিয়ের booking-এর জীবনচক্র
দেখো বড় সিদ্ধান্তটা কোথায় হয়: ঠিক একবার, booking-এর সময়। FactoryChosen state-এর পরে, রান্নাঘর আর কখনো "কোন style?" জিজ্ঞেস করে না — শুধু serve করে।
ServingGuests-এর self-loop হলো pattern-এর সৌন্দর্য: সেখানে শত শত creation হয়, আর একটাও family প্রশ্ন আবার করে না। চিত্র ৩-এর no-pattern flowchart-এর সাথে তুলনা করো, যেখানে প্রতিটা একটা প্লেট তিনবার সেটা জিজ্ঞেস করেছিল।
প্রতিবেশী pattern-এর সাথে তুলনা
| Pattern | কী তৈরি করে | শক্তি | সাবধান থাকো |
|---|---|---|---|
| Abstract Factory | সম্পর্কিত product-এর একটা family | Set match করার guarantee দেয় | নতুন product kind ব্যয়বহুল |
| Factory Method | একটা product | সহজ; দুর্দান্ত শুরুর জায়গা | এক method, এক product শুধু |
| Builder | একটা জটিল product, ধাপে ধাপে | বড় object-এর পাঠযোগ্য assembly | Family নিয়ে একদম না |
| Prototype | একটা বিদ্যমান object-এর copy | Subclassing লাগে না | ভালো clone operation দরকার |
| Singleton | একটাই shared instance | প্রায়ই concrete factory-র জন্য ব্যবহার হয় | সম্পূর্ণ আলাদা উদ্দেশ্য |
গল্পের ভাষায় কিছু দরকারী সম্পর্ক মনে রাখো:
- Abstract Factory-র ভেতরের প্রতিটা method (
createStarter(),createSweet()) সাধারণত Factory Method হিসেবে implement হয়। তাই সুমাইয়া আপার counter আসলে কয়েকটা Factory Method-এর কৌশলের একটা দল। - Design প্রায়ই একটা Factory Method দিয়ে শুরু হয় আর আরো সম্পর্কিত product দেখা দিলে Abstract Factory-তে বেড়ে ওঠে।
- একটা concrete factory প্রায়ই Singleton করা হয়, কারণ পুরো বিয়েতে একটাই বাংলাদেশি counter যথেষ্ট।
- Builder আলাদা প্রশ্নের উত্তর দেয়: "কোন family?" নয় বরং "এই একটা জটিল object কীভাবে assemble করব?" Abstract Factory এমন ready-made builder-ও return করতে পারে — একটা counter যেটা order slip দেয়।
ছাত্রছাত্রীরা যেসব সাধারণ ভুল করে
ভুল ১: এক প্লেটের জন্য দুটো আলাদা factory ব্যবহার। Client যদি এক function-এ new BangladeshiThaliFactory() আর অন্য function-এ new MoghlaiThaliFactory() তৈরি করে, তুমি অতিরিক্ত পদক্ষেপ দিয়ে রুবেলের ফালুদা বিপদ আবার তৈরি করেছ। Factory একবার বেছে নিতে হবে আর নিচে pass করতে হবে — সাধারণত constructor-এর মাধ্যমে।
ভুল ২: Factory method থেকে concrete type return করা। createStarter() যদি Starter-এর বদলে Shingara return type declare করে, client code concrete class-এর উপর depend করতে শুরু করে আর decoupling বাষ্প হয়ে যায়। Factory method-গুলো অবশ্যই abstract product type return করবে।
ভুল ৩: Factory Method-এর সাথে গুলিয়ে ফেলা। "আমার একটা factory class আছে, তাই এটা নিশ্চয়ই Abstract Factory" — জরুরি না! মাত্র একটা product তৈরি করলে এটা একটা Factory Method। এখানে abstract মানে একাধিক creation method সহ একটা factory interface যেটা একটা family তৈরি করে।
ভুল ৪: Matrix আলসেমি দিয়ে design করা। ছাত্রছাত্রীরা প্রায়ই এক product kind দিয়ে শুরু করে, তারপর প্রতি সপ্তাহে createX() method bolt on করতে থাকে। প্রতিটা যোগ প্রতিটা concrete factory ভাঙে। আগে বসো, তোমার সারি (kind) আর column (family) list করো, তারপরই interface লেখো — রান্নাঘর খোলার আগে দেয়াল matrix আঁকো।
একটা quick-scan table হিসেবে একই list:
| ভুল | কেমন দেখায় | সমাধান |
|---|---|---|
| এক প্লেটের জন্য দুটো factory | আলাদা function-এ আলাদা new XFactory() call | Composition root-এ একবার বেছে নাও, নিচে pass করো |
| Concrete return type | createStarter(): Shingara | সবসময় abstract product type return করো |
| ভুল pattern নাম | যেকোনো factory class-কে Abstract Factory বলা | Methods-এর family = Abstract Factory; এক method = Factory Method |
| আলসে matrix | প্রতি সপ্তাহে নতুন createX() bolt on | Interface লেখার আগে সারি আর column পরিকল্পনা করো |
দ্রুত revision box
+=================================================================+
| ABSTRACT FACTORY - QUICK REVISION |
+=================================================================+
| IDEA : One factory interface creates a whole FAMILY |
| of matching products. Pick the family once. |
| ALSO CALLED: Kit / Toolkit (GoF, 1994) |
|-----------------------------------------------------------------|
| THE MATRIX |
| rows = product kinds (Starter, Main, Sweet) |
| columns = families (Gujarati, South Indian, Punjabi) |
| one interface per ROW, one concrete factory per COLUMN |
|-----------------------------------------------------------------|
| GOLDEN RULES |
| 1. Client sees ONLY abstract factory + abstract products. |
| 2. One factory = one family. Mixing becomes impossible. |
| 3. New family = cheap (add a column). |
| New kind = costly (every factory changes). |
|-----------------------------------------------------------------|
| REAL LIFE : DocumentBuilderFactory (Java XML), |
| DbProviderFactory (.NET), UI look-and-feel kits |
| REMEMBER : "One counter, one matching thali." |
+=================================================================+Practice exercise
তোমার পালা counter-এ! এই কাজগুলো চেষ্টা করো:
- চট্টগ্রামি থালি যোগ করো।
MezbanBeef(starter),KalaiBhuna(main), আরRosogolla(sweet) তৈরি করো, সাথে একটাChittagongThaliFactory। নিজেই prove করো যেWeddingService-এ শূন্য পরিবর্তন লেগেছে। - উৎসব decoration kit। ধরো, একটা school বার্ষিকীর জন্য দুটো theme-এ সাজায়: ঐতিহ্যবাহী (গাঁদাফুলের মালা, মাটির প্রদীপ, আলপনা) আর আধুনিক (balloon arch, LED আলো, poster art)।
Garland,Light, আরFloorArt-কে product kind হিসেবে model করো আর দুটো concrete factory বানাও। একটাDecorationCrewclient লেখো যেটা যে factory পায় সেটা দিয়ে একটা classroom সাজায়। - Code লেখার আগে আঁকো। Exercise 2-এর জন্য নিজে দেয়াল matrix (সারি আর column) আঁকো আর চিত্র ৫ আর চিত্র ৯-এর নিজের version আঁকো। Theme কোন এক state-এ বেছে নেওয়া হয় সেটা চিহ্নিত করো।
- ভাবো আর লেখো (code ছাড়া)। সুমাইয়া আপা প্রতিটা থালিতে চতুর্থ item যোগ করতে চান: একটা স্বাগত পানীয়। ৪-৫ বাক্যে ব্যাখ্যা করো কোন কোন file বদলাতে হবে, কেন এটা pattern-এর "ব্যয়বহুল দিক", আর আগে matrix পরিকল্পনা করলে কীভাবে সাহায্য হতো।
- কলেজ corner challenge। Exercise 2-এর জন্য
FakeDecorationFactoryআইডিয়া বানাও: একটা test family যার product-গুলো তাদের কাছে কী চাওয়া হয়েছে সেটা record করে। একটা unit test লেখো যেটা prove করেDecorationCrewকোনো real decoration class না ছুঁয়ে সঠিকভাবে classroom সাজায়।
দারুণ কাজ! তুমি এখন জানো সুমাইয়া আপা কীভাবে matching family guarantee দেন। এরপর, তারিক চাচার দর্জির দোকানে যাও আর Builder pattern দিয়ে একটা বড়, জটিল object ধাপে ধাপে কীভাবে তৈরি করতে হয় সেটা শিখে নাও।
সচরাচর জিজ্ঞাসা
- 'product-এর family' মানে আসলে কী?
- মানে হলো একদল আলাদা আলাদা object, যেগুলো একসাথে ব্যবহার হওয়ার জন্য তৈরি আর অবশ্যই একে অপরের সাথে match করতে হবে। যেমন, বাংলাদেশি starter, বাংলাদেশি main course, আর বাংলাদেশি মিষ্টি মিলে এক family। Windows button আর Windows checkbox মিলে আরেক family। Factory গ্যারান্টি দেয় দুই family-র item কখনো mix হবে না।
- Abstract Factory আর Factory Method-এর পার্থক্য কী?
- Factory Method-এ একটা creation method থাকে আর একটা product তৈরি হয়। Abstract Factory হলো একটা interface যেখানে প্রতিটা product-এর ধরনের জন্য আলাদা creation method আছে, আর সবগুলো মিলে একই matching family-র product return করে। আসলে Abstract Factory-র ভেতরের প্রতিটা method নিজেই সাধারণত একটা Factory Method।
- কোন concrete factory ব্যবহার করব সেটা কোথায় ঠিক করি?
- ঠিক একটাই জায়গায়, সাধারণত program-এর শুরুতে। একটা setting পড়ো, একবার matching concrete factory তৈরি করো, আর বাকি কোডে pass করে দাও। এরপর আর কোনো কোড জানবে না কোন family ব্যবহার হচ্ছে।
- Abstract Factory-র সবচেয়ে বড় দুর্বলতা কী?
- নতুন ধরনের product যোগ করা কষ্টের। factory interface-এ একটা createDrink method যোগ করলে প্রতিটা concrete factory update করতে হবে। এই pattern নতুন family-র জন্য বন্ধুত্বপূর্ণ, কিন্তু নতুন product-এর ধরনের জন্য না।
- ছোট program-এ Abstract Factory কি বেশি জটিল হয়ে যায়?
- অনেক সময় হ্যাঁ। এই pattern-এ অনেক interface আর class যোগ হয়। মাত্র একটা product variant থাকলে, বা product-গুলো একসেট হিসেবে match করতে না হলে, একটা সাধারণ Factory Method বা সরাসরি constructor ব্যবহার করলে কোড ছোট আর পড়তে সহজ হবে।
আরো দেখো
সম্পর্কিত পাঠ
Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle
Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।
Builder Pattern: একটু একটু করে বানাও, ওস্তাদ দর্জির মতো
ঢাকার একটা দর্জির দোকানের গল্পের মাধ্যমে Builder design pattern শেখো — fluent TypeScript ও C# কোড, telescoping constructor এর সমস্যা, আর practice টাস্কসহ।
Singleton Pattern: পুরো স্কুলে একজনই হেড স্যার
স্কুলের হেড স্যারের গল্প দিয়ে Singleton design pattern বোঝো — সহজ TypeScript ও C# কোড, thread safety, আর কেন অনেক সিনিয়র ডেভেলপার এটাকে anti-pattern বলেন।
Prototype Pattern: জিরো থেকে না বানিয়ে ফটোকপি করো
বিয়ের কার্ডের দোকানের গল্প দিয়ে Prototype design pattern শেখো — TypeScript আর Python-এর সহজ উদাহরণ, আর shallow vs deep copy-এর পরিষ্কার demo সহ।