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

Flyweight Pattern: একটা জার্সি ডিজাইন, পুরো দলের খেলোয়াড়

ক্রিকেট জার্সির গল্প দিয়ে Flyweight pattern শেখো। হাজার হাজার object-এর মধ্যে ভারী common data শেয়ার করো আর বিশাল পরিমাণ memory বাঁচাও।

22 মিনিট আপডেট: June 11, 2026beginner
design-patternsstructural-patternsflyweightmemory-optimizationcachingtypescriptcsharp

🏏 একটা জার্সি ডিজাইন, এগারোজন খেলোয়াড়, লক্ষ লক্ষ ভক্ত

ধরো, রুবেল-এর ঢাকার নিউমার্কেটে একটা ছোট ক্রিকেট ফ্যান শপ আছে। বিশ্বকাপের সিজন আসছে, আর তার সবচেয়ে বেশি বিক্রি হওয়া জিনিস হলো কাস্টম নাম প্রিন্ট করা বাংলাদেশ দলের জার্সি। তার ছোট বোন সুমাইয়া CSE-তে পড়ে, ছুটিতে দোকানের billing software দেখে — আর সে এখন software-এ একটা গুরুতর bug খুঁজে পেতে চলেছে।

আগে ভাবো জার্সিটা কীভাবে তৈরি হয়। ডিজাইন টিম মাসের পর মাস কাজ করে: সবুজের নির্দিষ্ট shade, লাল-সবুজের stroke, sponsor logo, কাপড়ের pattern। এই ডিজাইন ভারী কাজ, আর এটা করা হয় একবারই

এখন দলের সব খেলোয়াড়ের জন্য জার্সি দরকার — সাকিব, মুশফিক, তামিম আর বাকি সবার। কারখানা কি প্রতিটা খেলোয়াড়ের জন্য শুরু থেকে নতুন ডিজাইন করে? অবশ্যই না। প্রতিটা খেলোয়াড়ের জার্সি একই shared design ব্যবহার করে। শুধু দুটো ছোট জিনিস আলাদা: পিছনে নাম আর নম্বর

আরেকটু এগিয়ে যাও, রুবেলের দোকানে। লক্ষ লক্ষ ভক্ত একই জার্সি কেনে, প্রত্যেকে পিছনে নিজের নাম প্রিন্ট করায়। একটা ডিজাইন; লক্ষ লক্ষ জার্সি। কারখানা একটাই master design file রাখে, লক্ষ লক্ষ কপি না।

একটা জার্সিকে দুই ভাগে ভাগ করো:

  • Shared অংশ — রং, pattern, logo, কাপড়। সবার জন্য একই। তৈরি করতে ভারী। একবারই রাখা হয়।
  • Unique অংশ — নাম আর নম্বর। সবার জন্য আলাদা। ছোট্ট। প্রতিজনের জন্য রাখা হয়।

এখন ভাবো কারখানা যদি বোকার মতো প্রতিটা জার্সি record-এর ভেতরে পুরো multi-GB design file-এর একটা করে কপি রাখতো। দশ লক্ষ জার্সি, দশ লক্ষ কপি একই ডিজাইনের। Storage কোনো কারণ ছাড়াই ফেটে যেত।

সুমাইয়ার আবিষ্কার: রুবেলের billing software ঠিক এই বোকামিটাই করছিল memory-তে object নিয়ে — প্রতিটা বিক্রি-হওয়া জার্সির record-এ design image-এর পুরো একটা কপি ছিল। Flyweight pattern হলো সেই কারখানার common sense কোডে প্রয়োগ: ভারী shared অংশ একবারই রাখো, শুধু ছোট্ট unique অংশ প্রতিটা object-এ রাখো, আর সবাইকে shared অংশটা point করতে দাও।

Figure 1: রুবেলের দোকানে একজন ভক্ত কাস্টম জার্সি কিনছে

শেষ section-টা লক্ষ্য করো। প্রতিটা বিক্রির জন্য যে record save হয় সেটা ছোট্ট — একটা নাম, একটা নম্বর, আর একটা pointer। ভারী ডিজাইনটা fetch করা হয়, কপি করা হয় না। এই ছবিটা মাথায় রাখো; পুরো pattern এখানেই আছে।

Flyweight pattern কী?

Flyweight একটা structural design pattern যেটা একই পরিমাণ RAM-এ অনেক বেশি object ধরতে দেয়। এটা কাজ করে একটা object-এর data-কে দুই ভাগে ভাগ করে আলাদাভাবে সামলানোর মাধ্যমে:

  • Intrinsic state — যে data অনেক object-এ একই আর কখনো বদলায় না (জার্সির ডিজাইন)। এটা flyweight নামের shared object-এর ভেতরে যায়, ঠিক একবারই রাখা হয়।
  • Extrinsic state — যে data প্রতিটা object-এ unique (খেলোয়াড়ের নাম, নম্বর)। এটা flyweight-এর বাইরে থাকে, একটা ছোট্ট context object-এ বা method-এর parameter হিসেবে পাঠানো হয়।

