آموزش interface در پایتون و استفاده از کلاس اینترفیس

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

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

اگر با زبان‌هایی مثل جاوا، #c یا PHP کدنویسی کرده باشید، احتمالاً خیلی زیاد با این مفهوم سروکار داشته‌اید. در ادامه ابتدا مروری روی تعریف اینترفیس دارم و سپس به روش‌های پیاده‌سازی آن در پایتون می‌پردازم.

اینترفیس یا interface چیست؟

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

مثلاً در یک سیستم حمل‌و‌نقل، چندین نوع وسیله داریم؛ از جمله خودرو، دوچرخه و هواپیما. هر کدام از این وسیله‌ها ویژگی‌ها (attributes) و عملکردهای (methods) منحصربه‌فردی دارند، اما همهٔ آن‌ها باید توانایی «حرکت کردن» را داشته باشند.

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

از این پس، هر وسیلهٔ نقلیه‌ای که بخواهیم تعریف کنیم، باید از این اینترفیس پیروی کند. بنابراین مطمئنیم کلاس‌های خودرو، دوچرخه و هواپیما حتماً دارای متدی به نام move() هستند. درحالی‌که کدهای بدنهٔ‌ آن‌ها متفاوت است.

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

  • کاهش وابستگی‌ها (Loose Coupling): وابستگی بین بخش‌های مختلف سیستم به حداقل می‌رسد.
  • ایجاد قابلیت گسترش با کمترین میزان تغییر: افزودن قابلیت‌های جدید به برنامه با کمترین میزان تغییر در کدهای قبلی ممکن خواهد شد. این مورد به اصل Open Closed که از اصول SOLID است نیز کمک می‌کند.
  • تعیین استانداردهای مشترک: تعریف یک قرارداد برای کلاس‌ها باعث جلوگیری از عدم هماهنگی بین کلاس‌های مختلف (از یک نوع خاص) می‌شود. مثلاً مطمئنیم تمام کلاس‌های وسیله نقلیه با متد move() حرکت می‌کنند و نیازی نیست برای کلاس خودرو از یک متد و برای دوچرخه از نامی دیگر استفاده کنیم.

اگر با مفاهیم کلاس، ویژگی و متد آشنایی کاملی ندارید، می‌توانید به جلسهٔ آموزش کلاس در پایتون از دوره آموزش پایتون مراجعه کنید.

مفهوم اینترفیس در پایتون، برخلاف برخی زبان‌ها، به‌طور مستقیم وجود ندارد. اما می‌توانیم از مفاهیمی مثل کلاس‌های انتزاعی (Abstract Class) و ماژول abc برای پیاده‌سازی اینترفیس‌ها استفاده کنیم.

ایجاد اینترفیس در پایتون

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

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

فرض کنید در یک سیستم پرداخت آنلاین، قرار است چندین درگاه پرداخت مختلف داشته باشیم. احتمالاً می‌دانید که کدهای مربوط به اتصال به درگاه بانک x با بانک y متفاوت است.

روش روتین این است که برای هر درگاه یک کلاس ایجاد کرده و متدهای آن را پیاده‌سازی کنیم. فرض کنید در هر دو کلاس متدی به نام pay() برای نوشتن کدهای اتصال به درگاه تعریف کرده‌ایم.

ممکن است در آینده (توسط خودمان یا سایر اعضای تیم) کلاس جدیدی برای یک درگاه جدید ایجاد شود که نام متد اتصال به درگاهش do() باشد.

اکنون سه کلاس داریم که دو تا با متد pay() کار می‌کنند و دیگری با do()! این‌طوری نحوه مدیریت کدها و نیز توسعه برنامه سخت و در برنامه‌های بزرگ بسیار پیچیده می‌شود.

در چنین مواقعی، یک کلاس انتزاعی یا Abstract مثلاً به‌نام Gateway (درگاه) ایجاد می‌کنیم که متدی به نام pay() دارد. از این پس هر درگاه بانکی که قرار است در نرم‌افزار پیاده شود، باید از این کلاس ارث‌بری کند. در این‌صورت مجبور است متد pay() را پیاده‌سازی کند و ما مطمئنیم تمام درگاه‌ها این متد را دارا خواهند بود.

تفاوت کلاس interface با Abstract این است که در کلاس Abstract بدنهٔ برخی متدها ممکن است نوشته شوند اما در interface بدنهٔ هیچ یک از متدها پیاده‌سازی نمی‌شوند.

تعریف اینترفیس با ABC و abstract method

مثال پردازش پرداخت‌ها و درگاه‌های بانکی را در نظر بگیرید. برای شروع کلاس انتزاعی به نام PaymentGateway تعریف می‌کنیم. کلاس انتزاعی زیر ساختار اینترفیس پایتون را دارد.

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

