سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Flyweight Pattern در PHP چیست؟ آموزش الگوی Flyweight با مثال عملی

Flyweight Pattern در PHP چیست؟ آموزش الگوی Flyweight با مثال عملی

ممدیر کل سیستم ۲۵ شهریور ۱۴۰۵ ۲۰ دقیقه مطالعه ۲ بازدید
Flyweight Pattern در PHP چیست؟ آموزش الگوی Flyweight با مثال عملی

در بعضی پروژه‌های PHP لازم است تعداد بسیار زیادی Object مشابه ایجاد و نگهداری شود. برای مثال:

  • نمایش هزاران درخت در یک نقشه
  • مدیریت تعداد زیادی ذره در یک بازی
  • نگهداری اطلاعات محصولات مشابه
  • مدیریت کاربران یا موجودیت‌هایی با ویژگی‌های تکراری
  • نمایش آیتم‌های زیاد در یک گزارش
  • پردازش داده‌هایی که بخش زیادی از اطلاعات آن‌ها مشترک است

اگر برای هر Object تمام اطلاعات را به‌صورت جداگانه ذخیره کنیم، ممکن است بخش زیادی از حافظه صرف نگهداری داده‌های تکراری شود.

برای مثال، فرض کنید ۱۰ هزار محصول داریم که در همه آن‌ها اطلاعات برند، رنگ و تصویر یکسان است، اما شناسه، قیمت و تعداد موجودی متفاوت است. ذخیره‌کردن برند، رنگ و تصویر در هر ۱۰ هزار Object می‌تواند باعث تکرار غیرضروری داده‌ها شود.

در چنین شرایطی می‌توان از Flyweight Pattern استفاده کرد.

Flyweight یکی از الگوهای ساختاری یا Structural Design Patterns است که با اشتراک‌گذاری بخش‌های مشترک State میان چندین Object، تعداد داده‌های تکراری را کاهش می‌دهد. هدف اصلی این الگو کاهش مصرف حافظه در شرایطی است که تعداد زیادی Object مشابه هم‌زمان در حافظه وجود دارند. <Cite refs={["turn0search0","turn0search2"]} />

Flyweight Pattern چیست؟

Flyweight Pattern الگویی است که بخش مشترک اطلاعات چند Object را در یک Object قابل اشتراک ذخیره می‌کند و اطلاعات اختصاصی هر Object را خارج از آن نگه می‌دارد.

در این الگو، State مربوط به Object معمولاً به دو بخش تقسیم می‌شود:

Intrinsic State

اطلاعات مشترک و معمولاً ثابت که میان چند Object استفاده می‌شود.

مثال:

  • نام برند
  • رنگ
  • تصویر
  • نوع محصول
  • نام فونت
  • نوع درخت
  • اطلاعات ثابت یک قالب

Extrinsic State

اطلاعات اختصاصی و وابسته به هر نمونه که میان Objectها متفاوت است.

مثال:

  • شناسه محصول
  • قیمت
  • موقعیت جغرافیایی
  • تعداد موجودی
  • نام کاربر
  • مختصات یک Object
  • وضعیت لحظه‌ای یک موجودیت

Flyweight اطلاعات مشترک را نگهداری می‌کند و اطلاعات اختصاصی را هنگام اجرای متد دریافت می‌کند. <Cite refs={["turn0search2","turn0search4"]} />

ساختار کلی:


Client

   │

   ▼

Flyweight Factory

   │

   ├── Shared Flyweight A

   ├── Shared Flyweight B

   └── Shared Flyweight C



Context Object

   ├── Extrinsic State

   └── Reference to Flyweight

چرا به Flyweight نیاز داریم؟

فرض کنید کلاس زیر را داریم:


<?php



declare(strict_types=1);



final class Product

{

    public function __construct(

        public readonly int $id,

        public readonly string $brand,

        public readonly string $color,

        public readonly string $image,

        public readonly float $price

    ) {

    }

}

