وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

الگوهای طراحی سازه در پایتون

0 5
زمان لازم برای مطالعه: 14 دقیقه


معرفی

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

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

الگوهای طراحی پوشش داده شده در این بخش عبارتند از:

الگوی طراحی آداپتور

در دنیای واقعی، ممکن است از یک آداپتور برای اتصال شارژرها به سوکت های مختلف هنگام سفر به کشورهای دیگر یا مدل های مختلف تلفن استفاده کنید. می توانید از آنها برای اتصال یک مانیتور VGA قدیمی به یک سوکت HDMI استفاده کنید روی کامپیوتر جدید شما

الگوی طراحی نام خود را به این دلیل گرفت که هدف آن یکسان است – تطبیق یک ورودی با یک خروجی از پیش تعیین شده متفاوت.

بگو داری کار میکنی روی یک نرم افزار نمایش تصویر، و تا کنون مشتریان شما فقط می خواستند تصاویر شطرنجی را نمایش دهند. شما یک پیاده سازی کامل برای طراحی دارید، مثلاً a .png فایل روی صفحه نمایش

برای سادگی، عملکرد به این صورت است:

from abc import ABC, abstractmethod

class PngInterface(ABC):
    @abstractmethod
    def draw(self):
        pass

class PngImage(PngInterface):
    def __init__(self, png):
        self.png = png
        self.format = "raster"
        
    def draw(self):
        print("drawing " + self.get_image())
            
    def get_image(self):
        return "png"

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

همانطور که به نظر می رسد، یک کتابخانه برای کار با گرافیک های برداری وجود دارد که می توانید به جای اجرای تمام آن عملکرد کاملاً جدید خودتان، از آن استفاده کنید. با این حال، کلاس ها با رابط کاربری شما مطابقت ندارند (آنها این را پیاده سازی نمی کنند draw() روش):

class SvgImage:
    def __init__(self, svg):
        self.svg = svg
        self.format = "vector"
        
    def get_image(self):
        return "svg"

شما نمی خواهید قبل از انجام کاری با هر شی، نوع آن را بررسی کنید، شما واقعاً دوست دارید از یک رابط یکنواخت استفاده کنید – رابطی که قبلاً دارید.

برای حل این مشکل، یک را پیاده سازی می کنیم آداپتور کلاس

مانند آداپتورهای دنیای واقعی، کلاس ما منبع خارجی موجود (SvgImage class) و آن را به خروجی مناسب ما تبدیل می کنیم.

در این حالت، این کار را با شطرنجی کردن تصویر برداری انجام می دهیم تا بتوانیم با استفاده از همان عملکردی که قبلاً پیاده سازی کرده ایم، آن را ترسیم کنیم.

توجه داشته باشید: باز هم، برای سادگی، ما فقط print بیرون "png"، اگرچه این تابع تصویر را در زندگی واقعی ترسیم می کند.

آداپتور شی

یک آداپتور شی به سادگی کلاس خارجی (سرویس) را بسته بندی می کند، و رابطی را ارائه می دهد که با کلاس خود ما (مشتری) مطابقت دارد. در این مورد، این سرویس یک گرافیک برداری را در اختیار ما قرار می دهد و آداپتور ما شطرنجی را انجام می دهد و تصویر حاصل را ترسیم می کند:

class SvgAdapter(png_interface):
    def __init__(self, svg):
        self.svg = svg
        
    def rasterize(self):
        return "rasterized " + self.svg.get_image()
    
    def draw(self):
        img = self.rasterize()
        print("drawing " + img)

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

regular_png = PngImage("some data")
regular_png.draw()

example_svg = SvgImage("some data")
example_adapter = SvgAdapter(example_svg)
example_adapter.draw()

عبور از regular_png برای ما خوب کار می کند graphic_draw() تابع. با این حال، گذراندن الف regular_svg کار نمی کند پس از تطبیق regular_svg شی، می‌توانیم از شکل تطبیق‌شده آن درست همانطور که از a استفاده می‌کنیم استفاده کنیم .png تصویر:

drawing png
drawing rasterized svg

نیازی به تغییر چیزی در درون ما نیست graphic_draw() تابع. مانند قبل کار می کند. ما فقط سازگار ورودی متناسب با عملکرد موجود.