کلاس ABC در پایتون مشخص می‌کند که این کلاس یک کلاس انتزاعی یا Abstract است و نمی‌توان از آن شیء ساخت. همچنین دکوریتور (Decorator) @abstractmethod قبل از نام هر متد مشخص می‌کند که این متد یک متد انتزاعی است و می‌بایست در کلاس فرزند حتماً بازنویسی شود.

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

class MellatGateway(PaymentGateway):
    def pay(self, amount):
        print(f"Processing Payment through MellatBank (amount: {amount})...")

اگر تلاش کنیم کلاسی طبق قرارداد PaymentGateway ایجاد کنیم که متد pay را نداشته باشد، با خطا مواجه خواهیم شد.

class SamanGateway(PaymentGateway):   # No Implementing pay() method
    def payment(self, amount):
        print(f"Processing Payment through SamanBank (amount: {amount})...")

saman = SamanGateway()
خطای متد پیاده‌سازی‌نشده از interface در کلاس فرزند
خطای متد پیاده‌سازی‌نشده از interface در کلاس فرزند

ایجاد شیء از کلاس interface پایتون

اگر یادتان باشد گفتم که امکان ساخت شیء از اینترفیس وجود ندارد. بنابراین اگر تلاش کنیم از PaymentGateway یک شیء بسازیم، هنگام اجرای کد با پایتون با خطا مواجه خواهیم شد:

gateway = PaymentGateway()
Traceback (most recent call last):
File "/home/omid/codes/sabzdanesh/interface.py", line 8, in <module>
gateway = PaymentGateway()
TypeError: Can't instantiate abstract class PaymentGateway without an implementation for abstract method 'pay'
خطای ایجاد شیء از اینترفیس در پایتون
خطای ایجاد شیء از اینترفیس در پایتون

برای استفاده از این سیستم، باید حتماً از کلاس‌های فرزند شیء را ایجاد کرده و استفاده کنیم.

class SamanGateway(PaymentGateway):
    def pay(self, amount):
        print(f"Processing Payment through SamanBank (amount: {amount})...")

gateway = SamanGateway()
gateway.pay(279000)

# Output:
# Processing Payment through SamanBank (amount: 279000)...

روش ۲: تعریف نامناسب interface در پایتون

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

ما می‌توانیم کلاسی بسازیم که بدنهٔ متدهای آن کدنویسی نشده باشند و صرفاً با کلمه کلیدی pass آن را رها کنیم. چیزی شبیه به کلاس کد زیر:

class InformalPaymentGateway:
    def pay(self, amount):
        pass

سپس کلاس‌های فرزند از این کلاس ارث‌بری کرده و متدهایش را پیاده‌سازی (یا در اصل override) می‌کنند.

class MellatGateway(InformalPaymentGateway):
    def pay(self, amount):
        print(f"Processing Payment through MellatBank (amount: {amount}).")

این روش هم از نظر اجرایی صحیح است اما دو مشکل اساسی دارد:

  • پایتون هیچ محدودیتی روی کلاس اینترفیس ندارد؛ بنابراین می‌توانیم از اینترفیس نیز شیء ایجاد کنیم. این مسئله با مفهوم اینترفیس در تضاد است، حتی اگر خودمان مراقب باشیم که هیچ شیء از آن ایجاد نشود.
  • هیچ اجباری برای بازنویسی متد pay() در فرزندان این کلاس وجود ندارد. یعنی ممکن است برخی از کلاس‌های فرزند این متد را پیاده‌سازی نکنند و هنگامی‌که آن را صدا می‌زنیم همان متد والد (که الآن صرفاً pass انجام می‌دهد)  صدا زده شود.

مجدداً تأکید کنم که این روش، روش مناسب و استانداردی برای تعریف اینترفیس در پایتون نیست. بهتر است از ارث‌بری از کلاس ABC و استفاده از @abstractmethod پیش از تعریف متدها استفاده کنید.

جمع‌بندی آموزش اینترفیس پایتون

اینترفیس‌ها یکی از مفاهیم کلیدی در طراحی نرم‌افزارهای شیءگرا هستند. interface در پایتون به‌طور مستقیم وجود ندارد اما با استفاده از abstract class و ماژول abc می‌توان آن‌ها را پیاده‌سازی کرد. به این منظور، لازم است کلاس اینترفیس از کلاس ABC درون ماژول abc ارث‌بری کرده و تمام متدهای آن نیز با دکوریتور @abstractmethod به‌صورت انتزاعی تعریف شوند.

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