حالا اگر هزاران محصول از یک برند و رنگ یکسان داشته باشیم، اطلاعاتی مانند brand، color و image در تعداد زیادی Object تکرار می‌شوند.

نسخه ساده استفاده:


$products = [];



for ($i = 1; $i <= 10000; $i++) {

    $products[] = new Product(

        id: $i,

        brand: 'Example Brand',

        color: 'Black',

        image: 'products/black-laptop.jpg',

        price: 50000000

    );

}

در این مدل، هر Object اطلاعات مشترک خودش را نگهداری می‌کند.

Flyweight پیشنهاد می‌کند اطلاعات مشترک را جدا کنیم:


Product Context

    id

    price

    sharedProductData



Shared Product Data

    brand

    color

    image

در این حالت، تعداد زیادی Product می‌توانند به یک Object مشترک اشاره کنند.

اجزای Flyweight Pattern

Flyweight Pattern معمولاً از اجزای زیر تشکیل می‌شود.

۱. Flyweight

Objectی که State مشترک را نگهداری می‌کند.

۲. Concrete Flyweight

پیاده‌سازی واقعی Flyweight که اطلاعات مشترک را در خود دارد.

۳. Flyweight Factory

کلاسی که Flyweightها را ایجاد و مدیریت می‌کند. Factory ابتدا بررسی می‌کند آیا Flyweight موردنظر قبلاً ساخته شده است یا خیر. اگر وجود داشته باشد، همان Object را برمی‌گرداند؛ در غیر این صورت، Object جدید ایجاد می‌کند. <Cite refs={["turn0search0","turn0search2"]} />

۴. Context

Objectی که State اختصاصی هر نمونه را نگهداری می‌کند و به Flyweight مشترک ارجاع می‌دهد.

۵. Client

بخشی از برنامه که از Factory و Context استفاده می‌کند.

مثال ساده Flyweight در PHP

فرض کنید می‌خواهیم محصولات یک فروشگاه را مدیریت کنیم. اطلاعاتی مانند برند، رنگ و تصویر میان محصولات زیادی مشترک است؛ اما شناسه، قیمت و موجودی هر محصول اختصاصی است.

تعریف Flyweight


<?php



declare(strict_types=1);



final class ProductVariation

{

    public function __construct(

        public readonly string $brand,

        public readonly string $color,

        public readonly string $image

    ) {

    }



    public function describe(): string

    {

        return sprintf(

            '%s - %s - %s',

            $this->brand,

            $this->color,

            $this->image

        );

    }

}

کلاس ProductVariation فقط اطلاعات مشترک را نگهداری می‌کند.

تعریف Context


<?php



declare(strict_types=1);



final class Product

{

    public function __construct(

        public readonly int $id,

        public readonly float $price,

        public readonly int $stock,

        private readonly ProductVariation $variation

    ) {

    }



    public function describe(): string

    {

        return sprintf(

            'Product #%d | Price: %.2f | Stock: %d | Variation: %s',

            $this->id,

            $this->price,

            $this->stock,

            $this->variation->describe()

        );

    }

}

در این کلاس، اطلاعات اختصاصی مانند شناسه، قیمت و موجودی ذخیره می‌شود. اطلاعات مشترک در Object مربوط به ProductVariation قرار دارد.

ساخت Flyweight Factory


<?php



declare(strict_types=1);



final class ProductVariationFactory

{

    /**

     * @var array<string, ProductVariation>

     */

    private array $variations = [];



    public function get(

        string $brand,

        string $color,

        string $image

    ): ProductVariation {

        $key = $this->createKey(

            brand: $brand,

            color: $color,

            image: $image

        );



        if (!isset($this->variations[$key])) {

            $this->variations[$key] = new ProductVariation(

                brand: $brand,

                color: $color,

                image: $image

            );

        }



        return $this->variations[$key];

    }



    private function createKey(

        string $brand,

        string $color,

        string $image

    ): string {

        return implode('|', [

            $brand,

            $color,

            $image,

        ]);

    }