آداپتور کلاس

آداپتورهای کلاس فقط می تواند در زبان هایی اجرا شود که از وراثت چندگانه پشتیبانی می کنند. آن‌ها هم کلاس ما و هم کلاس خارجی را به ارث می‌برند، در نتیجه تمام قابلیت‌هایشان را به ارث می‌برند. به همین دلیل، یک نمونه از آداپتور می تواند جایگزین کلاس ما یا کلاس خارجی، تحت یک رابط یکنواخت شود.

برای اینکه بتوانیم این کار را انجام دهیم، باید راهی برای بررسی اینکه آیا نیاز به انجام یک تبدیل داریم یا خیر، داشته باشیم. برای بررسی این موضوع، یک استثنا را معرفی می کنیم:

class ConvertingNonVector(Exception):
    
    
    pass

و با آن، می توانیم یک آداپتور کلاس بسازیم:

class ClassAdapter(png_image, svg_image):
    def __init__(self, image):
        self.image = image
        
    def rasterize(self):
        if(self.image.format == "vector"):
            return "rasterized " + self.image.get_image()
        else:
            raise ConvertingNonVector
        
    def draw(self):
        try:
            img = self.rasterize()
            print("drawing " + img)
        except ConvertingNonVector as e:
            print("drawing " + self.image.get_image())

برای تست اینکه آیا خوب کار می کند، بیایید آن را آزمایش کنیم روی هر دو .png و .svg تصاویر:

example_png = PngImage("some data")
regular_png = ClassAdapter(example_png)
regular_png.draw()

example_svg = SvgImage("some data")
example_adapter = ClassAdapter(example_svg)
example_adapter.draw()

اجرای این کد نتیجه می دهد:

drawing png
drawing rasterized svg

شی یا آداپتور کلاس؟

به طور کلی، شما باید ترجیح دهید استفاده کنید آداپتورهای شی. دو دلیل عمده برای برتری آن نسبت به نسخه کلاسی آن وجود دارد و آن ها عبارتند از:

  • اصل ترکیب بیش از وراثت، اتصال شل را تضمین می کند. در مثال بالا، فرض شده است format لازم نیست فیلد برای کارکرد آداپتور شی وجود داشته باشد، در حالی که برای آداپتور کلاس ضروری است.
  • پیچیدگی اضافه شده که می تواند منجر به مشکلات همراه با ارث چندگانه شود.

الگوی طراحی پل

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

به عنوان مثال، تصور کنید ما یک ساختار طبقاتی داریم که شامل ساختمان های قرون وسطایی است. ما یک wall، tower، stable، mill، house، armoryو غیره. اکنون می‌خواهیم آنها را بر اساس متمایز کنیم روی از چه موادی ساخته شده اند ما می توانستیم هر کلاسی را استخراج کنیم و بسازیم straw_wall، log_wall، cobblestone_wall، limestone_watchtower، و غیره…

علاوه بر این، الف tower می تواند به الف گسترش یابد watchtower، lighthouse، و castle_tower.

ساختار طبقه بد

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

علاوه بر این، خواهد شد limestone_watchtower توسعه دادن، گسترش limestone_tower و مشخصات یک برج مراقبت یا گسترش را اضافه کنید watchtower و مشخصات مواد را اضافه کنید؟

برای جلوگیری از این، ما اطلاعات اساسی را بیرون می آوریم و آن را به عنوان یک زمینه مشترک که بر اساس آن تغییرات ایجاد می کنیم، تبدیل می کنیم. در مورد ما، ما یک سلسله مراتب کلاس را برای Building و Material.

ما می خواهیم یک پل بین همه Building زیر کلاس ها و همه Material زیر کلاس‌ها تا بتوانیم تغییراتی از آن‌ها را تولید کنیم، بدون اینکه مجبور باشیم آنها را به عنوان کلاس‌های جداگانه تعریف کنیم. از آنجایی که یک ماده را می توان در بسیاری از موارد استفاده کرد، Building کلاس شامل خواهد شد Material به عنوان یکی از زمینه های آن:

from abc import ABC, abstractmethod

class Material(ABC):
    @abstractmethod
    def __str__(self):
        pass
        