একটা flyweight factory flyweight বিতরণ করে। দুটো code যখন "বাংলাদেশ ODI জার্সি ডিজাইন" চাইবে, factory দুজনকেই cache থেকে একই object দেবে। তাই এই pattern-এর আরেক নাম হলো Cache

দুই ধরনের state পাশাপাশি দেখো — এই table-ই পুরো pattern:

জিজ্ঞেস করার প্রশ্নIntrinsic stateExtrinsic state
জার্সির উদাহরণরং, logo, কাপড়ের patternভক্তের নাম, পিছনে নম্বর
Object-গুলোতে একই?হ্যাঁ — লক্ষ লক্ষে একইনা — প্রতিটা object-এ unique
বদলাতে পারে?কখনো না — তৈরির পর frozenঅবাধে, context অনুযায়ী
কোথায় থাকে?Shared flyweight-এর ভেতরেContext-এ, বা parameter হিসেবে
কতটা বড়?ভারী (KB থেকে MB)ছোট্ট (কয়েক byte)
RAM-এ কতটা কপি?প্রতিটা distinct design-এর জন্য একটাপ্রতিটা object-এ একটা — এড়ানো যায় না

একটা নিয়ম একদম বাধ্যতামূলক: flyweight অবশ্যই immutable (তৈরির পর read-only) হতে হবে। একটা object হাজার হাজার context শেয়ার করে; কেউ যদি এটায় লিখতে পারতো, সবার data একসাথে বদলে যেত।

💡

নাম মনে রাখার কৌশল: intrinsic = inside = identical (shared, flyweight-এর ভেতরে রাখা)। Extrinsic = external = exclusive (unique, বাইরে রাখা আর পাঠানো)। তোমার class-এর প্রতিটা field যদি এই দুটো শব্দের একটা দিয়ে label করতে পারো, তাহলে pattern-এর সবচেয়ে কঠিন অংশটা শেষ।

কলেজ কর্নার — formal split: intrinsic state হলো context-free information — এটা শুধু নির্ভর করে জিনিসটা কী তার উপর ("বাংলাদেশ ODI জার্সি"), কোথায় বা কীভাবে ব্যবহার হচ্ছে তার উপর না। Extrinsic state হলো context-dependent: এটা ব্যবহারের জায়গা অনুযায়ী বদলায় (কার পিছনে, কোন নম্বর, কোন তাকে)। Pattern কাজ করে কারণ context-free data নিরাপদে alias করা যায় — একটা immutable object-এর অনেক reference মেমরির bill বাদে অনেক কপির মতোই। এটাই compiler আর functional language-এ hash consing-এর পেছনের ধারণা, যেখানে identical immutable subtree একবারই রাখা হয় আর pointer দিয়ে compare করা হয়। Data mutable বা context-dependent হলেই aliasing দেখা যায় (একজন লিখলে সবার view বদলায়) আর কৌশলটা ভেঙে পড়ে। তাই immutability এখানে style preference না — এটা load-bearing দেয়াল।

এটা কোন সমস্যা সমাধান করে

এই হলো সুমাইয়ার খুঁজে পাওয়া bug-এর চেহারা। দোকানের system প্রতিটা বিক্রি হওয়া জার্সির জন্য একটা object রাখে, আর naive class সব কিছু প্রতিটা object-এ রাখে:

// The naive way: every jersey carries the FULL design
class Jersey {
  constructor(
    public fanName: string,        // unique  — fine
    public num: number,            // unique  — fine
    public teamName: string,       // same for lakhs of jerseys
    public colorScheme: string,    // same for lakhs of jerseys
    public logoImage: Uint8Array,  // ~200 KB — same for lakhs of jerseys!
    public fabricPattern: Uint8Array, // ~300 KB — same again!
  ) {}
}

একটা ডিজাইনের দশ লক্ষ (1,000,000) জার্সি বিক্রি করো। নাম আর নম্বর প্রতিটা object-এ আসলেই আলাদা — এটা প্রায় কিছুই লাগায় না। কিন্তু logo আর fabric data-র আধা megabyte প্রতিটা single object-এ একই। হিসাবটা নির্মম:

  • 1,000,000 জার্সি × ~500 KB duplicated design data ≈ 500 GB RAM
  • কার্যকর unique data: 1,000,000 × কয়েক ডজন byte ≈ কয়েক MB