    public function count(): int

    {

        return count($this->variations);

    }

}

Factory بر اساس اطلاعات مشترک یک Key می‌سازد. اگر همان Key قبلاً در آرایه وجود داشته باشد، Object قبلی را برمی‌گرداند.

استفاده از Factory


<?php



$factory = new ProductVariationFactory();



$variationA = $factory->get(

    brand: 'Example Brand',

    color: 'Black',

    image: 'products/black-laptop.jpg'

);



$variationB = $factory->get(

    brand: 'Example Brand',

    color: 'Black',

    image: 'products/black-laptop.jpg'

);



var_dump($variationA === $variationB);

خروجی:


bool(true)

هر دو متغیر به یک Object مشترک اشاره می‌کنند.

ساخت محصولات


<?php



$productA = new Product(

    id: 1,

    price: 50000000,

    stock: 10,

    variation: $variationA

);



$productB = new Product(

    id: 2,

    price: 52000000,

    stock: 4,

    variation: $variationB

);



echo $productA->describe() . PHP_EOL;

echo $productB->describe() . PHP_EOL;



echo "Shared variations: {$factory->count()}" . PHP_EOL;

خروجی نمونه:


Product #1 | Price: 50000000.00 | Stock: 10 | Variation: Example Brand - Black - products/black-laptop.jpg

Product #2 | Price: 52000000.00 | Stock: 4 | Variation: Example Brand - Black - products/black-laptop.jpg

Shared variations: 1

در این مثال، دو محصول متفاوت وجود دارد، اما اطلاعات مشترک آن‌ها فقط یک بار در ProductVariation ذخیره شده است.

اهمیت Immutable بودن Flyweight

از آنجا که یک Flyweight ممکن است میان چندین Context استفاده شود، نباید State مشترک آن به‌صورت ناخواسته تغییر کند.

به همین دلیل بهتر است اطلاعات Flyweight:

  • در Constructor مقداردهی شوند.
  • دارای Propertyهای readonly باشند.
  • Setter عمومی نداشته باشند.
  • توسط چند بخش مختلف قابل تغییر نباشند.
  • اطلاعات اختصاصی هر Context را در خود نگهداری نکنند.

نمونه مناسب:


<?php



declare(strict_types=1);



final class TextStyle

{

    public function __construct(

        public readonly string $fontFamily,

        public readonly int $fontSize,

        public readonly string $color

    ) {

    }

}

اگر TextStyle میان هزاران متن استفاده شود، تغییر اطلاعات آن می‌تواند روی تمام متن‌هایی که به آن ارجاع دارند اثر بگذارد. بنابراین Immutable بودن Flyweight یک اصل مهم در طراحی آن است. <Cite refs={["turn0search2","turn0search4"]} />

مثال واقعی‌تر؛ مدیریت درخت‌ها در PHP

فرض کنید برنامه‌ای داریم که باید هزاران درخت را روی یک نقشه نمایش دهد.

اطلاعاتی مانند نوع درخت، رنگ برگ‌ها و تصویر آن میان بسیاری از درخت‌ها مشترک است. اما موقعیت هر درخت متفاوت است.

Flyweight مربوط به نوع درخت


<?php



declare(strict_types=1);



final class TreeType

{

    public function __construct(

        public readonly string $name,

        public readonly string $color,

        public readonly string $texture

    ) {

    }



    public function draw(int $x, int $y): string

    {

        return sprintf(

            '%s at (%d, %d), color: %s, texture: %s',

            $this->name,

            $x,

            $y,

            $this->color,

            $this->texture

        );

    }

}

Context مربوط به هر درخت


<?php



declare(strict_types=1);



final class Tree

{

    public function __construct(

        private readonly int $x,

        private readonly int $y,

        private readonly TreeType $type

    ) {

    }



    public function draw(): string

    {

        return $this->type->draw(

            x: $this->x,

            y: $this->y

        );

    }

}

