مقدمه:

الگوهای طراحی به چه چیزهایی اطلاق می‌شوند؟ منظور از الگوهای طراحی رفتاری، الگوهای رفتاری پیشرفته و الگوی طراحی یگانه در پایتون چیست؟ و چگونه با بهره‌گیری از این الگوهای طراحی می‌توان به شکلی ساده‌تر و شفاف‌تر، کدنویسی کرد؟

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

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

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

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

با این حال، به این علت که پایتون یک زبان بسیار قدرتمند و انعطاف پذیر است، ما به قوانین (یا الگوهایی) برای برنامه نویسی در آن نیاز داریم. بنابراین، اجازه دهید ببینیم که منظور از الگوهای طراحی چیست و چگونه این الگوها با زبان برنامه نویسی پایتون ارتباط پیدا می‌کنند.

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

یک الگوی طراحی به چه چیزی گفته می‌شود؟

همه چیز از Gang of Four ) GOF ) شروع شد. اگر با این عبارت آشنا نیستید، یک سرچ سریع درباره آن انجام دهید.

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

  • برای یک واسط کاربری و نه پیاده‌ سازی، برنامه نویسی کنید.
  • ترکیب شیء را بر وراثت آن ترجیح دهید.

اکنون اجازه دهید که از چشم انداز برنامه نویسان پایتون، به این دو اصل نگاهی داشته باشیم.

اصول برنامه نویسی در پایتون

اصل اول Design Pattern: به منظور ایجاد یک واسط کاربری برنامه نویسی کنید و نه پیاده سازی

چند لحظه به نوع دهی اردکی فکر کنید. در پایتون python ما علاقه نداریم که واسط‌های کاربری و کلاس‌های برنامه نویسی متناظر با این واسط‌ها را تعریف کنیم، این‌طور نیست؟ با این حال، این گفته به این معنا نیست که ما درباره واسط‌ها فکر نمی‌کنیم، بلکه واقعیت این است که با نوع دهی اردکی، ما تمام مدت در حال انجام همین کار هستیم.

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

اگر ظاهر چیزی شبیه به اردک است و مثل اردک هم کواک کواک می‌کند، پس این چیز اردک است!

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

پس، آیا این شیء کواک کواک می‌کند؟ اجازه بده کواک کواک کند!

try:    bird.quack()except AttributeError:    self.lol()

آیا ما برای اردک خود یک واسط کاربری تعریف کردیم؟ نه! آیا ما برای واسط کاربری به جای پیاده سازی برنامه نویسی کردیم؟ بله!

اصل دوم: ترکیب اشیاء را به وراثت آنها ترجیح دهید

مبنای این اصل آن است که ما کلاس‌ها/ زیرکلاس‌های کمتری رادر مقایسه با بسته بندی یک کلاس (یا در اغلب مواقع چند کلاس) در یک کلاس دیگر، ایجاد کنیم. بنابراین به جای انجام این کار:

try:    bird.quack()except AttributeError:    self.lol()

می‌توانیم چنین کاری انجام دهیم:

try:    bird.quack()except AttributeError:    self.lol()

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

در پایتون python ترکیب، یک امر ذاتی و ظریف است.

الگوهای طراحی در پایتون: الگوهای رفتاری

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

Iterator

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

زنجیره مسئولیت

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

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

class ContentFilter(object):    def __init__(self, filters=None):        self._filters = list()        if filters is not None:            self._filters += filters     def filter(self, content):        for filter in self._filters:            content = filter(content)        return content filter = ContentFilter([                offensive_filter,                ads_filter,                porno_video_filter])filtered_content = filter.filter(content)

command

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

یک نمونه ساده و پرکاربرد از این design pattern به صورت زیر است:

class RenameFileCommand(object):    def __init__(self, from_name, to_name):        self._from = from_name        self._to = to_name     def execute(self):        os.rename(self._from, self._to)     def undo(self):        os.rename(self._to, self._from) class History(object):    def __init__(self):        self._commands = list()     def execute(self, command):        self._commands.append(command)        command.execute()     def undo(self):        self._commands.pop().undo() history = History()history.execute(RenameFileCommand('docs/cv.doc', 'docs/cv-en.doc'))history.execute(RenameFileCommand('docs/cv1.doc', 'docs/cv-bg.doc'))history.undo()history.undo()

الگوهای طراحی در پایتون: الگوهای طراحی creational

اجازه دهید با این موضوع شروع کنیم که چرا الگوهای creational به طور گسترده در پایتون مورد استفاده قرار نمی‌گیرند؟ پاسخ این است که این الگوها، به ذات دینامیک این زبان برنامه نویسی برمی‌گردند.

واقعیت این است که در پایتون یک کارخانه جاساز شده است و این گفته به آن معناست که زبان برنامه نویسی پایتون، به خودی خود، ما را به تمام انعطاف پذیری مورد نیاز برای ایجاد اشیاء به روشی کاملا ظریف مجهز می‌کند و ما به ندرت نیاز داریم که چیزی مانند singleton design pattern یا Factory را پیاده سازی نماییم.

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

الگوهای طراحی در پایتون: الگوهای طراحی یگانه یا singleton design pattern

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

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

class Logger(object):    def __new__(cls, *args, **kwargs):        if not hasattr(cls, '_logger'):            cls._logger = super(Logger, cls                    ).__new__(cls, *args, **kwargs)        return cls._logger

در این نمونه، Logger  یک singleton design pattern محسوب می‌شود.

جایگزین‌های استفاده از الگوهای طراحی یگانه در پایتون، به صورت موارد زیر هستند:

  • استفاده از ماژول
  • ایجاد یک نمونه در سطوح بالایی اپلیکیشن، شاید در فایل config
  • رد کردن نمونه به هر شیئی که به آن نیاز دارد.