class Cobblestone(Material):
    def __init__(self):
        pass
    
    def __str__(self):
        return 'cobblestone'
        
class Wood(Material):
    def __init__(self):
        pass
    
    def __str__(self):
        return 'wood'

و با آن، بیایید یک Building کلاس:

from abc import ABC, abstractmethod       
        
class Building(ABC):
    @abstractmethod
    def print_name(self):
        pass
        
class Tower(Building):
    def __init__(self, name, material):
        self.name = name
        self.material = material
        
    def print_name(self):
        print(str(self.material) + ' tower ' + self.name)
        
class Mill(Building):
    def __init__(self, name, material):
        self.name = name
        self.material = material
        
    def print_name(self):
        print(str(self.material) + ' mill ' + self.name)

حالا، وقتی می‌خواهیم یک آسیاب سنگفرش یا یک برج چوبی بسازیم، به آن نیازی نداریم CobblestoneMill یا WoodenTower کلاس ها. در عوض، می توانیم a را نمونه کنیم Mill یا Tower و هر ماده ای را که می خواهیم به آن اختصاص دهیم:

cobb = Cobblestone()
local_mill = Mill('Hilltop Mill', cobb)
local_mill.print_name()

wooden = Wood()
watchtower = Tower('Abandoned Sentry', wooden)
watchtower.print_name()

با اجرای این کد به دست می آید:

cobblestone mill Hilltop Mill
wooden tower Abandoned Sentry

الگوی طراحی ترکیبی

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

این کار به سادگی اجرای یک حلقه نیست، زیرا ساختار هر جعبه نامنظم است. مطمئناً می‌توانید روی اقلام داخل حلقه بزنید، اما چه اتفاقی می‌افتد اگر یک جعبه حاوی جعبه دیگری با اقلام داخل آن باشد؟ حلقه شما چگونه می تواند با آن مقابله کند؟

مطمئناً، می توانید کلاس هر عنصر حلقه شده را بررسی کنید، اما این فقط پیچیدگی بیشتری را ایجاد می کند. هرچه کلاس های بیشتری داشته باشید، موارد لبه بیشتری وجود دارد که منجر به یک می شود سیستم غیر مقیاس پذیر.

چیزی که در چنین مشکلاتی قابل توجه است این است که آنها دارند ساختاری درخت مانند و سلسله مراتبی. شما بزرگترین جعبه را در بالا دارید. و سپس اقلام یا جعبه های کوچکتری در داخل دارید. یک راه خوب برای مقابله با ساختاری مانند این این است که جسم را مستقیماً بالای سر کنترل رفتار افراد زیر آن قرار دهید.

این الگوی طراحی ترکیبی استفاده میشه برای ساختن ساختارهای درخت مانند و با مجموعه ای از اشیا به شیوه ای مشابه رفتار می کنند.

در مثال ما، می‌توانیم کاری کنیم که هر کادر حاوی فهرستی از محتویاتش باشد و مطمئن شویم که همه جعبه‌ها و آیتم‌ها دارای یک تابع هستند – return_price(). اگر تماس بگیرید return_price() روی یک جعبه، محتویات آن را حلقه می زند و قیمت های آن را جمع می کند (همچنین با فراخوانی آنها محاسبه می شود return_price()) و اگر کالایی دارید فقط قیمتش را برمی گرداند.

ما یک موقعیت بازگشت مانند ایجاد کرده ایم که در آن یک مشکل بزرگ را با تقسیم آن به مسائل کوچکتر و فراخوانی همان عملیات حل می کنیم. روی آنها را ما به یک معنا در حال انجام یک جستجوی عمقی از طریق سلسله مراتب اشیا

ما یک چکیده تعریف می کنیم item کلاس، که همه موارد خاص ما از آن به ارث می برند:

from abc import ABC, abstractmethod

class Item(ABC):
    @abstractmethod
    def return_price(self):
        pass

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

class Box(Item):
    def __init__(self, contents):
        self.contents = contents
        
    def return_price(self):
        price = 0
        for item in self.contents:
            price = price + item.return_price()
        return price

class Phone(Item):
    def __init__(self, price):
        self.price = price
        
    def return_price(self):
        return self.price

class Charger(Item):
    def __init__(self, price):
        self.price = price
        
    def return_price(self):
        return self.price