Factory درخت‌ها


<?php



declare(strict_types=1);



final class TreeTypeFactory

{

    /**

     * @var array<string, TreeType>

     */

    private array $types = [];



    public function get(

        string $name,

        string $color,

        string $texture

    ): TreeType {

        $key = "{$name}|{$color}|{$texture}";



        if (!isset($this->types[$key])) {

            $this->types[$key] = new TreeType(

                name: $name,

                color: $color,

                texture: $texture

            );

        }



        return $this->types[$key];

    }

}

استفاده


<?php



$factory = new TreeTypeFactory();



$oak = $factory->get(

    name: 'Oak',

    color: 'Green',

    texture: 'oak.png'

);



$treeA = new Tree(

    x: 10,

    y: 20,

    type: $oak

);



$treeB = new Tree(

    x: 50,

    y: 80,

    type: $oak

);



echo $treeA->draw() . PHP_EOL;

echo $treeB->draw() . PHP_EOL;

در اینجا دو درخت در موقعیت‌های مختلف وجود دارند، اما هر دو از یک TreeType مشترک استفاده می‌کنند.

این مثال نشان می‌دهد که Flyweight چگونه اطلاعات مشترک را از اطلاعات وابسته به هر نمونه جدا می‌کند. <Cite refs={["turn0search2","turn0search3"]} />

Intrinsic State و Extrinsic State در عمل

برای تشخیص اینکه چه داده‌ای باید در Flyweight قرار گیرد، باید هر Property را بررسی کنیم.

Propertyنوع Stateدلیل
نام برندIntrinsicمیان چند محصول مشترک است
رنگ محصولIntrinsicدر محصولات مشابه تکرار می‌شود
تصویر محصولIntrinsicداده‌ای مشترک و معمولاً حجیم است
شناسه محصولExtrinsicبرای هر محصول متفاوت است
قیمتExtrinsicممکن است برای هر محصول متفاوت باشد
موجودیExtrinsicوابسته به همان نمونه است
موقعیت روی نقشهExtrinsicبرای هر Object متفاوت است
نوع فونتIntrinsicمیان متن‌های متعدد قابل اشتراک است
مختصات متنExtrinsicبرای هر متن تغییر می‌کند

اگر داده‌ای در هر نمونه متفاوت است یا در طول زمان تغییر می‌کند، معمولاً نباید در Flyweight مشترک قرار بگیرد.

Flyweight Factory چه کاری انجام می‌دهد؟

Factory مسئول مدیریت مجموعه Flyweightهای ایجادشده است.

فرایند معمول آن به شکل زیر است:

  1. Client اطلاعات مشترک موردنیاز را ارسال می‌کند.
  2. Factory یک Key از آن اطلاعات می‌سازد.
  3. Factory بررسی می‌کند آیا Flyweight مربوط به آن Key وجود دارد یا خیر.
  4. اگر وجود داشته باشد، همان Object بازگردانده می‌شود.
  5. اگر وجود نداشته باشد، Factory یک Flyweight جدید می‌سازد.
  6. Object جدید در Pool ذخیره می‌شود.
  7. Flyweight به Client تحویل داده می‌شود.

این روش باعث می‌شود Client مستقیماً مسئول ایجاد و مدیریت Objectهای مشترک نباشد. <Cite refs={["turn0search0","turn0search2"]} />

استفاده احتمالی از Flyweight در Laravel

Flyweight در Laravel در همه پروژه‌ها ضروری نیست، اما در بعضی سناریوها می‌تواند مفید باشد.

۱. اطلاعات مشترک محصولات

اگر محصولات زیادی اطلاعات مشترکی مانند برند، تصویر، رنگ یا مشخصات ثابت داشته باشند، می‌توان اطلاعات مشترک را در Objectهای قابل اشتراک قرار داد.

۲. قالب‌های پیامک

فرض کنید تعداد زیادی پیامک با قالب‌های یکسان ارسال می‌شود، اما شماره موبایل و پارامترهای هر پیام متفاوت است.