تزریق وابستگی یا DEPENDENCY INJECTION

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


class Command:     def __init__(self, authenticate=None, authorize=None):        self.authenticate = authenticate or self._not_authenticated        self.authorize = authorize or self._not_autorized     def execute(self, user, action):        self.authenticate(user)        self.authorize(user, action)        return action() if in_sudo_mode:    command = Command(always_authenticated, always_authorized)else:    command = Command(config.authenticate, config.authorize)command.execute(current_user, delete_user_action)

در این مثال، ما متدهای authenticator  و authorizer  را در کلاس Command تزریق کردیم. تمام کاری که کلاس Command به آن احتیاج دارد، آن است که این متدها را به صورت موفقیت آمیز و بدون درگیر شدن با جزئیات پیاده سازی، اجرا نماید. به این صورت، ما می‌توانیم کلاس Command را با هر آنچه از مکانیزم‌های authentication و authorization که قصد داریم در زمان اجرا استفاده کنیم، به کار ببریم.

نشان دادیم که چگونه وابستگی‌ها را از طریق سازنده تزریق کنیم، اما ما به سادگی می‌توانیم همین وابستگی‌ها را با تنظیم مستقیم ویژگی‌های شیء نیز تزریق نماییم که پتانسیل بیشتری از زبان برنامه نویسی را در اختیار ما قرار می‌دهد:

command = Command() if in_sudo_mode:    command.authenticate = always_authenticated    command.authorize = always_authorizedelse:    command.authenticate = config.authenticate    command.authorize = config.authorizecommand.execute(current_user, delete_user_action)

الگوهای طراحی در پایتون phyton: الگوهای طراحی ساختاری

FACADE

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

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

Facade یکی از الگوهای طراحی بسیار ظریف در پایتون است و یک راه عالی برای سرراست کردن واسط کاربری به شمار می‌رود. نمونه‌ای از facade design pattern را در ادامه مشاهده می‌کنید:

class Car(object):     def __init__(self):        self._tyres = [Tyre('front_left'),                             Tyre('front_right'),                             Tyre('rear_left'),                             Tyre('rear_right'), ]        self._tank = Tank(70)     def tyres_pressure(self):        return [tyre.pressure for tyre in self._tyres]     def fuel_level(self):        return self._tank.level

 

ADAPTER

در حالی که در میان الگوهای طراحی در پایتون، facadeها برای ساده سازی واسط کاربری مورد استفاده قرار می‌گیرند، کار Adapterها تماما به تغییر واسط کاربری مربوط می‌شود. فرض می‌کنیم که شما یک متد کاری برای ورود اطلاعات به یک مقصد خاص را در دست دارید. متد شما انتظار دارد که مقصد، یک متد write() در اختیار داشته باشد:

def log(message, destination):    destination.write('[{}] - {}'.format(datetime.now(), message))

این کد، یک متد خوش ساختار با تزریق وابستگی است که توسعه پذیری را ممکن می‌سازد. فرض می‌کنیم که شما می‌خواهید به جای فایل، به برخی از سوکت‌های UDP ورود کنید، شما می‌دانید که چطور باید این سوکت را باز کنید، اما مشکل این است که شیء socket فاقد متد write() است. در این وضعیت، شما به یک adapter نیاز پیدا می‌کنید.

import socket class SocketWriter(object):     def __init__(self, ip, port):        self._socket = socket.socket(socket.AF_INET,                                     socket.SOCK_DGRAM)        self._ip = ip        self._port = port     def write(self, message):        self._socket.send(message, (self._ip, self._port)) def log(message, destination):    destination.write('[{}] - {}'.format(datetime.now(), message)) upd_logger = SocketWriter('1.2.3.4', '9999')log('Something happened', udp_destination)

DECORATOR

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

def execute(user, action):    self.authenticate(user)    self.authorize(user, action)    return action()

در اینجا مسئله آن است که تابع execute بسیار بیشتر از اجرای یک چیز، کار انجام می‌دهد. ما در این قطعه کد، از اصل تک مسئولیتی پیروی نکرده‌ایم. به همین دلیل، نوشتن کد به صورت زیر می‌تواند جایگزین شود:

def execute(action):    return action()

ما می‌توانیم هر کاربرد authorization و authentication را در جای دیگری، مثل یک decorator پیاده سازی کنیم:

def execute(action, *args, **kwargs):    return action() def autheticated_only(method):    def decorated(*args, **kwargs):        if check_authenticated(kwargs['user']):            return method(*args, **kwargs)        else:            raise UnauthenticatedError    return decorated def authorized_only(method):    def decorated(*args, **kwargs):        if check_authorized(kwargs['user'], kwargs['action']):            return method(*args, **kwargs)        else:            raise UnauthorizeddError    return decorated execute = authenticated_only(execute)execute = authorized_only(execute)

اکنون متد execute():

  • راحت خوانده می‌شود.
  • تنها یک کار انجام می‌دهد.
  • با متد authentication، decorate شده است.
  • با متد authorization ، decorate شده است.

ما می‌توانیم دقیقا همین کد را با نحو  decorator یکپارچه پایتون بنویسیم:

def autheticated_only(method):    def decorated(*args, **kwargs):        if check_authenticated(kwargs['user']):            return method(*args, **kwargs )        else:            raise UnauthenticatedError    return decorated  def authorized_only(method):    def decorated(*args, **kwargs):        if check_authorized(kwargs['user'], kwargs['action']):            return method(*args, **kwargs)        else:            raise UnauthorizedError    return decorated  @authorized_only@authenticated_onlydef execute(action, *args, **kwargs):    return action()
نکته
دوره پیشنهادی: آموزش رایگان پایتون

نتیجه‌گیری

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

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