প্রোগ্রাম out-of-memory error দিয়ে crash করে, আর memory ভরা ছিল 99.99% একই bitmap এক মিলিয়ন বার রাখার কারণে। Logic-এ কোনো সমস্যা নেই — শুধু duplication-এ।

Figure 2: Duplicated heavy data vs one shared flyweight

Fix-এর পরে memory-র খরচ প্রায় (দশ লক্ষ ছোট context) + (1 design × 500 KB)। 500 GB থেকে কয়েক MB-তে নেমে আসে। একই সংখ্যক object, RAM-এর একটা ভগ্নাংশ।

দোকান বড় হওয়ার সাথে সাথে দুটো পথ কেমন দেখায় দেখো। Naive line সরাসরি মারাত্মকভাবে উপরে উঠতে থাকে; flyweight line প্রায় নড়েই না, কারণ শুধু প্রতি-জার্সির ছোট অংশটা বাড়ে:

Figure 3: Jersey সংখ্যা বাড়ার সাথে memory saving-এর curve

100k জার্সিতে naive পদ্ধতি শুধু duplicated design bytes-এর জন্যই প্রায় 50 GB চায়; flyweight পদ্ধতি single-digit MB লাগায় — একটা ডিজাইন আর 100k ছোট record। পার্থক্য scale-এর সাথে চিরকালের জন্য বাড়তেই থাকে। এই বাড়তে থাকা পার্থক্যই এই pattern-এর পুরো কারণ।

কলেজ কর্নার — build করার আগে memory math করো: formula টা সহজ। Naive cost ≈ N × (unique bytes + shared bytes)। Flyweight cost ≈ N × (unique bytes + one pointer) + D × shared bytes, যেখানে N হলো object count আর D হলো distinct design-এর সংখ্যা। D ছোট হলে saving factor প্রায় (shared bytes) ÷ (unique bytes + 8)। আমাদের জার্সির জন্য: shared ≈ 500,000 bytes, unique ≈ 50 bytes, তাই saving প্রায় 10,000×। কিন্তু সংখ্যা উল্টে দাও — shared 50 bytes, unique 500 KB — আর pattern প্রায় কিছুই বাঁচায় না অথচ একটা factory, একটা cache, আর প্রতিটা call-এ indirection যোগ করে। কোনো flyweight code লেখার আগে এই দুই লাইনের calculation করো; এটা তোমাকে সৎভাবে বলবে pattern কাজে দেবে কিনা।

⚙️ এটা কীভাবে কাজ করে, ধাপে ধাপে

  1. Field-গুলো দুটো bucket-এ ভাগ করো। Heavy class-এর প্রতিটা field-এর জন্য জিজ্ঞেস করো: "এটা কি অনেক object-এ একই, আর constant?" হ্যাঁ হলে intrinsic। Object প্রতি আলাদা হলে extrinsic। (Design = intrinsic; নাম আর নম্বর = extrinsic।)
  2. শুধু intrinsic field নিয়ে flyweight class বানাও। Constructor-এ set করো, কখনো কোনো setter expose করো না। Flyweight immutable।
  3. Method-গুলো extrinsic state parameter হিসেবে নেওয়ার জন্য বদলাও। আগে this.fanName পড়তো এমন method এখন fanName argument নেবে: print(fanName, num)
  4. Cache সহ flyweight factory বানাও। এটা intrinsic state দিয়ে keyed একটা map রাখে ("bangladesh-odi-2026" flyweight)। Request-এ cache-এ থাকলে সেটাই ফেরত দেয়, না থাকলে একবার তৈরি করে cache করে।
  5. Client code থেকে সরাসরি flyweight constructor কখনো call করো না। শুধু factory করে — এটাই actual sharing নিশ্চিত করে।
  6. একটা ছোট context class বানাও যেটা extrinsic field আর shared flyweight-এর reference ধরে। অনেক context, কম flyweight।
Figure 4: Flyweight pattern-এর class structure

গল্প হিসেবে পড়ো: Shop (client) DesignFactory-র কাছে design চায়। Factory একটা shared, immutable JerseyDesign ফেরত দেয়। Shop তখন একটা ছোট্ট Jersey context তৈরি করে যেটায় শুধু ভক্তের নাম, নম্বর, আর সেই shared design-এর একটা pointer থাকে।

Factory-র cache-এর নিজস্ব একটা ছোট্ট life cycle আছে, আর এটা দেখার মতো — কারণ প্রতিটা flyweight bug এই diagram-এর কোথাও না কোথাও লুকিয়ে থাকে:

Figure 5: Factory cache-এর ভেতরে একটা design-এর জীবন

