از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
الگوهای طراحی رفتاری در پایتون
سرفصلهای مطلب
معرفی
ما قبلاً به الگوهای طراحی ساختاری و خلاقانه پرداخته ایم، و این بخش بر یک مقوله حیاتی دیگر تمرکز دارد – الگوهای طراحی رفتاری.
الگوهای رفتاری همه چیز است ارتباط بین اشیاء. آنها به مسئولیت اشیاء و نحوه برقراری ارتباط آنها می پردازند و اطمینان حاصل می کنند که اشیا به طور مؤثر با هم همکاری می کنند و در عین حال باقی می مانند. سست جفت شده است. این اتصال شل بسیار مهم است زیرا باعث انعطاف پذیری در سیستم می شود و امکان نگهداری و مقیاس پذیری آسان تر را فراهم می کند.
توجه داشته باشید: اتصال شل یک اصل طراحی است که استقلال اجزای سیستم را ارتقا میدهد و تضمین میکند که ماژولها یا کلاسها حداقل دانشی از عملکرد داخلی ماژولها یا کلاسهای دیگر دارند. با پایبندی به این اصل، تغییرات در یک ماژول حداقل یا بدون هیچ تاثیری بر سایر ماژول ها می گذارد و سیستم را قابل نگهداری، مقیاس پذیرتر و انعطاف پذیرتر می کند.
این بدان معنی است که شما باید کلاس ها، توابع و ماژول های خود را طوری طراحی کنید که کمتر به ویژگی های کلاس ها، توابع یا ماژول های دیگر تکیه کنند. در عوض، آنها باید بر انتزاعات یا رابط ها تکیه کنند.
بر خلاف الگوهای ساختاری، که بر نحوه ترکیب اشیاء تمرکز میکنند، یا الگوهای خلاقانه، که با مکانیسمهای خلق اشیا سروکار دارند، الگوهای رفتاری نوری را به آن میتابانند. تعاملات پویا بین اشیاء.
الگوهای طراحی پوشش داده شده در این بخش عبارتند از:
الگوی طراحی زنجیره مسئولیت
تصور کنید در حال توسعه یک سیستم پشتیبانی مشتری برای یک پلتفرم بزرگ تجارت الکترونیک هستید. مشتریان می توانند انواع مختلفی از مسائل را مطرح کنند، از مشکلات پرداخت تا درخواست حمل و نقل. همه عوامل پشتیبانی نمی توانند هر نوع مشکلی را مدیریت کنند. برخی از نمایندگان در بازپرداخت، برخی دیگر در مشکلات فنی و غیره تخصص دارند. وقتی مشتری موضوعی را مطرح میکند، چگونه مطمئن میشوید که بدون کدگذاری یک ساختار پیچیده تصمیمگیری، به عامل مناسب میرسد؟
در کد ما، این میتواند مانند یک سری دستورات تودرتوی if-else باشد که نوع مشکل را بررسی کرده و سپس آن را به عامل مناسب هدایت میکند. اما این رویکرد با اضافه شدن انواع بیشتری از مسائل و متخصصان به سیستم، به سرعت دشوار می شود.
def handle_issue(issue_type, issue_details):
if issue_type == "payment":
# direct to payment specialist
elif issue_type == "shipping":
# direct to shipping specialist
# ... and so on for every type of issue
این الگوی زنجیره مسئولیت یک راه حل زیبا برای این مشکل ارائه می دهد. فرستنده (در این مورد، مشکل مشتری) را از گیرندگان خود (نمایندگان پشتیبانی) با اجازه دادن به چندین شی برای پردازش درخواست جدا می کند. این اشیاء در یک زنجیره به هم متصل می شوند و درخواست در طول زنجیره حرکت می کند تا زمانی که پردازش شود یا به پایان برسد.
در سیستم پشتیبانی ما، هر عامل نشان دهنده یک حلقه در زنجیره است. یک نماینده یا مشکل را مدیریت می کند یا آن را به نماینده بعدی در صف ارسال می کند.
class SupportAgent:
def __init__(self, specialty, next_agent=None):
self.specialty = specialty
self.next_agent = next_agent
def handle_issue(self, issue_type, issue_details):
if issue_type == self.specialty:
# handle the issue
print(f"Handled {issue_type} issue by {self.specialty} specialist.")
elif self.next_agent:
self.next_agent.handle_issue(issue_type, issue_details)
else:
print("Issue couldn't be handled.")
بیایید این را با ایجاد یک نماینده پرداخت و یک نماینده حمل و نقل آزمایش کنیم. سپس، موضوع پرداخت را به نماینده حمل و نقل منتقل می کنیم و مشاهده می کنیم که چه اتفاقی می افتد:
# Create a chain of agents
payment_agent = SupportAgent("payment")
shipping_agent = SupportAgent("shipping", payment_agent)
# Raise an issue
shipping_agent.handle_issue("payment", "Payment declined.")
به دلیل الگوی زنجیره مسئولیت که در اینجا پیادهسازی کردهایم، نماینده حمل و نقل موضوع را به نماینده پرداخت منتقل میکند، که آن را مدیریت میکند:
Handled payment issue by payment specialist.
با الگوی زنجیره مسئولیت، سیستم ما انعطاف پذیرتر می شود. با رشد تیم پشتیبانی و ظهور تخصص های جدید، می توانیم به راحتی زنجیره را بدون تغییر ساختار کد موجود گسترش دهیم.
الگوی طراحی فرمان
در نظر بگیرید که در حال ساختن یک سیستم خانه هوشمند هستید که در آن کاربران می توانند دستگاه های مختلفی مانند چراغ ها، ترموستات ها و پخش کننده های موسیقی را از طریق یک رابط مرکزی کنترل کنند. با تکامل سیستم، دستگاه ها و قابلیت های بیشتری را اضافه خواهید کرد. یک رویکرد ساده لوحانه ممکن است شامل ایجاد یک روش جداگانه برای هر عمل در هر دستگاه باشد. با این حال، با افزایش تعداد دستگاه ها و اقدامات، این می تواند به سرعت تبدیل به یک کابوس تعمیر و نگهداری شود.
به عنوان مثال، روشن کردن یک چراغ ممکن است به شکل زیر باشد:
class SmartHome:
def turn_on_light(self):
# logic to turn on the light
حالا تصور کنید روش هایی برای خاموش کردن نور، تنظیم ترموستات، پخش موسیقی و غیره اضافه کنید. کلاس بیش از حد حجیم می شود و هرگونه تغییر در یک روش ممکن است روی روش های دیگر تأثیر بگذارد.
این الگوی فرمان در چنین سناریوهایی به کمک می آید. آی تی یک درخواست را به عنوان یک شی کپسوله می کند، بنابراین به کاربران اجازه می دهد تا مشتریان را با درخواست های مختلف، درخواست های صف و پشتیبانی از عملیات غیرقابل انجام پارامتر بندی کنند. در اصل، شیئی که دستور را فراخوانی می کند از شیئی که می داند چگونه آن را اجرا کند جدا می کند.
برای پیاده سازی این، a را تعریف می کنیم رابط فرمان با یک execute()
روش. هر عمل دستگاه به یک فرمان مشخص تبدیل می شود که این رابط را پیاده سازی می کند. سیستم خانه هوشمند صرفاً این را فراخوانی می کند execute()
روش بدون نیاز به دانستن ویژگی های عمل:
from abc import ABC, abstractmethod
# Command interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
# Concrete command
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class Light:
def turn_on(self):
print("Light is ON")
# Invoker
class SmartHome:
def __init__(self, command):
self.command = command
def press_button(self):
self.command.execute()
برای آزمایش این موضوع، اجازه دهید یک چراغ، یک فرمان مربوطه برای روشن کردن چراغ و یک شی خانه هوشمند طراحی شده برای روشن کردن چراغ ایجاد کنیم. برای روشن کردن چراغ، فقط باید آن را فراخوانی کنید press_button()
روش از home
به نظر می رسد، لازم نیست بدانید که در واقع در زیر کاپوت چه می کند:
light = Light()
light_on = LightOnCommand(light)
home = SmartHome(light_on)
home.press_button()
اجرای این به شما می دهد:
Light is ON
این فرمان الگو به شما کمک می کند دستگاه ها یا اقدامات جدیدی اضافه کنید. هر اقدام جدید یک کلاس دستوری جدید است که تضمین می کند سیستم ماژولار باقی می ماند و نگهداری آن آسان است.
الگوی طراحی Iterator
تصور کنید که در حال توسعه یک ساختار داده سفارشی هستید، مثلاً یک نوع مجموعه منحصر به فرد برای ذخیره کتاب در یک سیستم کتابخانه. کاربران این مجموعه باید بتوانند بدون نیاز به درک مکانیسم ذخیره سازی زیرین کتاب ها را طی کنند. یک رویکرد ساده ممکن است ساختار داخلی مجموعه را در معرض دید قرار دهد، اما این می تواند منجر به اتصال شدید و سوء استفاده احتمالی شود. به عنوان مثال، اگر مجموعه سفارشی ما یک لیست باشد:
class BookCollection:
def __init__(self):
self.books = ()
def add_book(self, book):
self.books.append(book)
برای عبور از کتابخانه، باید داخلی را در معرض دید قرار دهید books
لیست:
library = BookCollection()
library.add_book("The Great Gatsby")
# To traverse, we're exposing the `library.books`
for book in library.books:
print(book)
این یک تمرین عالی نیست! اگر در آینده مکانیسم ذخیره سازی اساسی را تغییر دهیم، همه کدهایی که مستقیماً به آن دسترسی دارند books
خواهد شکست.
این الگوی تکرار کننده راه حلی را با ارائه راهی برای دسترسی متوالی به عناصر یک شی انبوه ارائه می دهد بدون افشای نمایندگی زیربنایی آن. منطق تکرار را در یک شی مجزا کپسوله می کند.
برای پیاده سازی این مورد در پایتون، می توانیم از پروتکل تکرار کننده داخلی پایتون استفاده کنیم (__iter__()
و __next__()
مواد و روش ها):
class BookCollection:
def __init__(self):
self._books = ()
def add_book(self, book):
self._books.append(book)
def __iter__(self):
return BookIterator(self)
class BookIterator:
def __init__(self, book_collection):
self._book_collection = book_collection
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index < len(self._book_collection._books):
book = self._book_collection._books(self._index)
self._index += 1
return book
raise StopIteration
در حال حاضر، نیازی به افشای نمایندگی داخلی وجود ندارد library
وقتی در حال تکرار روی آن هستیم:
library = BookCollection()
library.add_book("The Great Gatsby")
# To traverse:
for book in library:
print(book)
در این حالت، اجرای کد به شما می دهد:
The Great Gatsby
با اشاره گر الگو، ساختار درونی
BookCollection
پنهان است. کاربران همچنان میتوانند بهطور یکپارچه از مجموعه عبور کنند، و ما انعطافپذیری لازم برای تغییر مکانیسم ذخیرهسازی داخلی را بدون تأثیرگذاری بر کد خارجی حفظ میکنیم.
الگوی طراحی میانجی
فرض کنید در حال ساخت یک رابط کاربری پیچیده (UI) برای یک برنامه نرم افزاری هستید. این رابط کاربری دارای اجزای متعددی مانند دکمه ها، فیلدهای متنی و منوهای کشویی است. این اجزا باید با یکدیگر تعامل داشته باشند. به عنوان مثال، انتخاب یک گزینه در یک کشویی ممکن است یک دکمه را فعال یا غیرفعال کند. یک رویکرد مستقیم شامل شناخت هر جزء و تعامل مستقیم با بسیاری از مؤلفههای دیگر میشود. این منجر به شبکه ای از وابستگی ها می شود و حفظ و گسترش سیستم را سخت می کند.
برای نشان دادن این موضوع، تصور کنید که با یک سناریوی ساده روبرو هستید که در آن یک دکمه باید فقط زمانی فعال شود که یک فیلد متنی دارای محتوا باشد:
class TextField:
def __init__(self):
self.content = ""
self.button = None
def set_content(self, content):
self.content = content
if self.content:
self.button.enable()
else:
self.button.disable()
class Button:
def enable(self):
print("Button enabled")
def disable(self):
print("Button disabled")
textfield = TextField()
button = Button()
textfield.button = button
اینجا، TextField
مستقیماً دستکاری می کند Button
، منجر به اتصال کامل. اگر مؤلفه های بیشتری اضافه کنیم، وابستگی های متقابل به طور تصاعدی رشد می کنند.
این میانجی الگوی الف را معرفی می کند شی مرکزی که نحوه تعامل مجموعه ای از اشیاء را در بر می گیرد. این میانجی با اطمینان از اینکه به جای ارجاع صریح اجزا به یکدیگر، به واسطه ای که منطق تعامل را مدیریت می کند، کوپلینگ شل را ترویج می کند.
بیایید مثال بالا را با استفاده از الگوی Mediator بازیابی کنیم:
class Mediator:
def __init__(self):
self.textfield = TextField(self)
self.button = Button(self)
def notify(self, sender, event):
if sender == "textfield" and event == "content_changed":
if self.textfield.content:
self.button.enable()
else:
self.button.disable()
class TextField:
def __init__(self, mediator):
self.content = ""
self.mediator = mediator
def set_content(self, content):
self.content = content
self.mediator.notify("textfield", "content_changed")
class Button:
def __init__(self, mediator):
self.mediator = mediator
def enable(self):
print("Button enabled")
def disable(self):
print("Button disabled")
در حال حاضر، شما می توانید استفاده کنید Mediator
کلاس برای تنظیم محتوای فیلد متنی:
ui_mediator = Mediator()
ui_mediator.textfield.set_content("Hello")
این به طور خودکار به اطلاع می رساند Button
کلاسی که برای فعال کردن دکمه نیاز دارد، که این کار را انجام می دهد:
Button enabled
هر بار که محتوا را تغییر می دهید همین امر صدق می کند، اما اگر آن را به طور کلی حذف کنید، دکمه غیرفعال می شود.
الگوی Mediator به شما کمک می کند تا منطق تعامل را متمرکز نگه دارید
Mediator
کلاس این امر نگهداری و گسترش سیستم را آسانتر میکند، زیرا افزودن مؤلفههای جدید یا تغییر فعل و انفعالات فقط به اصلاحات در میانجی نیاز دارد، بدون اینکه به اجزای جداگانه دست بزنیم.
الگوی طراحی یادگاری
شما در حال توسعه یک ویرایشگر متن هستید. یکی از ویژگی های ضروری چنین اپلیکیشنی، توانایی آن است لغو کردن تغییر می کند. کاربران انتظار دارند که اقدامات خود را به صورت یکپارچه به حالت قبلی برگردانند. پیادهسازی این قابلیت «لغو» ممکن است ساده به نظر برسد، اما اطمینان از اینکه وضعیت ویرایشگر بدون افشای ساختار داخلی آن ضبط و بازیابی میشود، میتواند چالش برانگیز باشد. یک رویکرد ساده لوحانه را در نظر بگیرید:
class TextEditor:
def __init__(self):
self.content = ""
self.previous_content = ""
def write(self, text):
self.previous_content = self.content
self.content += text
def undo(self):
self.content = self.previous_content
این رویکرد محدود است – فقط آخرین حالت را به خاطر می آورد. اگر کاربر چندین تغییر ایجاد کند، فقط جدیدترین آن قابل لغو است.
این یادگاری الگو راهی برای ثبت وضعیت داخلی یک شی فراهم می کند تا بتوان بعداً آن را بازیابی کرد، همه اینها بدون نقض کپسولاسیون. در زمینه ویرایشگر متن ما، هر حالت از محتوا را می توان به عنوان یادگاری ذخیره کرد و ویرایشگر می تواند با استفاده از این یادگاری ها به هر حالت قبلی برگردد.
حالا بیایید از الگوی Memento برای ذخیره تغییرات ایجاد شده در یک متن استفاده کنیم. ما یک را ایجاد خواهیم کرد Memento
کلاسی که حالت را در خود جای می دهد و یک متد getter که می توانید برای دسترسی به حالت ذخیره شده استفاده کنید. از سوی دیگر، ما آن را اجرا خواهیم کرد write()
روش از TextEditor
کلاس به طوری که آن را ذخیره می کند وضعیت فعلی قبل از ایجاد هر گونه تغییر در محتوا:
class Memento:
def __init__(self, state):
self._state = state
def get_saved_state(self):
return self._state
class TextEditor:
def __init__(self):
self._content = ""
def write(self, text):
return Memento(self._content) # Save the current state before changing
self._content += text
def restore(self, memento):
self._content = memento.get_saved_state()
def __str__(self):
return self._content
بیایید سریع کد را اجرا کنیم:
editor = TextEditor()
editor.write("Hello, ")
memento1 = editor.write("world!")
editor.write(" How are you?")
print(editor)
در اینجا، ما ایجاد کردیم TextEditor
شی، متنی را به ویرایشگر متن نوشت، سپس متن دیگری نوشت و محتوا را از ویرایشگر متن درخواست کرد:
Hello, world! How are you?
اما از آنجایی که حالت قبلی را در قسمت ذخیره کردیم memento1
متغیر، ما همچنین میتوانیم آخرین تغییری را که در متن ایجاد کردهایم، که اضافه کردن آن است، لغو کنیم "How are you?"
سوال در پایان:
editor.restore(memento1)
print(editor)
این به ما آخرین وضعیت ویرایشگر متن را بدون استفاده از "How are you?"
بخش:
Hello, world!
با الگوی یادگاری،
TextEditor
می تواند حالت خود را بدون افشای ساختار داخلی خود ذخیره و بازیابی کند. این امر محصورسازی را تضمین میکند و مکانیزمی قوی برای پیادهسازی ویژگیهایی مانند undo و redo فراهم میکند.
الگوی طراحی ناظر
تصور کنید در حال ساختن یک اپلیکیشن پایش آب و هوا هستید. این برنامه دارای عناصر نمایشی متعددی است، مانند نمایشگر شرایط فعلی، نمایشگر آمار و نمایش پیش بینی. هر زمان که داده های آب و هوا (مانند دما، رطوبت یا فشار) به روز می شوند، همه این نمایشگرها باید به روز شوند تا آخرین داده ها را منعکس کنند. یک رویکرد مستقیم ممکن است شامل دانستن شی داده آب و هوا در مورد تمام عناصر نمایشگر و به روز رسانی صریح آنها باشد. با این حال، این منجر به اتصال کامل، سیستم را غیر قابل انعطاف و توسعه آن سخت می کند. به عنوان مثال، به روز رسانی داده های آب و هوا را مانند این بگویید:
class WeatherData:
def __init__(self):
self.temperature = 0
self.humidity = 0
self.pressure = 0
self.current_display = CurrentConditionsDisplay()
self.stats_display = StatisticsDisplay()
def measurements_changed(self):
self.current_display.update(self.temperature, self.humidity, self.pressure)
self.stats_display.update(self.temperature, self.humidity, self.pressure)
این رویکرد کاملاً مشکل ساز است. اگر یک نمایشگر جدید اضافه کنیم یا یک موجود را حذف کنیم، WeatherData
کلاس باید اصلاح شود
این مشاهده کننده الگو با تعریف a یک راه حل ارائه می دهد وابستگی یک به چند بین اشیاء به طوری که وقتی یک شیء تغییر حالت می دهد، تمام وابستگان آن به طور خودکار مطلع و به روز می شوند.
در مورد ما، WeatherData
موضوع است و نمایشگرها ناظر هستند:
from abc import ABC, abstractmethod
# Observer & Subject interfaces
class Observer(ABC):
@abstractmethod
def update(self, temperature, humidity, pressure):
pass
class Subject(ABC):
@abstractmethod
def register_observer(self, observer):
pass
@abstractmethod
def remove_observer(self, observer):
pass
@abstractmethod
def notify_observers(self):
pass
# Concrete implementations
class WeatherData(Subject):
def __init__(self):
self.observers = ()
self.temperature = 0
self.humidity = 0
self.pressure = 0
def register_observer(self, observer):
self.observers.append(observer)
def remove_observer(self, observer):
self.observers.remove(observer)
def notify_observers(self):
for observer in self.observers:
observer.update(self.temperature, self.humidity, self.pressure)
def measurements_changed(self):
self.notify_observers()
def set_measurements(self, temperature, humidity, pressure):
self.temperature = temperature
self.humidity = humidity
self.pressure = pressure
self.measurements_changed()
class CurrentConditionsDisplay(Observer):
def update(self, temperature, humidity, pressure):
print(f"Current conditions: {temperature}°C and {humidity}% humidity")
بیایید یک آزمایش سریع برای مثالی که ایجاد کردیم انجام دهیم:
# Running the code
weather_data = WeatherData()
current_display = CurrentConditionsDisplay()
weather_data.register_observer(current_display)
weather_data.set_measurements(25, 65, 1012)
این به ما می دهد:
Current conditions: 25°C and 65% humidity
اینجا
WeatherData
کلاس نیازی به دانستن عناصر نمایشگر خاصی ندارد. این فقط به همه ناظران ثبت شده در هنگام تغییر داده ها اطلاع می دهد. این امر اتصال شل را تقویت می کند و سیستم را مدولارتر و قابل گسترش تر می کند.
الگوی طراحی دولتی
زمانی که در حال توسعه یک نرم افزار ماشین فروش ساده هستید، الگوهای طراحی حالت می توانند مفید باشند. دستگاه خودپرداز چندین حالت دارد، مانند «بدون سکه»، «سکه دارد»، «فروخته شده» و «خالی». بسته به وضعیت فعلی، وقتی کاربر یک سکه را وارد می کند، محصولی را درخواست می کند یا درخواست بازپرداخت می کند، دستگاه رفتار متفاوتی دارد. یک رویکرد ساده ممکن است شامل استفاده از یک سری از if-else
یا عبارت های سوئیچ مورد برای رسیدگی به این اقدامات بر اساس وضعیت فعلی. با این حال، این می تواند به سرعت دست و پا گیر شود، به خصوص با افزایش تعداد حالت ها و انتقال ها:
class VendingMachine:
def __init__(self):
self.state = "No Coin"
def insert_coin(self):
if self.state == "No Coin":
self.state = "Has Coin"
elif self.state == "Has Coin":
print("Coin already inserted.")
# ... other states
این VendingMachine
کلاس می تواند به راحتی بیش از حد دست و پا گیر شود و افزودن حالت های جدید یا تغییر انتقال ها چالش برانگیز می شود.
این حالت الگو با اجازه دادن به یک شی برای تغییر رفتار خود در صورت تغییر حالت داخلی، راه حلی را ارائه می دهد. این الگو شامل کپسولهسازی رفتار خاص حالت در کلاسهای جداگانه است، و اطمینان حاصل میکند که هر کلاس حالت، انتقالها و اقدامات خود را انجام میدهد.
برای پیاده سازی الگوی State، باید هر انتقال و عمل حالت را در کلاس حالت مربوطه آن کپسوله کنید:
from abc import ABC, abstractmethod
# State interface
class State(ABC):
@abstractmethod
def insert_coin(self):
pass
@abstractmethod
def eject_coin(self):
pass
@abstractmethod
def dispense(self):
pass
# Concrete states
class NoCoinState(State):
def insert_coin(self):
print("Coin accepted.")
return "Has Coin"
def eject_coin(self):
print("No coin to eject.")
return "No Coin"
def dispense(self):
print("Insert coin first.")
return "No Coin"
class HasCoinState(State):
def insert_coin(self):
print("Coin already inserted.")
return "Has Coin"
def eject_coin(self):
print("Coin returned.")
return "No Coin"
def dispense(self):
print("Product dispensed.")
return "No Coin"
# Context
class VendingMachine:
def __init__(self):
self.state = NoCoinState()
def insert_coin(self):
self.state = self.state.insert_coin()
def eject_coin(self):
self.state = self.state.eject_coin()
def dispense(self):
self.state = self.state.dispense()
برای عملی کردن همه اینها، بیایید یک ماشین فروش ساده را شبیهسازی کنیم که یک سکه در آن وارد میکنیم، سپس دستگاه را توزیع میکنیم، و در نهایت سعی میکنیم یک سکه را از دستگاه توزیعشده بیرون بیاوریم:
# Running the code
machine = VendingMachine()
machine.insert_coin()
machine.dispense()
machine.eject_coin()
همانطور که احتمالا حدس زده اید، این به شما می دهد:
Coin accepted.
Product dispensed.
No coin to eject.
الگوی طراحی استراتژی
برای نشان دادن الگوی طراحی استراتژی، بگویید که در حال ساختن یک پلتفرم تجارت الکترونیک هستید که در آن انواع مختلفی از تخفیف ها برای سفارش ها اعمال می شود. ممکن است تخفیف “فروش جشن”، تخفیف “کاربر جدید” یا حتی تخفیف “امتیاز وفاداری” وجود داشته باشد. یک رویکرد مستقیم ممکن است شامل استفاده از if-else
بیانیه اعمال این تخفیف ها بر اساس نوع. با این حال، با افزایش تعداد انواع تخفیف، این روش سخت و سخت می شود:
class Order:
def __init__(self, total, discount_type):
self.total = total
self.discount_type = discount_type
def final_price(self):
if self.discount_type == "Festive Sale":
return self.total * 0.9
elif self.discount_type == "New User":
return self.total * 0.95
# ... other discount types
با این رویکرد Order
کلاس متورم می شود و افزودن استراتژی های تخفیف جدید یا اصلاح استراتژی های موجود چالش برانگیز می شود.
این استراتژی الگو با تعریف خانوادهای از الگوریتمها (در این مورد تخفیفها)، محصور کردن هر یک و تبدیل آنها به یکدیگر، راهحلی را ارائه میدهد. این اجازه می دهد تا الگوریتم به طور مستقل از مشتریانی که از آن استفاده می کنند متفاوت باشد.
هنگام استفاده از الگوی استراتژی، باید هر نوع تخفیف را در کلاس استراتژی مربوطه خود قرار دهید. این باعث می شود سیستم سازماندهی شده، مدولارتر و نگهداری یا گسترش آن آسان تر باشد. افزودن یک نوع تخفیف جدید به سادگی مستلزم ایجاد یک کلاس استراتژی جدید بدون تغییر کد موجود است:
from abc import ABC, abstractmethod
# Strategy interface
class DiscountStrategy(ABC):
@abstractmethod
def apply_discount(self, total):
pass
# Concrete strategies
class FestiveSaleDiscount(DiscountStrategy):
def apply_discount(self, total):
return total * 0.9
class NewUserDiscount(DiscountStrategy):
def apply_discount(self, total):
return total * 0.95
# Context
class Order:
def __init__(self, total, discount_strategy):
self.total = total
self.discount_strategy = discount_strategy
def final_price(self):
return self.discount_strategy.apply_discount(self.total)
بیایید این را آزمایش کنیم! ما دو سفارش ایجاد خواهیم کرد، یکی با تخفیف فروش جشنواره و دیگری با تخفیف کاربر جدید:
# Running the code
order1 = Order(100, FestiveSaleDiscount())
print(order1.final_price())
order2 = Order(100, NewUserDiscount())
print(order2.final_price())
چاپ کردن قیمت های سفارش به ما می دهد 90.0
برای فروش جشنواره سفارش با تخفیف، و 95.0
برای سفارشی که تخفیف کاربر جدید را اعمال کردیم.
الگوی طراحی بازدید کننده
در این بخش، شما در حال توسعه یک سیستم گرافیک کامپیوتری هستید که می تواند اشکال مختلفی مانند دایره، مستطیل و مثلث را ارائه دهد. اکنون، میخواهید عملکردی برای محاسبه مساحت این اشکال و شاید بعداً، محیط آنها اضافه کنید. یکی از روش ها اضافه کردن این روش ها به طور مستقیم به کلاس های شکل است. با این حال، این خواهد شد اصل باز/بسته را نقض می کند، زیرا هر بار که می خواهید عملیات جدیدی اضافه کنید، کلاس های موجود را تغییر می دهید:
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
# compute area for circle
با اضافه کردن عملیات یا شکلهای بیشتر، کلاسها متورم میشوند و نگهداری سیستم سختتر میشود.
این بازدید کننده الگو با اجازه دادن به شما برای اضافه کردن عملیات بیشتر به اشیا بدون نیاز به تغییر آنها، راه حلی را ارائه می دهد. این شامل ایجاد یک کلاس بازدید کننده برای هر عملیات است که باید روی عناصر پیاده سازی شود.
با الگوی بازدیدکننده، افزودن یک عملیات جدید (مانند محاسبه محیط) مستلزم ایجاد یک کلاس بازدیدکننده جدید بدون تغییر کلاسهای شکل موجود است. این تضمین می کند که سیستم توسعه پذیر باقی می ماند و به اصل باز/بسته پایبند است. بیایید آن را اجرا کنیم:
from abc import ABC, abstractmethod
# Element interface
class Shape(ABC):
@abstractmethod
def accept(self, visitor):
pass
# Concrete elements
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def accept(self, visitor):
return visitor.visit_circle(self)
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def accept(self, visitor):
return visitor.visit_rectangle(self)
# Visitor interface
class ShapeVisitor(ABC):
@abstractmethod
def visit_circle(self, circle):
pass
@abstractmethod
def visit_rectangle(self, rectangle):
pass
# Concrete visitor
class AreaVisitor(ShapeVisitor):
def visit_circle(self, circle):
return 3.14 * circle.radius * circle.radius
def visit_rectangle(self, rectangle):
return rectangle.width * rectangle.height
و حالا بیایید از این برای محاسبه مساحت دایره و مستطیل استفاده کنیم:
# Running the code
circle = Circle(5)
rectangle = Rectangle(4, 6)
area_visitor = AreaVisitor()
print(circle.accept(area_visitor))
print(rectangle.accept(area_visitor))
این به ترتیب مساحت های صحیح دایره و مستطیل را به ما می دهد:
78.5
24
نتیجه
در طول این مقاله، 9 الگوی طراحی رفتاری حیاتی را مشاهده کردیم که هر کدام به چالشها و سناریوهایی که معمولاً در طراحی نرمافزار با آنها مواجه میشوند، پاسخ میدهد. این الگوها، اعم از زنجیره مسئولیت، که رسیدگی به درخواست را غیرمتمرکز می کند بازدید کننده الگو، که مکانیزمی را برای افزودن عملیات جدید بدون تغییر کلاسهای موجود فراهم میکند، راهحلهای قوی برای تقویت مدولار بودن، انعطافپذیری و قابلیت نگهداری در برنامههای ما ارائه میدهد.
یادآوری این نکته ضروری است که در حالی که الگوهای طراحی راه حل های آزمایش شده و آزمایش شده را برای مشکلات تکراری ارائه می دهند، کاربرد عاقلانه آنها بسیار مهم است. استفاده بیش از حد یا استفاده نادرست از آنها گاهی اوقات می تواند پیچیدگی غیر ضروری ایجاد کند. بنابراین، همیشه نیازهای خاص پروژه خود را در نظر بگیرید و الگویی را انتخاب کنید که به بهترین شکل با بیان مشکل شما همخوانی دارد.
منتشر شده در 1402-12-26 12:40:03