class Earphones(Item):
    def __init__(self, price):
        self.price = price
        
    def return_price(self):
        return self.price

این Box خود یک است Item و همچنین می توانیم a اضافه کنیم Box نمونه داخل a Box نمونه، مثال. بیایید چند مورد را نمونه‌سازی کنیم و قبل از اینکه ارزششان را بدست آوریم، آنها را در یک جعبه قرار دهیم:

phone_case_contents = ()
phone_case_contents.append(Phone(200))
phone_case_box = Box(phone_case_contents)

big_box_contents = ()
big_box_contents.append(phone_case_box)
big_box_contents.append(Charger(10))
big_box_contents.append(Earphones(10))
big_box = Box(big_box_contents)

print("Total price: " + str(big_box.return_price()))

اجرای این کد باعث می شود:

Total price: 220

الگوی طراحی دکوراتور

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

ممکن است در ابتدا به استفاده از وراثت برای حل این مشکل فکر کنید. پس از همه، اگر شما دارید basic_player، می توانید ارث ببرید blazing_player، bouncy_player، و bowman_player از آن.

اما چه در مورد blazing_bouncy_player، bouncy_bowman_player، blazing_bowman_player، و blazing_bouncy_bowman_player?

با اضافه کردن قدرت‌های بیشتر، ساختار پیچیده‌تر می‌شود، باید از ارث بری چندگانه استفاده کنیم یا کد را تکرار کنیم، و هر بار که چیزی به بازی اضافه می‌کنیم، کار بسیار زیادی است که آن را با هر چیز دیگری کار کنیم.

این الگوی دکوراتور برای افزودن قابلیت به یک کلاس بدون تغییر خود کلاس استفاده می شود. ایده این است که یک Wrapper ایجاد کنیم که با همان رابط کلاسی که در حال پیچیدن آن هستیم مطابقت داشته باشد، اما متدهای آن را لغو کند.

می تواند متد را از شی عضو فراخوانی کند و سپس فقط برخی از عملکردهای خود را اضافه کند روی بالای آن، یا می تواند به طور کامل آن را نادیده بگیرد. سپس می توان دکوراتور (لفاف) را با دکوراتور دیگری پیچیده که دقیقاً به همان شکل عمل می کند.

به این ترتیب، می‌توانیم یک شی را هر چند بار که می‌خواهیم تزئین کنیم، بدون اینکه کلاس اصلی را یک بیت تغییر دهیم. بیایید جلو برویم و a را تعریف کنیم PlayerDecorator:

from abc import ABC, abstractmethod

class PlayerDecorator(ABC):
    @abstractmethod
    def handle_input(self, c):
        pass

و حالا بیایید a را تعریف کنیم BasePlayer کلاس، با برخی رفتارهای پیش‌فرض و زیر کلاس‌های آن، رفتار متفاوتی را مشخص می‌کند:

class BasePlayer:
    def __init__(self):
        pass
    
    def handle_input(self, c):
        if   c=='w':
            print('moving forward')
        elif c == 'a':
            print('moving left')
        elif c == 's':
            print('moving back')
        elif c == 'd':
            print('moving right')
        elif c == 'e':
            print('attacking ')
        elif c == ' ':
            print('jumping')
        else:
            print('undefined command')
            
class BlazingPlayer(PlayerDecorator):
    def __init__(self, wrapee):
        self.wrapee = wrapee
        
    def handle_input(self, c):
        if c == 'e':
            print('using fire ', end='')
        
        self.wrapee.handle_input(c)
        
class BowmanPlayer(PlayerDecorator):
    def __init__(self, wrapee):
        self.wrapee = wrapee
        
    def handle_input(self, c):
        if c == 'e':
            print('with arrows ', end='')
            
        self.wrapee.handle_input(c)
        
class BouncyPlayer(PlayerDecorator):
    def __init__(self, wrapee):
        self.wrapee = wrapee
        
    def handle_input(self, c):
        if c == ' ':
            print('double jump')
        else:
            self.wrapee.handle_input(c)

بیایید آنها را یکی یکی بپیچیم و با یک شروع کنیم BasePlayer:

player = BasePlayer()
player.handle_input('e')
player.handle_input(' ')