اطلاعات قالب می‌تواند Shared State باشد:


<?php



declare(strict_types=1);



final class SmsTemplate

{

    public function __construct(

        public readonly string $code,

        public readonly string $body

    ) {

    }



    public function render(array $parameters): string

    {

        return str_replace(

            array_keys($parameters),

            array_values($parameters),

            $this->body

        );

    }

}

شماره موبایل و پارامترهای پیام در Context قرار می‌گیرند.

۳. اطلاعات ثابت سرویس‌های n8n

در یک سیستم مدیریت سرویس، ممکن است چندین سرویس از یک نوع Image، تنظیمات عمومی یا مشخصات ثابت استفاده کنند، اما شناسه، وضعیت، کاربر و زمان‌بندی هر سرویس متفاوت باشد.

اطلاعات ثابت می‌تواند در یک Object مشترک قرار گیرد؛ البته فقط در صورتی که تعداد Objectها زیاد باشد و اشتراک‌گذاری واقعاً ارزش داشته باشد.

۴. داده‌های ثابت API

اگر چندین درخواست API از یک Provider، Endpoint یا تنظیمات مشترک استفاده کنند، می‌توان بخشی از تنظیمات ثابت را در یک Object مشترک نگهداری کرد.

با این حال، در بسیاری از پروژه‌های Laravel، استفاده از Service Container و Cache معمولاً ساده‌تر و مناسب‌تر از پیاده‌سازی کامل Flyweight است.

تفاوت Flyweight و Singleton

Flyweight و Singleton هر دو ممکن است از یک Object مشترک استفاده کنند، اما هدفشان یکسان نیست.

Singleton

هدف Singleton تضمین وجود یک نمونه از یک کلاس در یک محدوده مشخص است.

Flyweight

هدف Flyweight اشتراک‌گذاری State مشترک میان تعداد زیادی Object مشابه است.

تفاوت‌های مهم:

SingletonFlyweight
معمولاً یک نمونه داردمی‌تواند چند نمونه با Stateهای مختلف داشته باشد
هدف آن کنترل تعداد Instance استهدف آن کاهش داده‌های تکراری است
ممکن است Mutable باشدبهتر است Immutable باشد
معمولاً دسترسی سراسری ایجاد می‌کندمعمولاً از Factory برای مدیریت استفاده می‌کند

Flyweight فقط به معنی «یک Object برای همه‌چیز» نیست. ممکن است چند Flyweight مختلف وجود داشته باشد؛ برای مثال یک Flyweight برای رنگ قرمز و یک Flyweight برای رنگ آبی. <Cite refs={["turn0search2"]} />

تفاوت Flyweight و Prototype

Prototype برای ساخت Object جدید از طریق کپی‌کردن یک Object موجود استفاده می‌شود.

Flyweight برعکس، تلاش می‌کند به جای ساخت Objectهای مشابه، همان Object مشترک را دوباره استفاده کند.

PrototypeFlyweight
هدف آن کپی‌کردن Object استهدف آن اشتراک‌گذاری Object است
معمولاً Instance جدید ایجاد می‌کندمعمولاً Instance موجود را برمی‌گرداند
برای ساخت نمونه‌های مشابه کاربرد داردبرای کاهش تکرار State کاربرد دارد

تفاوت Flyweight و Object Pool

Object Pool مجموعه‌ای از Objectهای آماده را مدیریت می‌کند و آن‌ها را برای استفاده مجدد در اختیار Client قرار می‌دهد.

Flyweight بیشتر روی اشتراک‌گذاری State مشترک تمرکز دارد.

برای مثال:

  • Connection Pool برای استفاده مجدد از Connectionها کاربرد دارد.
  • Thread Pool برای استفاده مجدد از Threadها کاربرد دارد.
  • Flyweight برای اشتراک‌گذاری داده‌های مشترک میان Objectها کاربرد دارد.

این دو الگو می‌توانند در یک سیستم کنار هم استفاده شوند، اما هدف طراحی آن‌ها متفاوت است.

مزایای Flyweight Pattern

۱. کاهش مصرف حافظه

اگر تعداد زیادی Object اطلاعات تکراری داشته باشند، اشتراک‌گذاری آن اطلاعات می‌تواند مصرف حافظه را کاهش دهد.

۲. کاهش داده‌های تکراری

اطلاعات مشترک فقط یک بار نگهداری می‌شود.

۳. مدیریت بهتر Objectهای مشابه

Factory ایجاد و استفاده مجدد از Flyweightها را مدیریت می‌کند.

۴. جداسازی State مشترک و اختصاصی

تقسیم Intrinsic و Extrinsic State باعث می‌شود ساختار داده‌ها واضح‌تر شود.

۵. امکان استفاده مجدد

یک Flyweight می‌تواند در Contextهای مختلف استفاده شود.

معایب Flyweight Pattern

۱. افزایش پیچیدگی طراحی

به جای یک کلاس ساده، ممکن است به Flyweight، Context و Factory نیاز داشته باشیم.

۲. افزایش وابستگی به Factory

Client باید برای دریافت Object مشترک از Factory استفاده کند.

۳. احتمال افزایش پردازش

ساخت Key، جست‌وجو در Pool و مدیریت State ممکن است هزینه پردازشی ایجاد کند.

۴. دشواری در مدیریت State

اگر State مشترک اشتباهاً تغییر کند، تمام Objectهایی که از آن استفاده می‌کنند تحت تأثیر قرار می‌گیرند.

۵. کاربرد محدود در PHP

در بسیاری از برنامه‌های PHP، تمام Objectهای برنامه به‌صورت هم‌زمان در حافظه نگهداری نمی‌شوند. بنابراین Flyweight همیشه بهترین راه‌حل نیست و باید فقط در شرایطی استفاده شود که تعداد زیادی Object مشابه واقعاً در حافظه وجود داشته باشد. <Cite refs={["turn0search0","turn0search2"]} />

آیا Flyweight در PHP همیشه باعث بهبود عملکرد می‌شود؟

خیر.

Flyweight یک ابزار بهینه‌سازی است و نباید بدون اندازه‌گیری استفاده شود.

قبل از پیاده‌سازی آن بهتر است موارد زیر بررسی شوند:

  • تعداد Objectهای هم‌زمان در حافظه چقدر است؟
  • چه مقدار State میان Objectها تکرار می‌شود؟
  • آیا مصرف حافظه واقعاً مشکل ایجاد کرده است؟
  • آیا می‌توان داده‌ها را با آرایه یا ساختار ساده‌تری نگهداری کرد؟
  • هزینه ساخت و جست‌وجوی Flyweight چقدر است؟
  • آیا پیچیدگی اضافه ارزش کاهش حافظه را دارد؟

اگر تعداد Objectها کم باشد، استفاده از Flyweight ممکن است فقط کد را پیچیده‌تر کند.

تست Flyweight با PHPUnit

برای تست Flyweight می‌توان بررسی کرد که Factory برای State مشترک یک Object یکسان برمی‌گرداند.


<?php



declare(strict_types=1);



use PHPUnit\Framework\TestCase;



final class ProductVariationFactoryTest extends TestCase

{

    public function test_it_reuses_same_variation(): void

    {

        $factory = new ProductVariationFactory();



        $first = $factory->get(

            brand: 'Example Brand',

            color: 'Black',

            image: 'products/black-laptop.jpg'

        );



        $second = $factory->get(

            brand: 'Example Brand',

            color: 'Black',

            image: 'products/black-laptop.jpg'

        );



        self::assertSame($first, $second);

        self::assertSame(1, $factory->count());

    }



    public function test_it_creates_different_variations_for_different_states(): void