ব্যয়বহুল Creating state প্রতিটা distinct design-এর জন্য একবারই যায়, যত হাজার request আসুক না কেন। প্রতিটা পরের request সস্তা Cached loop নেয়। Log-এ যদি একই design-এর জন্য বারবার Creating দেখো, তার মানে কেউ factory bypass করছে।

Real-life code উদাহরণ

TypeScript-এ পুরো জার্সি শপ। শেষে counter লক্ষ্য করো — এটা প্রমাণ করে memory saving, কতটা heavy object আসলে তৈরি হয়েছে সেটা print করে।

// ----- The Flyweight: intrinsic (shared, immutable) state only -----
class JerseyDesign {
  // 'readonly' everywhere — a flyweight must never change.
  constructor(
    public readonly team: string,
    public readonly kit: string,          // "ODI" | "Test" | "T20"
    public readonly colorScheme: string,
    public readonly designData: string,   // imagine ~500 KB of image bytes
  ) {
    JerseyDesign.created++;               // count heavy objects made
    console.log(`  (heavy work) Designing ${team} ${kit} jersey...`);
  }
 
  static created = 0;
 
  // Extrinsic state (fanName, num) is PASSED IN, never stored here.
  print(fanName: string, num: number): void {
    console.log(
      `${this.team} ${this.kit} [${this.colorScheme}] -> ${fanName} #${num}`
    );
  }
}
 
// ----- The Flyweight Factory: one cache for the whole shop -----
class DesignFactory {
  private cache = new Map<string, JerseyDesign>();
 
  getDesign(team: string, kit: string, colors: string): JerseyDesign {
    const key = `${team}-${kit}`;
    if (!this.cache.has(key)) {
      // Heavy creation happens only ONCE per distinct design.
      this.cache.set(
        key,
        new JerseyDesign(team, kit, colors, "<500KB of image data>")
      );
    }
    return this.cache.get(key)!;          // everyone shares THIS object
  }
 
  get designCount(): number {
    return this.cache.size;
  }
}
 
// ----- The Context: tiny, unique, one per sold jersey -----
class Jersey {
  constructor(
    private fanName: string,              // extrinsic
    private num: number,                  // extrinsic
    private design: JerseyDesign,         // reference to SHARED flyweight
  ) {}
 
  print(): void {
    this.design.print(this.fanName, this.num);
  }
}
 
// ----- The client: Kiran's shop sells thousands of jerseys -----
const factory = new DesignFactory();
const sold: Jersey[] = [];
 
const fans = ["Aarav", "Diya", "Kabir", "Meera", "Ishaan", "Anaya"];
 
// Sell 10,000 jerseys across only 3 designs.
for (let i = 0; i < 10_000; i++) {
  const fan = fans[i % fans.length] + "-" + i;
  const kit = ["ODI", "Test", "T20"][i % 3];
  const design = factory.getDesign("India", kit, "Blue/Tricolour");
  sold.push(new Jersey(fan, (i % 99) + 1, design));
}
 
// Print a small sample.
sold[0].print();
sold[1].print();
sold[2].print();
 
// ----- The PROOF: shared objects vs total objects -----
console.log("-".repeat(50));
console.log(`Total jerseys sold (contexts) : ${sold.length}`);
console.log(`Heavy design objects created  : ${JerseyDesign.created}`);
console.log(`Designs in factory cache      : ${factory.designCount}`);
console.log(
  `Memory for designs: ~${JerseyDesign.created * 500} KB instead of ~${
    sold.length * 500
  } KB`
);

Output:

  (heavy work) Designing India ODI jersey...
  (heavy work) Designing India Test jersey...
  (heavy work) Designing India T20 jersey...
India ODI [Blue/Tricolour] -> Aarav-0 #1
India Test [Blue/Tricolour] -> Diya-1 #2
India T20 [Blue/Tricolour] -> Kabir-2 #3
--------------------------------------------------
Total jerseys sold (contexts) : 10000
Heavy design objects created  : 3
Designs in factory cache      : 3
Memory for designs: ~1500 KB instead of ~5000000 KB

Proof line-গুলো ধীরে ধীরে পড়ো। আমরা 10,000 জার্সি object তৈরি করেছি, কিন্তু ভারী "designing" message মাত্র 3 বার print হয়েছে। দশ হাজার context তিনটা flyweight শেয়ার করছে। Design memory প্রায় 5 GB থেকে প্রায় 1.5 MB-তে নেমেছে — 99.9%-এরও বেশি saving। দুটো সংখ্যায় পুরো pattern: অনেক context, কম flyweight।

এই সংখ্যাগুলোর পেছনে factory-র conversation — প্রথম request দাম দেয়, প্রতিটা পরের request বিনামূল্যে পায়:

Figure 6: প্রথম request তৈরি করে; পরের সব request শেয়ার করে

আর fix-এর পরে memory আসলে কোথায় থাকে। প্রায় পুরো bill এখন ছোট্ট per-jersey data — heavy design একটা sliver-এ পরিণত হয়েছে কারণ এটা একবারই আছে:

Figure 7: Flyweight apply করার পর 10,000 বিক্রির memory split

এই pie-টার সাথে naive world তুলনা করো, যেখানে "shared designs" slice হতো দশ হাজার গুণ বড় chart-এর 99.9%। একটা সুস্থ flyweight system সবসময় এরকম দেখায়: অনিবার্য unique data আধিপত্য করে, আর shared অংশ rounding error।

একই idea C#-এ

// Flyweight: immutable shared state. A record is perfect — immutable by default.
public record JerseyDesign(string Team, string Kit, string Colors)
{
    public static int Created = 0;
    public void Print(string fanName, int num) =>
        Console.WriteLine($"{Team} {Kit} -> {fanName} #{num}");
}
 
// Factory with a cache
public class DesignFactory
{
    private readonly Dictionary<string, JerseyDesign> _cache = new();
 
    public JerseyDesign Get(string team, string kit, string colors)
    {
        var key = $"{team}-{kit}";
        if (!_cache.TryGetValue(key, out var design))
        {
            design = new JerseyDesign(team, kit, colors);
            JerseyDesign.Created++;
            _cache[key] = design;
        }
        return design;
    }
}
 
// Context: tiny per-jersey data + shared reference
public class Jersey
{
    private readonly string _fan; private readonly int _num;
    private readonly JerseyDesign _design;
    public Jersey(string fan, int num, JerseyDesign design)
        => (_fan, _num, _design) = (fan, num, design);
    public void Print() => _design.Print(_fan, _num);
}
 
// Client
var factory = new DesignFactory();
var sold = new List<Jersey>();
for (int i = 0; i < 10_000; i++)
{
    var kit = new[] { "ODI", "Test", "T20" }[i % 3];
    sold.Add(new Jersey($"Fan-{i}", i % 99 + 1, factory.Get("India", kit, "Blue")));
}
Console.WriteLine($"Jerseys: {sold.Count}, Designs created: {JerseyDesign.Created}");
// Output: Jerseys: 10000, Designs created: 3

মজার তথ্য: C# নিজেই তোমার জন্য flyweight করে। তোমার code-এ একই string literal interned হয় — একবারই রাখা হয় আর শেয়ার করা হয় — আর ReferenceEquals("blue", "blue") true ফেরত দেয়।

📚 পাঠ্যপুস্তক মুদ্রণযন্ত্র, Python-এ

সুমাইয়ার কলেজ তাকে একটা নিখুঁত দ্বিতীয় উদাহরণ দিয়েছিল। ধরো একটা বোর্ড সপ্তম শ্রেণির বিজ্ঞান বইয়ের লক্ষ লক্ষ কপি মুদ্রণ করছে। প্রেস কি প্রতিটা printed copy-র জন্য পুরো 300 পাতার layout আলাদা করে রাখে? কখনো না। একটা master plate; লক্ষ লক্ষ কপি; প্রতিটা কপি শুধু serial number আর কোন স্কুলে যাবে সেটায় আলাদা। একই pattern, নতুন পোশাক:

class BookDesign:
    """Flyweight: the master plate. Intrinsic, immutable, made once."""
    created = 0
 
    def __init__(self, title, subject, page_layout):
        BookDesign.created += 1
        print(f"  (heavy work) Typesetting master plate: {title}")
        self._title = title            # no setters anywhere —
        self._subject = subject        # the plate is frozen
        self._page_layout = page_layout
 
    def print_copy(self, serial, school):
        # Extrinsic state arrives as parameters, never stored here.
        print(f"{self._title} | copy #{serial} -> {school}")
 
class PressFactory:
    """Factory: one cache of master plates for the whole press."""
    def __init__(self):
        self._plates = {}
 
    def get_design(self, title, subject, layout):
        if title not in self._plates:
            self._plates[title] = BookDesign(title, subject, layout)
        return self._plates[title]
 
class PrintedCopy:
    """Context: tiny, unique, one per physical book."""
    def __init__(self, serial, school, design):
        self.serial = serial           # extrinsic
        self.school = school           # extrinsic
        self.design = design           # pointer to the SHARED plate
 
    def print_label(self):
        self.design.print_copy(self.serial, self.school)
 
# The press run: 50,000 copies across 4 textbook designs.
press = PressFactory()
titles = ["Science 7", "Maths 7", "English 7", "History 7"]
copies = [
    PrintedCopy(i, f"School-{i % 900}",
                press.get_design(titles[i % 4], "Class 7", "<300-page layout>"))
    for i in range(50_000)
]
 
copies[0].print_label()
copies[1].print_label()
print("-" * 50)
print(f"Copies printed (contexts) : {len(copies)}")
print(f"Master plates made        : {BookDesign.created}")   # -> 4

পঞ্চাশ হাজার context, চারটা flyweight। Proof line-গুলো জার্সি শপের মতোই দুটো সংখ্যা — সবসময় তাই থাকে। আর হ্যাঁ, Python ছোট integer flyweight করে তোমার জন্যই: তোমার পুরো প্রোগ্রামে প্রতিটা -5 থেকে 256 একই shared object, যেটা তুমি a = 100; b = 100; print(a is b) দিয়ে check করতে পারো।

Real software-এ এটা কোথায় দেখা যায়

Flyweight language আর engine-এর ভেতরে লুকিয়ে থাকে যেগুলো তুমি ইতিমধ্যে ব্যবহার করো।

  • String interning। Java, C#, আর Python একই string literal-এর একটাই shared copy রাখে। একশো জায়গায় "team bangladesh" লেখো; runtime একবারই রাখে আর প্রতিটা variable একই immutable object-এ point করে। String-এর immutability ঠিক এটাকে নিরাপদ করে — flyweight rule কাজে।
  • Java-র Integer cache। Integer.valueOf(n) -128 থেকে 127 পর্যন্ত প্রতিটা মানের জন্য pre-made shared object ফেরত দেয়। Boolean.TRUE আর Boolean.FALSE পুরো JVM-এর জন্য দুটো flyweight। BigDecimal.ZERO, ONE, আর TEN একই কৌশল।
  • Game sprite আর mesh। ধরো একটা game-এ দশ লক্ষ গাছের forest। GPU memory-তে মাত্র কয়েকটা tree mesh আর texture রাখে আর দশ লক্ষ আলাদা position-এ draw করে। Position হলো extrinsic; mesh হলো intrinsic। Shooter game-এ bullet, particle, আর monster type-এও একই idea — Game Programming Patterns book-এ এটা নিয়ে একটা সুন্দর chapter আছে।
  • Text editor আর browser। একটা পাতায় লক্ষ লক্ষ character থাকে, কিন্তু "a" অক্ষরের glyph shape আর font data একবারই থাকে; screen-এ প্রতিটা character শুধু তার position আর shared glyph-এর reference রাখে।
  • Open-source reference। java-design-patterns flyweight example-এ একটা alchemist shop model করা হয়েছে যেখানে potion object বারবার তৈরি না করে cache থেকে share করা হয়।

🧭 কখন ব্যবহার করবে, কখন করবে না

Flyweight একটা বিশেষ tool। এই checklist-এ সৎ থাকো — সব ✅ শর্ত পূরণ হওয়া উচিত।

পরিস্থিতিFlyweight ব্যবহার করবে?
তোমাকে বিশাল সংখ্যক object (লক্ষ+) জীবিত রাখতে হবে আর RAM কষ্ট দিচ্ছে✅ হ্যাঁ
প্রতিটা object-এর data-র বড় একটা অংশ সব object-এ একই✅ হ্যাঁ — সেই অংশটাই flyweight হবে
Repeated data immutable, বা immutable করা সম্ভব✅ হ্যাঁ — শেয়ার করা নিরাপদ
Shared অংশ বের করে নেওয়ার পরেও object কার্যকর থাকে✅ হ্যাঁ
কয়েকশো object আছে❌ না — saving সামান্য, complexity বাস্তব
প্রতিটা object-এর data সত্যিই unique❌ না — শেয়ার করার কিছু নেই
"Shared" data কখনো কখনো প্রতিটা object-এর জন্য বদলাতে হবে❌ না — mutation sharing ভাঙে; split নিয়ে আবার ভাবো
CPU তোমার bottleneck, memory না❌ সাবধান — Flyweight RAM-এর বদলে CPU লাগায় (extrinsic data প্রতিটা call-এ pass/recompute হয়)

একটা map হিসেবে একই checklist। কোনো class লেখার আগে নিজের case plot করো:

Figure 8: এখানে Flyweight ব্যবহার করবে?

উপরে-ডানে — বিশাল count, mostly shared data — flyweight-এর জায়গা। নিচে-ডানে হলো সেই ফাঁদ যেখানে ছাত্ররা পড়ে: অনেক object কিন্তু meaningfully শেয়ার করার কিছু নেই, যেখানে pattern machinery যোগ করে আর কিছুই বাঁচায় না।

ছাত্ররা যে common mistake করে

⚠️

Mistake 1: Flyweight mutate করা। design.colorScheme = "red" এক লাইন চুপচাপ দোকানের প্রতিটা জার্সি নতুন রং করে দেয়। Intrinsic field readonly করো, কোনো setter দিও না, আর যখনই "চলো shared object একটু tweak করি" মনে আসে সেটাকে bug ভাবো।

Mistake 2: Factory bypass করা। Client code-এ সরাসরি new JerseyDesign(...) call করলে private copy তৈরি হয় আর চুপচাপ sharing নষ্ট হয়। সব creation factory-র cache-এর মাধ্যমে যেতে হবে — এটাই পুরো enforcement mechanism।

Mistake 3: Flyweight-এ extrinsic data রাখা। Flyweight-এ fanName রাখলে একই design চাওয়া দুজন ভক্তের জন্য হঠাৎ দুটো flyweight লাগে, আর sharing ভেঙে পড়ে। Object প্রতি যা আলাদা তা অবশ্যই বাইরে থাকতে হবে।

Mistake 4: সব জায়গায় apply করা। Flyweight code কঠিন করে পড়তে — নতুন পাঠকরা অবাক হয় কেন Jersey নিজের design field জানে না। এই cost তখনই দাও যখন measurement দেখায় memory সত্যিই সমস্যা।

Mistake 5: Cache growth ভুলে যাওয়া। Factory cache default-এ flyweight চিরকালের জন্য ধরে রাখে। Intrinsic key open-ended হলে (প্রতি user-এর জন্য একটা design!), "memory saver" memory leak হয়ে যায়। Distinct-design count ছোট ও bounded রাখো, বা eviction rule যোগ করো।

প্রতিবেশী pattern-এর সাথে তুলনা

ছাত্ররা Flyweight-কে Singleton, Prototype, আর object pool-এর সাথে মিলিয়ে ফেলে। Table-টা পরিষ্কার করে দেবে:

প্রশ্নFlyweightSingletonPrototypeObject Pool
কতটা instance?কম — প্রতিটা distinct intrinsic state-এর জন্য একটাঠিক একটাযত clone ততFixed set, ধার নেওয়া ও ফেরত দেওয়া
Mutable?কখনো না (immutable হতেই হবে)প্রায়ই mutableClone স্বাধীন আর mutableUse-এর মাঝে reset
লক্ষ্যশেয়ার করে memory বাঁচানোSingle global access pointTemplate-এর সস্তা কপিCreation/destroy cost এড়ানো
Sharing directionঅনেক context থেকে একটা objectসব code থেকে একটা objectনেই — কপিগুলো আলাদাসাময়িক exclusive use

আরও কিছু কার্যকর সম্পর্ক:

  • Flyweight factory নিজেই প্রায়ই Singleton — পুরো app-এর জন্য একটাই cache।
  • Composite tree leaf node-এর জন্য flyweight ব্যবহার করে: একটা document-এর হাজার হাজার একই character shared glyph object-এ point করে যখন tree structure রাখে।
  • Facade spectrum-এর উল্টো দিক: Facade একটা subsystem-এর সামনে একটা বড় helpful object বানায়; Flyweight তোমার data-র পেছনে অনেক ছোট shared object বানায়।

Revision box-এর আগে সব একটা গাছে:

Figure 9: পুরো Flyweight pattern একটা mind map-এ

Quick revision box

+--------------------------------------------------------------+
|                  FLYWEIGHT — QUICK REVISION                   |
+--------------------------------------------------------------+
| Idea      : Share the heavy common data; keep only the tiny  |
|             unique part per object. Saves huge RAM.          |
| Nickname  : Cache                                            |
| Analogy   : One cricket jersey DESIGN shared by all players; |
|             only name + number differ per jersey.            |
| Intrinsic : inside / identical / immutable -> in flyweight   |
| Extrinsic : external / exclusive -> in context or params     |
| Parts     : Flyweight, FlyweightFactory(cache), Context      |
| Iron rule : Flyweight is IMMUTABLE. Factory only creates.    |
| The proof : contexts = 10,000 ... flyweights = 3             |
| Memory    : N x shared  ->  N x pointer + D x shared         |
| Trade-off : Saves RAM, may spend CPU passing extrinsic data  |
| Real life : String interning, Java Integer cache (-128..127),|
|             game sprites/meshes drawn at many positions      |
+--------------------------------------------------------------+

Practice exercise 📝

  1. পাঠ্যপুস্তক মুদ্রণ, বাড়তি। উপরের Python press নাও আর মাঝপথে একটা পঞ্চম design যোগ করো। Proof line আবার print করো আর confirm করো plate count 4 থেকে 5 হয়েছে যখন copy count স্বাধীনভাবে বেড়েছে। তারপর College corner formula দিয়ে হিসাব করো — প্রতিটা plate 40 MB আর প্রতিটা copy record 60 byte হলে কতটা memory বাঁচে।
  2. দাবার ঘুঁটি। ধরো একটা chess server 10,000 game host করে। প্রতিটা game-এ 32টা পর্যন্ত ঘুঁটি, কিন্তু মাত্র 12 ধরনের ঘুঁটি (6 type × 2 রং)। একটা PieceType flyweight (type, colour, movementRules) আর একটা Piece context (square, gameId) বানাও। দেখাও 10,000 game মাত্র 12টা flyweight শেয়ার করছে।
  3. Challenge — bug ধরো। Exercise 1-এর code নাও আর ইচ্ছে করে flyweight-এ set_title() setter যোগ করো। একটা copy-র মাধ্যমে title বদলাও আর আলাদা স্কুলের দুটো copy print করো। দেখো দুটোই বদলে যায়। দুটো বাক্যে explain করো কেন immutability flyweight-এর সবচেয়ে গুরুত্বপূর্ণ নিয়ম।
  4. College challenge — break-even খুঁজে বের করো। Formula naive = N × (u + s) আর flyweight = N × (u + 8) + D × s ব্যবহার করে, s (shared bytes)-এর সেই মান বের করো যার নিচে তোমার N, u, আর D-এর জন্য pattern 10%-এরও কম বাঁচায়। এই একটা calculation তোমাকে কখনো এমন জায়গায় Flyweight apply করা থেকে বাঁচাবে যেখানে এটা pay করতে পারে না।

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

সহজ ভাষায় Flyweight pattern কী?
Flyweight memory বাঁচায়। প্রতিটা object-এ copy না রেখে, common আর কখনো না-বদলানো data একবার রেখে সবাইকে শেয়ার করায়। যেমন পুরো দলের জন্য একটাই ক্রিকেট জার্সি ডিজাইন — শুধু নাম আর নম্বর আলাদা।
Intrinsic state আর extrinsic state কী?
Intrinsic state হলো shared, constant data যেটা flyweight-এর ভেতরে একবারই রাখা হয় (জার্সির ডিজাইন)। Extrinsic state হলো unique, per-object data যেটা বাইরে রাখা হয় আর দরকারমতো পাঠানো হয় (প্রতিটা খেলোয়াড়ের নাম আর নম্বর)।
Flyweight কেন immutable হতে হবে?
কারণ একটা flyweight object হাজার হাজার context শেয়ার করে। কেউ যদি এটা বদলাতে পারতো, সবার data একসাথে চুপচাপ বদলে যেত। শেয়ার করা তখনই নিরাপদ যখন shared data কখনো বদলানো যায় না।
কখন Flyweight ব্যবহার করা উচিত না?
যখন object-এর সংখ্যা কম, বা যখন প্রতিটা object-এর data সত্যিই unique। শেয়ার করার মতো বড় কিছু না থাকলে pattern শুধু complexity বাড়ায়, memory বাঁচায় না।
Real software-এ Flyweight কোথায় দেখা যায়?
Java আর Python-এ String interning, Java-র Integer cache (-128 থেকে 127 মানের জন্য), আর game engine যেখানে একটাই sprite বা mesh লোড করে হাজার হাজার জায়গায় draw করা হয়।

আরো দেখো

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

Composite Pattern: বাক্সের ভেতরে বাক্স — একটা জিনিস আর অনেক জিনিসকে একই চোখে দেখো

কুরিয়ারের পার্সেলে বাক্সের ভেতরে বাক্সের উদাহরণ দিয়ে Composite pattern শেখো। একটা আইটেম আর পুরো গ্রুপকে একই interface দিয়ে ট্রিট করো, আর সহজ recursion দিয়ে মোট হিসাব বের করো।

আরও পড়ুন

Singleton Pattern: পুরো স্কুলে একজনই হেড স্যার

স্কুলের হেড স্যারের গল্প দিয়ে Singleton design pattern বোঝো — সহজ TypeScript ও C# কোড, thread safety, আর কেন অনেক সিনিয়র ডেভেলপার এটাকে anti-pattern বলেন।

আরও পড়ুন

Facade Pattern: একটা ফোন কলেই পুরো জটিল ট্রিপ বুক

ট্রাভেল এজেন্টের গল্প দিয়ে Facade pattern শেখো। অনেকগুলো জটিল subsystem-কে একটা সহজ method-এর পেছনে লুকিয়ে রাখো, যাতে client code ছোট আর পরিষ্কার থাকে।

আরও পড়ুন

Prototype Pattern: জিরো থেকে না বানিয়ে ফটোকপি করো

বিয়ের কার্ডের দোকানের গল্প দিয়ে Prototype design pattern শেখো — TypeScript আর Python-এর সহজ উদাহরণ, আর shallow vs deep copy-এর পরিষ্কার demo সহ।

আরও পড়ুন