اجرای این کد برمی گردد:

attacking 
jumping

حال، بیایید آن را با کلاس دیگری بپیچیم که این دستورات را به طور متفاوتی مدیریت می کند:

player = BlazingPlayer(player)
player.handle_input('e')
player.handle_input(' ')

این برمی گردد:

using fire attacking 
jumping

حالا بیایید اضافه کنیم BouncyPlayer مشخصات:

player = BouncyPlayer(player)
player.handle_input('e')
player.handle_input(' ')
using fire attacking 
double jump

آنچه شایان ذکر است این است که player از حمله آتش استفاده می کند، همچنین به عنوان دو پرش ما در حال تزئین player با کلاس های مختلف بیایید آن را بیشتر تزئین کنیم:

player = BowmanPlayer(player)
player.handle_input('e')
player.handle_input(' ')

این برمی گرداند:

with arrows using fire attacking 
double jump

الگوی طراحی نما

فرض کنید در حال شبیه سازی یک پدیده هستید، شاید یک مفهوم تکاملی مانند تعادل بین استراتژی های مختلف. شما مسئول بک‌اند هستید و باید برنامه‌ریزی کنید که نمونه‌ها هنگام تعامل چه می‌کنند، ویژگی‌هایشان چیست، استراتژی‌هایشان چگونه کار می‌کنند، چگونه با یکدیگر تعامل می‌کنند، چه شرایطی باعث مرگ یا تکثیر آن‌ها می‌شود و غیره. .

همکار شما در حال کار است روی نمایش گرافیکی همه اینها آنها به منطق اساسی برنامه شما، عملکردهای مختلفی که بررسی می کنند نمونه با چه کسی سروکار دارد، ذخیره اطلاعات مربوط به تعاملات قبلی و غیره اهمیتی نمی دهند.

ساختار زیربنایی پیچیده شما برای همکارتان خیلی مهم نیست، آنها فقط می خواهند بدانند هر نمونه کجاست و قرار است چه شکلی باشد.

بنابراین، چگونه می‌توانید سیستم پیچیده خود را برای کسی که ممکن است کمی از تئوری بازی و کمتر در مورد اجرای خاص شما از برخی مشکلات بداند، در دسترس قرار دهید؟

این الگوی نما الف را فرا می خواند نما از اجرای شما مردم نیازی به دانستن همه چیز در مورد اجرای اساسی ندارند. شما می توانید یک کلاس بزرگ ایجاد کنید که به طور کامل زیرسیستم پیچیده شما را مدیریت کند و فقط عملکردهایی را که کاربر شما احتمالاً به آن نیاز دارد ارائه می دهد.

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

فرض کنید قطعه کد زیر «سیستم پیچیده» ما است. طبیعتاً می‌توانید از خواندن آن صرفنظر کنید، زیرا نکته اینجاست که برای استفاده از آن نیازی به دانستن جزئیات آن نیست:

class Hawk:
    def __init__(self):
        self.asset = '(`A´)'
        self.alive = True
        self.reproducing = False
    
    def move(self):
        return 'deflect'
    
    def reproduce(self):
        return hawk()
    
    def __str__(self):
        return self.asset
    
class Dove:
    def __init__(self):
        self.asset = '(๑•́ω•̀)'
        self.alive = True
        self.reproducing = False
    
    def move(self):
        return 'cooperate'
    
    def reproduce(self):
        return dove()
    
    def __str__(self):
        return self.asset
        
 def iteration(specimen):
    half = len(specimen)//2
    spec1 = specimen(:half)
    spec2 = specimen(half:)
    
    for s1, s2 in zip(spec1, spec2):
        move1 = s1.move()
        move2 = s2.move()
        
        if move1 == 'cooperate':
            
            if move2 == 'cooperate':
                pass
            
            elif move2 == 'deflect':
                s1.alive = False
                s2.reproducing = True
        elif move1 == 'deflect':
            
            if move2 == 'cooperate':
                s2.alive = False
                s1.reproducing = True
            
            elif move2 == 'deflect':
                s1.alive = False
                s2.alive = False
                
    s = spec1 + spec2
    s = (x for x in s if x.alive == True)
    
    for spec in s:
        if spec.reproducing == True:
            s.append(spec.reproduce())
            spec.reproducing = False
                
    return s