    {

        $factory = new ProductVariationFactory();



        $black = $factory->get(

            brand: 'Example Brand',

            color: 'Black',

            image: 'products/black-laptop.jpg'

        );



        $white = $factory->get(

            brand: 'Example Brand',

            color: 'White',

            image: 'products/white-laptop.jpg'

        );



        self::assertNotSame($black, $white);

        self::assertSame(2, $factory->count());

    }

}

در تست اول، دو درخواست با State یکسان باید همان Object را برگردانند. در تست دوم، چون State متفاوت است، Factory باید دو Flyweight جداگانه ایجاد کند.

اشتباهات رایج در Flyweight Pattern

اشتباه اول: استفاده از Flyweight بدون مشکل حافظه

اگر پروژه شما تعداد کمی Object دارد، Flyweight احتمالاً ضروری نیست.

اشتباه دوم: قرار دادن State اختصاصی در Flyweight

قیمت، شناسه، موجودی و موقعیت هر نمونه نباید در Object مشترک قرار بگیرد.

اشتباه سوم: قابل تغییر بودن Flyweight

تغییر State مشترک می‌تواند روی چندین Context اثر بگذارد.

اشتباه چهارم: ساخت Key نامطمئن

اگر Key به‌درستی ساخته نشود، ممکن است دو State متفاوت یک Flyweight مشترک دریافت کنند.

اشتباه پنجم: نادیده‌گرفتن نوع داده

برای ساخت Key بهتر است نوع داده‌ها مشخص و قابل پیش‌بینی باشند. استفاده از رشته‌سازی ساده برای داده‌هایی که شامل جداکننده هستند ممکن است باعث تداخل Key شود.

اشتباه ششم: نگهداری بی‌نهایت Flyweight در حافظه

Factory نیز می‌تواند به مرور زمان تعداد زیادی Object در خود نگهداری کند. در سیستم‌های طولانی‌مدت باید ظرفیت، Expiration یا روش پاک‌سازی Pool بررسی شود.

اشتباه هفتم: استفاده از Flyweight به جای Cache

Cache برای ذخیره نتیجه محاسبات یا درخواست‌هاست؛ Flyweight برای اشتراک‌گذاری State میان Objectها طراحی شده است. این دو مفهوم ممکن است شبیه به نظر برسند، اما یکسان نیستند.

نمونه کامل Flyweight Pattern در PHP


<?php



declare(strict_types=1);



final class ProductVariation

{

    public function __construct(

        public readonly string $brand,

        public readonly string $color,

        public readonly string $image

    ) {

    }

}



final class Product

{

    public function __construct(

        public readonly int $id,

        public readonly float $price,

        public readonly ProductVariation $variation

    ) {

    }



    public function summary(): string

    {

        return sprintf(

            '#%d | %.2f | %s | %s',

            $this->id,

            $this->price,

            $this->variation->brand,

            $this->variation->color

        );

    }

}



final class ProductVariationFactory

{

    /**

     * @var array<string, ProductVariation>

     */

    private array $pool = [];



    public function get(

        string $brand,

        string $color,

        string $image

    ): ProductVariation {

        $key = hash(

            'sha256',

            json_encode([

                'brand' => $brand,

                'color' => $color,

                'image' => $image,

            ], JSON_THROW_ON_ERROR)

        );



        if (!isset($this->pool[$key])) {

            $this->pool[$key] = new ProductVariation(

                brand: $brand,

                color: $color,

                image: $image

            );

        }



        return $this->pool[$key];

    }



    public function count(): int

    {

        return count($this->pool);

    }

}



$factory = new ProductVariationFactory();



$products = [];



for ($i = 1; $i <= 5; $i++) {

    $products[] = new Product(

        id: $i,

        price: 50000000 + $i * 100000,

        variation: $factory->get(

            brand: 'Example Brand',

            color: 'Black',

            image: 'products/black-laptop.jpg'

        )

    );

}



foreach ($products as $product) {

    echo $product->summary() . PHP_EOL;

}



echo "Shared variations: {$factory->count()}" . PHP_EOL;

خروجی نمونه:


#1 | 50100000.00 | Example Brand | Black

#2 | 50200000.00 | Example Brand | Black

#3 | 50300000.00 | Example Brand | Black

#4 | 50400000.00 | Example Brand | Black

#5 | 50500000.00 | Example Brand | Black

Shared variations: 1

در این نمونه، پنج محصول داریم، اما فقط یک ProductVariation ساخته شده است.

جمع‌بندی

Flyweight Pattern در PHP یک الگوی ساختاری برای کاهش مصرف حافظه از طریق اشتراک‌گذاری State مشترک میان چندین Object است.

در این الگو:

  • Intrinsic State در Flyweight قرار می‌گیرد.
  • Extrinsic State در Context نگهداری می‌شود.
  • Flyweight Factory Objectهای مشترک را مدیریت می‌کند.
  • Flyweight بهتر است Immutable باشد.
  • Client به جای ساخت مستقیم Object، آن را از Factory دریافت می‌کند.

Flyweight برای سیستم‌هایی مناسب است که تعداد زیادی Object مشابه را به‌صورت هم‌زمان در حافظه نگهداری می‌کنند. با این حال، در پروژه‌های معمول PHP و Laravel نباید بدون بررسی مصرف حافظه از آن استفاده کرد. ابتدا باید مشخص شود که مشکل واقعی حافظه وجود دارد و اشتراک‌گذاری State می‌تواند آن را برطرف کند.

سوالات متداول

Flyweight Pattern در PHP چیست؟

Flyweight Pattern الگویی ساختاری است که اطلاعات مشترک میان چندین Object را در یک Object قابل اشتراک نگهداری می‌کند تا از تکرار داده‌ها و مصرف غیرضروری حافظه جلوگیری شود.

Intrinsic State چیست؟

Intrinsic State اطلاعات مشترک و معمولاً ثابت است که میان چندین Object استفاده می‌شود؛ مانند رنگ، تصویر، نوع محصول یا نام برند.

Extrinsic State چیست؟

Extrinsic State اطلاعات اختصاصی هر نمونه است؛ مانند شناسه، قیمت، موجودی، مختصات یا وضعیت لحظه‌ای.

Flyweight Factory چه کاری انجام می‌دهد؟

Factory بررسی می‌کند Flyweight موردنظر قبلاً ساخته شده است یا خیر. اگر ساخته شده باشد همان Object را برمی‌گرداند و در غیر این صورت Object جدید ایجاد می‌کند.

آیا Flyweight با Singleton یکسان است؟

خیر. Singleton معمولاً برای داشتن یک نمونه از یک کلاس استفاده می‌شود، اما Flyweight می‌تواند چند نمونه با Stateهای مختلف داشته باشد و هدفش اشتراک‌گذاری داده‌های مشترک است.

آیا Flyweight در Laravel کاربرد دارد؟

بله، اما فقط در شرایط خاص. برای داده‌های مشترک محصولات، قالب‌های پیام، تنظیمات ثابت یا تعداد زیادی Object مشابه می‌توان از آن استفاده کرد؛ البته در بسیاری از موارد Cache، Service Container یا ساختارهای ساده‌تر انتخاب مناسب‌تری هستند.

آیا Flyweight همیشه مصرف حافظه را کاهش می‌دهد؟

خیر. میزان کاهش حافظه به تعداد Objectها، مقدار داده‌های تکراری و نحوه پیاده‌سازی بستگی دارد. اگر Objectهای کمی وجود داشته باشند، Flyweight ممکن است فقط پیچیدگی بیشتری ایجاد کند.

مهم‌ترین نکته در طراحی Flyweight چیست؟

باید State مشترک و اختصاصی به‌درستی از هم جدا شوند. همچنین Flyweight بهتر است Immutable باشد تا تغییر آن روی چندین Context اثر ناخواسته نگذارد. <Cite refs={["turn0search2","turn0search4"]} />

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه