از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
الگوهای طراحی سازه در پایتون
سرفصلهای مطلب
معرفی
الگوهای طراحی سازه برای جمع آوری چندین کلاس در ساختارهای کاری بزرگتر استفاده می شود. گاهی اوقات رابطهای کار با چندین شی به سادگی مناسب نیستند، یا با کدهای قدیمی کار میکنید که نمیتوانید تغییر دهید، اما به عملکرد جدیدی نیاز دارید، یا تازه متوجه میشوید که ساختارهای شما نامرتب و بیش از حد به نظر میرسند، اما همه عناصر ضروری به نظر میرسند.
آنها برای ایجاد بسیار مفید هستند کد خوانا، قابل نگهداری، لایه لایه، به خصوص هنگام کار با کتابخانه های خارجی، کدهای قدیمی، کلاس های وابسته به هم یا اشیاء متعدد.
الگوهای طراحی پوشش داده شده در این بخش عبارتند از:
الگوی طراحی آداپتور
در دنیای واقعی، ممکن است از یک آداپتور برای اتصال شارژرها به سوکت های مختلف هنگام سفر به کشورهای دیگر یا مدل های مختلف تلفن استفاده کنید. می توانید از آنها برای اتصال یک مانیتور 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