اکنون، دادن این کد به همکارانمان، آنها را ملزم می کند تا قبل از تلاش برای تجسم حیوانات، با عملکرد درونی آشنا شوند. در عوض، بیایید یک نما روی آن نقاشی کنیم و چند کارکرد راحت برای تکرار جمعیت و دسترسی به حیوانات از آن به آنها ارائه دهیم:

import random

class Simulation:
    def __init__(self, hawk_number, dove_number):
        self.population = ()
        for _ in range(hawk_number):
            self.population.append(hawk())
        for _ in range(dove_number):
            self.population.append(dove())
        random.shuffle(self.population)
            
    def iterate(self):
        self.population = iteration(self.population)
        random.shuffle(self.population)
        
    def get_assets(self):
        return (str(x) for x in population)

یک خواننده کنجکاو می تواند با تماس بازی کند iterate() و ببینید چه اتفاقی برای مردم می افتد.

الگوی طراحی Flyweight

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

class Bullet:
    def __init__(self, x, y, z, velocity):
        self.x = x
        self.y = y
        self.z = z
        self.velocity = velocity
        self.asset = '■■►'

اینها حافظه قابل توجهی را اشغال می کنند، به خصوص اگر تعداد زیادی گلوله در یک زمان در هوا وجود داشته باشد (و ما یک شکلک یونیکد را به جای دارایی ها در زندگی واقعی ذخیره نمی کنیم).

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

اگر نوع دیگری از گلوله شلیک می‌شد، با بافت متفاوت – هر دو را نمونه‌سازی می‌کردیم و آنها را برمی‌گردانیم. با این حال، اگر با مقادیر تکراری سر و کار داریم – می توانیم مقدار اصلی را در a نگه داریم استخر/حافظه پنهان و فقط از آنجا بکشید

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

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

کاری که ما می توانیم انجام دهیم این است که این صفات را از هم جدا کنیم، به طوری که صفات ذاتی همه در یک نمونه ذخیره شوند – یک کلاس Flyweight. صفات بیرونی در موارد جداگانه نامیده می شوند کلاس های زمینه. کلاس Flyweight معمولاً شامل تمام متدهای کلاس اصلی است و با ارسال نمونه ای از آن به آنها کار می کند. کلاس زمینه.

برای اطمینان از اینکه برنامه طبق برنامه کار می کند، کلاس Flyweight باید تغییر ناپذیر باشد. به این ترتیب، اگر از زمینه های مختلف فراخوانی شود، هیچ رفتار غیرمنتظره ای وجود نخواهد داشت.

برای استفاده عملی، کارخانه Flyweight اغلب اجرا می شود. این کلاسی است که وقتی از یک حالت ذاتی عبور می کند، بررسی می کند که آیا یک شی با آن حالت از قبل وجود دارد یا خیر، و اگر وجود داشته باشد آن را برمی گرداند. اگر این کار را انجام ندهد، یک شی جدید را نمونه سازی می کند و آن را برمی گرداند:

class BulletContext:
    def __init__(self, x, y, z, velocity):
        self.x = x
        self.y = y
        self.z = z
        self.velocity = velocity
        
 class BulletFlyweight:
    def __init__(self):
        self.asset = '■■►'
        self.bullets = ()
        
    def bullet_factory(self, x, y, z, velocity):
        bull = (b for b in self.bullets if b.x==x and b.y==y and b.z==z and b.velocity==velocity)
        if not bull:
            bull = bullet(x,y,z,velocity)
            self.bullets.append(bull)
        else:
            bull = bull(0)
            
        return bull
        
    def print_bullets(self):
        print('Bullets:')
        for bullet in self.bullets:
            print(str(bullet.x)+' '+str(bullet.y)+' '+str(bullet.z)+' '+str(bullet.velocity))

ما زمینه ها و وزن خود را ساخته ایم. هر بار که سعی می کنیم یک زمینه جدید (گلوله) را از طریق آن اضافه کنیم bullet_factory() تابع – لیستی از گلوله‌های موجود را که در اصل همان گلوله هستند تولید می‌کند. اگر چنین گلوله ای پیدا کردیم، فقط می توانیم آن را برگردانیم. اگر این کار را نکنیم، یک مورد جدید تولید می کنیم.

حال، با در نظر گرفتن آن، بیایید از آن استفاده کنیم bullet_factory() به نمونه چند گلوله و print ارزش های آنها:

bf = BulletFlyweight()


bf.bullet_factory(1,1,1,1)
bf.bullet_factory(1,2,5,1)

bf.print_bullets()

این منجر به:

Bullets:
1 1 1 1
1 2 5 1

اکنون، بیایید گلوله های بیشتری را از طریق کارخانه، که از قبل وجود دارد، اضافه کنیم:


bf.bullet_factory(1,1,1,1)
bf.print_bullets()

این منجر به:

Bullets:
1 1 1 1
1 2 5 1

الگوی طراحی پروکسی

یک بیمارستان از یک نرم افزار با یک PatientFileManager کلاس برای ذخیره داده ها روی بیماران آنها با این حال، بسته به روی سطح دسترسی شما، ممکن است نتوانید فایل های برخی از بیماران را مشاهده کنید. به هر حال، حق حفظ حریم خصوصی، بیمارستان را از انتشار این اطلاعات بیشتر از حد لازم برای ارائه خدمات خود منع می کند.

این فقط یک مثال است – الگوی پروکسی در واقع می تواند در شرایط بسیار متنوعی استفاده شود، از جمله:

  • مدیریت دسترسی به یک شی که گران است، مانند یک سرور راه دور یا پایگاه داده
  • ایستادن برای اشیایی که مقداردهی اولیه آنها می تواند گران باشد تا زمانی که واقعاً در یک برنامه مورد نیاز نباشند، مانند بافت هایی که فضای RAM زیادی را اشغال می کنند یا یک پایگاه داده بزرگ.
  • مدیریت دسترسی برای اهداف امنیتی

در مثال بیمارستان ما، می توانید کلاس دیگری مانند an AccessManager، که کنترل می کند چه کاربرانی می توانند یا نمی توانند با ویژگی های خاصی تعامل داشته باشند PatientFileManager. این AccessManager هست یک پروکسی کلاس و کاربر از طریق آن با کلاس زیرین ارتباط برقرار می کند.

بیایید یک PatientFileManager کلاس:

class PatientFileManager:
    def __init__(self):
        self.__patients = {}
        
    def _add_patient(self, patient_id, data):
        self.__patients(patient_id) = data
        
    def _get_patient(self, patient_id):
        return self.__patients(patient_id)

حالا بیایید یک پروکسی برای آن بسازیم:

class AccessManager(PatientFileManager):
    def __init__(self, fm):
        self.fm = fm
    
    def add_patient(self, patient_id, data, password):
        if password == 'sudo':
            self.fm._add_patient(patient_id, data)
        else:
            print("Wrong password.")
            
    def get_patient(self, patient_id, password):
        if password == 'totallytheirdoctor' or password == 'sudo':
            return self.fm._get_patient(patient_id)
        else:
            print("Only their doctor can access this patients data.")

در اینجا، ما چند چک داریم. اگر رمز عبور ارائه شده به پروکسی درست باشد، AccessManager نمونه می تواند اطلاعات بیمار را اضافه یا بازیابی کند. اگر رمز عبور اشتباه است – نمی تواند.

حال، بیایید یک نمونه را مثال بزنیم AccessManager و یک بیمار اضافه کنید:

am = AccessManager(PatientFileManager())
am.add_patient('Jessica', ('pneumonia 2020-23-03', 'shortsighted'), 'sudo')

print(am.get_patient('Jessica', 'totallytheirdoctor'))

این منجر به:

('pneumonia 2020-23-03', 'shortsighted')

توجه داشته باشید: توجه به این نکته مهم است که پایتون متغیرهای خصوصی واقعی ندارد – زیرخط ها فقط نشانه ای برای برنامه نویسان دیگر هستند که چیزها را لمس نکنند. بنابراین در این مورد، پیاده سازی یک پروکسی بیشتر به منظور نشان دادن قصد شما در مورد مدیریت دسترسی به جای مدیریت واقعی دسترسی عمل می کند.

نتیجه

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

(برچسب‌ها به ترجمه)# python



منتشر شده در 1403-01-17 06:34:03

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید