مفهوم شی گرایی object-oriented programming در پایتون چیست؟

شیگرایی (object-oriented programming یا OOP)
شی گرایی در پایتون، روشی برای ساختن برنامه ها با استفاده از اشیا است. اشیا ترکیبی از دادهها (ویژگیها) و رفتارها (متدها) هستند. مثلا اگر بخوایم یک ماشین تعریف کنیم، میتونیم یک کلاس به اسم car بسازیم که ویژگی هایی مثل رنگ و سرعت و متدهایی مثل حرکت یا توقف داشته باشد.
این ساختار کمک میکند مخصوصا وقتی پروژه بزرگ میشود، برنامه ها مرتبتر و قابل فهمتر باشد. (تعریف شی گرایی در ویکی پدیا)
اصول شی گرایی در پایتون
شی گرایی در پایتون بر پایه چهار اصل کلیدی طراحی شده است که شامل موارد زیر است:
- encapsulation
- inheritance
- polymorphism
- abstraction
در پایتون، همه چیز یک شی است و حتی توابع و کلاسها نیز از نوع object هستند.
کلاس ها با کلید واژه class تعریف می شوند و میتوان با استفاده از __init__ مقادیر اولیه را به ویژگیهای یک شی اختصاص داد.
پایتون همچنین از ویژگیهای خاصی مانند duck typing و dynamic binding بهره میبرد که آن را نسبت به زبانهای strongly typed در مدیریت اشیا منعطفتر میکند. (مستندات رسمی پایتون در مورد اشیا و کلاس ها)
مفهوم duck typing در پایتون
مفهوم duck typing یعنی به جای اینکه نوع یک شی (object) مهم باشد، رفتارش مهم است. به این معنی که اگر یک شی مثل اردک راه برود و صدای اردک دربیاورد، ما با آن مثل اردک رفتار میکنیم!
به زبان ساده، اگر یک شی متدی که ما نیاز داریم را دارد مثلا ()speak ، ما با خیال راحت این متد رو صدا میزنیم، حتی اگر ندانیم دقیقا از چه کلاسی آمده است، این باعث می شود کدها در پایتون خیلی انعطافپذیرتر و سادهتر نوشته شوند.
در پایتون که یک زبان dynamically typed محسوب میشود، مفهوم duck typing یک اصل کلیدی در طراحی نرمافزار به سبک EAFP (Easier to Ask Forgiveness than Permission) است.
بر اساس duck typing، ارثبری یا نوع کلاس اهمیتی ندارد؛ آنچه اهمیت دارد وجود متد یا ویژگی مورد نیاز در زمان اجراست.
این یعنی شما میتوانید با آبجکتهایی کار کنید که فقط رابط رفتاری مورد نیاز را دارند، بدون اینکه از یک کلاس پایه مشخص ارث برده باشند. این رویکرد باعث میشود برنامهنویس درگیر تایپچک های پیچیده نشود و کدی بنویسد که از polymorphism طبیعی زبان پایتون بهره میبرد، ولی در عین حال باید توجه داشت که مدیریت خطا در این روش باید دقیقتر انجام شود تا از بروز runtime error جلوگیری شود.
از مفاهیم پیشرفتهتر دیگر میتوان به multiple inheritance، mixin classes و استفاده از metaclasses اشاره کرد که در ساختارهای پیچیدهتر و طراحی framework برای راه اندازی پروژه های پایتون کاربرد دارند.
اصل polymorphism در پایتون
اصل polymorphism (چندریختی) یعنی اینکه چند کلاس مختلف میتوانند یک متد با اسم یکسان داشته باشند، ولی هر کدام به شکل و شیوه خود آن متد رو اجرا کنند.
مثلا اگه دو کلاس Dog و Cat داشته باشیم و هر کدام متدی به اسم ()speak داشته باشند، با اینکه اسم متد یکی است، ولی ()Dog.speak میگوید “woof” و ()Cat.speak میگوید “meow”.
این اصل در پایتون، کمک میکند بتوانیم با استفاده از یک اسم متد ثابت، با اشیا مختلف به شکلهای مختلف رفتار کنیم.
polymorphism در پایتون هم به صورت explicit (با ارثبری کلاسها) و هم به صورت implicit (با duck typing) پیادهسازی میشود.
در فرم کلاسیکی، کلاس والد یک interface یا متد عمومی تعریف میکند و کلاسهای فرزند آن را override میکند. ولی پایتون بهدلیل پویایی نوعها (dynamic typing) نیازی به interface رسمی ندارد.
شما میتوانید بدون inheritance صرفا متدی با اسم مشابه در چند کلاس مختلف تعریف کنید و از آنها در یک context مشترک استفاده کنید، که همون duck typing هست (اگر چیزی مثل ()speak دارد، پس حیوان است ).
در واقع، اصل polymorphism یا چندریختی به این معناست که میتوان از یک تابع یا متد با یک نام ثابت برای چند نوع شی مختلف استفاده کرد، بدون اینکه نیاز باشد نگران نوع دقیق آن اشیا باشیم.
مثلاً اگر چند کلاس مختلف متدی با نام یکسان داشته باشند، میتوان از همان نام متد برای کار با همه آنها استفاده کرد و انتظار داشت هر شی رفتار خاص خودش را اجرا کند. این ویژگی باعث میشود کدها انعطافپذیرتر و قابل توسعهتر باشند.
polymorphism در پایتون بهصورت ضمنی (implicit) پیادهسازی میشود و برخلاف زبانهای strongly typed، نیازی به تعریف رسمی interface یا overload کردن نیست.
در این زبان، آنچه اهمیت دارد نه نوع دقیق شی بلکه رفتار آن است. اگر شی موردنظر متدی با نام مورد انتظار داشته باشد، پایتون آن را اجرا میکند؛ حتی اگر کلاس آن هیچ ارتباطی با کلاسهای دیگر نداشته باشد. این ویژگی که به آن duck typing گفته میشود، به برنامهنویس اجازه میدهد بدون نگرانی از ساختار کلاسها، تنها بر اساس قابلیتهای آنها برنامهنویسی کند.
polymorphism در پایتون همچنین در ترکیب با inheritance و override کردن متدهای کلاس پایه در کلاسهای فرزند کاربرد گستردهتری پیدا میکند و امکان توسعهی نرمافزار بهصورت ماژولار، تستپذیر و قابل نگهداری را فراهم میسازد.
مفهوم strongly typed در زبان های برنامه نویسی
در زبانهای strongly typed، نوع دادهها اهمیت زیادی دارد و برنامهنویس نمیتواند بدون انجام تبدیل مناسب، از انواع مختلف دادهها با هم استفاده کند.
مثلا در این زبانها نمیتوان یک عدد را مستقیما با یک رشته (string) جمع کرد و اگر چنین کاری انجام شود، مفسر یا کامپایلر خطا خواهد داد.
این ویژگی باعث میشود که برنامهنویس مجبور باشد همیشه نوع دادهها را بهدرستی مدیریت کند. زبانهایی مانند Java یا Rust نمونههایی از strongly typed languages هستند که در آنها نوعدهی بسیار سختگیرانه اعمال میشود.
از دید حرفهای، زبانهای strongly typed دارای سیستم نوعدهی دقیقی هستند که از تبدیل ضمنی نوعها (implicit type coercion) جلوگیری میکند یا آن را محدود میسازد.
این زبانها توسعهدهنده را ملزم میکنند تا در زمان استفاده از انواع متفاوت، تبدیل نوع (type casting) را بهصورت صریح انجام دهد. این سختگیری مزایایی مانند کاهش خطاهای زمان اجرا، افزایش قابلیت اطمینان کد و بهبود تحلیل استاتیک را به همراه دارد.
زبانهایی مانند Java، Kotlin و Rust از جمله نمونههای کلاسیک strongly typed هستند.
اصل inheritance در پایتون
اصل inheritance یا وراثت در پایتون به این معنی است که یک کلاس میتواند ویژگیها (متغیرها) و رفتارها (متدها) را از کلاس دیگری به ارث ببرد. این کار باعث میشود که بتوانیم بدون نوشتن دوبارهی کدها، از آنچه در یک کلاس دیگر نوشته شده استفاده کنیم.
مثلا اگر یک کلاس پایه (مثل Animal) تعریف کنیم که یک متد speak دارد، میتوانیم کلاسهای دیگری مثل Dog یا Cat بسازیم که از Animal ارثبری میکنند و متد speak را با رفتار مخصوص خودشان تغییر میدهند یا از همان متد استفاده میکنند.
inheritance در پایتون از نوع کلاسیک single inheritance شروع میشود و تا سطوح پیچیدهتر مثل multiple inheritance hierarchical inheritance و حتی multilevel inheritance را پوشش میدهد.
در تعریف کلاسها، وراثت با قرار دادن نام کلاس پایه داخل پرانتز کلاس فرزند انجام میشود. کلاس فرزند تمام اعضای عمومی و محافظتشدهی کلاس پایه را به ارث میبرد و میتواند متدها یا ویژگیهای خاص خودش را نیز اضافه کند یا متدهای کلاس پایه را بازنویسی (override) کند.
در multiple inheritance، پایتون با استفاده از الگوریتم MRO (Method Resolution Order) تعیین میکند که کدام نسخه از یک متد در اولویت اجرا قرار گیرد. این ویژگی به برنامهنویس اجازه میدهد ساختارهایی پیچیده و چندلایه طراحی کند و منطق برنامه را به صورت ساخت یافته مدیریت نماید.
در پایتون، کلاس پایه معمولا به عنوان یک کلاس عمومی طراحی میشود که منطق کلی یا قابلیتهای مشترک بین چند کلاس را در خود جای میدهد. کلاسهای فرزند با ارثبری از این کلاس، میتوانند بدون تکرار کد، آن ویژگیها و متدها را داشته باشند.
اگر نیاز به بازنویسی (override) متدی از کلاس پایه باشد، کافیست در کلاس فرزند متدی با همان نام تعریف شود. در زمان اجرا، نسخهی جدید اجرا خواهد شد.
در وراثت چندگانه (multiple inheritance)، یک کلاس میتواند همزمان از چند کلاس دیگر ارثبری کند. مثلاً:
class A:
def show(self):
print("Class A")
class B:
def show(self):
print("Class B")
class C(A, B):
pass
در این مثال، کلاس C از هر دو کلاس A و B ارثبری کرده است. اگر متد ()show از کلاس C فراخوانی شود، پایتون بر اساس MRO ابتدا به دنبال ()show در کلاس A میگردد، سپس در B.
ترتیب جستجو توسط الگوریتم C3 linearization مشخص میشود که در کلاس با دستور C.__mro__ قابل مشاهده است.
از دیگر امکانات inheritance در پایتون میتوان به استفاده از تابع ()super اشاره کرد. این تابع در داخل متدهای کلاس فرزند برای دسترسی به متدهای کلاس پایه استفاده میشود. بهویژه در سناریوهایی که constructor کلاس پایه (__init__) باید در کلاس فرزند نیز اجرا شود، ()super ابزاری حیاتی است. مثلا:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
در این مثال، با استفاده از super().__init__(name) مطمئن میشویم که مقداردهی اولیهی ویژگیهای کلاس پایه نیز انجام شده است.
در کل، inheritance یکی از ارکان اصلی طراحی شیگرا در پایتون است که باعث افزایش قابلیت استفادهی مجدد از کد، تسهیل نگهداری پروژه و سادهسازی توسعهی نرمافزارهای بزرگ میشود. استفادهی هوشمندانه از این اصل، بهویژه در ترکیب با اصول دیگر مثل polymorphism و encapsulation، میتواند ساختار پروژه را کاملا ماژولار، منعطف و قابل گسترش کند.
اصل encapsulation در پایتون
اصل encapsulation یا کپسولهسازی به این معنی است که ما اطلاعات داخلی یک شی مثل متغیرها یا متدهای آن را مخفی میکنیم و فقط از طریق روشهایی مشخص مانند متدها یا propertyها به آنها دسترسی میدهیم.
این کار باعث میشود کسی نتواند مستقیما به اطلاعات داخلی شی دست بزند یا آنها را بهصورت ناخواسته تغییر دهد.
در پایتون، برای این کار معمولاً از یک خط زیر (_) یا دو خط زیر (__) قبل از نام متغیر استفاده میکنیم تا نشان دهیم آن عضو خصوصی یا محافظتشده است.
اصل encapsulation در پایتون برخلاف زبانهای strongly typed مانند جاوا یا ++C بهصورت غیرسختگیرانه (non-strict) پیادهسازی شده است.
پایتون بر پایهی فلسفهی “ما همه برنامهنویسان بالغ هستیم“، فقط از قراردادهای ظاهری برای محدود کردن دسترسی استفاده میکند. برای مثال، پیشوند یک زیرخط ( var_ ) نشاندهندهی متغیر protected است و نباید مستقیما خارج از کلاس به آن دسترسی پیدا کرد.
پیشوند دو زیرخط ( var__ ) نیز برای مخفیسازی نام استفاده میشود و با name mangling دسترسی مستقیم را سختتر میکند.
البته همچنان میتوان با object._ClassName__var به آن دسترسی داشت، ولی این کار توصیه نمیشود.
برای پیادهسازی واقعی encapsulation، معمولا از property decorators مثل property , @setter, @deleter استفاده میکنیم تا دسترسی کنترل شده به متغیرها فراهم کنیم.
این روش امکان اضافهکردن اعتبارسنجی، محدودسازی یا محاسبهی دینامیک مقدار را بدون تغییر API عمومی کلاس فراهم میسازد.
در مجموع، encapsulation نه تنها باعث محافظت از وضعیت داخلی اشیا میشود، بلکه باعث میشود وابستگی بین اجزای مختلف برنامه کاهش یابد، مدیریت خطاها سادهتر شود، و برنامهنویسی شیگرا حرفهایتر و قابلاعتمادتر گردد.
در ادامهی توضیح اصل encapsulation در پایتون، میتوان به برخی کاربردهای دقیقتر، رایجتر و همچنین نکات طراحی معماری اشاره کرد:
کپسولهسازی به ما این امکان را میدهد که تغییرات در منطق داخلی یک کلاس بدون تاثیر روی کدهایی که از آن کلاس استفاده میکنند انجام شود. به عبارت دیگر، encapsulation باعث پایداری رابط (interface) عمومی کلاس میشود، در حالی که پیادهسازی داخلی میتواند بدون نگرانی از شکستن عملکرد برنامههای وابسته، تغییر کند.
یکی از مهمترین ابزارهای پایتون برای پیادهسازی این اصل، property است. این قابلیت اجازه میدهد بدون آنکه کاربر کلاس متوجه شود، بهجای دسترسی مستقیم به یک متغیر، پشتصحنه از یک متد (getter یا setter) استفاده شود. مثال سادهای از این رویکرد:
class BankAccount:
def __init__(self, balance):
self.__balance = balance
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self.__balance = value
در این مثال، متغیر __balance خصوصی (private) در نظر گرفته شده و نمیتوان به آن مستقیماً دسترسی داشت. اما با استفاده از property@ و balance.setter@ بهصورت کنترلشده به آن دسترسی داده شده است. این یعنی ما میتوانیم هنگام تغییر مقدار، اعتبارسنجی انجام دهیم یا حتی لاگبرداری کنیم، بدون اینکه API برای کاربر تغییر کند.
همچنین encapsulation کمک میکند تا مفاهیم data hiding (مخفیسازی داده) و separation of concerns (تفکیک مسئولیتها) در طراحی سیستم رعایت شوند. این اصل به ویژه در برنامههایی با معماری چندلایه یا پروژههای تیمی اهمیت پیدا میکند، چون اعضای مختلف تیم تنها با رابط تعریفشده کلاس کار میکنند و نگران جزئیات پیادهسازی داخلی نیستند.
در پایتون حتی میتوان با استفاده از متاکلاسها یا descriptor ها، کپسولهسازیهای پیچیدهتری طراحی کرد. این رویکرد در مواقعی استفاده میشود که بخواهیم رفتار مشترک برای مجموعهای از کلاسها یا ویژگیها پیادهسازی کنیم که نیاز به کنترل دقیقتری روی فرآیند دسترسی و تنظیم مقدار دارند.
در نهایت، encapsulation صرفا یک ویژگی تکنیکی نیست، بلکه یک اصل طراحی کلیدی برای توسعه نرمافزارهای پایدار، قابل نگهداری و امن محسوب میشود. در کنار inheritance و polymorphism، این اصل پایهایترین ستون معماری شیگرا در پایتون است.
اصل abstraction در پایتون
اصل abstraction در برنامهنویسی شیگرا به این معنی است که ما فقط اطلاعات ضروری از یک شی را به بیرون نمایش میدهیم و جزئیات پیچیدهی پشت صحنه را مخفی میکنیم.
این کار باعث میشود استفاده از کلاسها و اشیا سادهتر شود. مثلا وقتی از یک ماشین استفاده میکنیم، فقط با پدال گاز، ترمز و فرمان سر و کار داریم، ولی نمیدانیم دقیقا در داخل موتور چه میگذرد، abstraction دقیقا همین ایده را در کدنویسی پیاده میکند.
abstraction در پایتون با استفاده از کلاسهای پایهی انتزاعی (abstract base classes) و متدهای انتزاعی (abstract methods) پیادهسازی میشود.
برای این کار از ماژول abc (مخفف abstract base class) استفاده میکنیم. کلاسهایی که از ABC ارثبری میکنند میتوانند شامل متدهایی باشند که فقط امضای متد (signature) را دارند، اما پیادهسازی ندارند. این متدها با decorator مخصوص abstractmethod@ تعریف میشوند و هر کلاس فرزند باید آنها را پیادهسازی کند، در غیر اینصورت آن کلاس نیز انتزاعی باقی میماند و قابل نمونهسازی (instantiate) نیست.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof"
در این مثال، کلاس Animal یک کلاس انتزاعی است که فقط مشخص میکند هر حیوان باید متدی به نام speak داشته باشد، ولی پیادهسازی آن را به کلاسهای فرزند (مثل Dog) میسپارد. این ساختار باعث میشود معماری سیستم بهصورت استاندارد و قابل پیشبینی باقی بماند و اطمینان حاصل شود که همه کلاسهای فرعی قرارداد تعریفشده را رعایت میکنند.
از abstraction بهصورت غیر رسمی نیز میتوان استفاده کرد؛ مثلا با طراحی کلاسهایی که فقط متدهای عمومی را در دسترس قرار میدهند و جزئیات داخلی را بدون استفاده از abc مخفی نگه میدارند. اما برای رعایت معماری لایهای، توسعهی ماژولار و پیادهسازی اصول SOLID، استفاده از کلاسهای انتزاعی توصیه میشود.
در نهایت، abstraction نهتنها باعث ساده تر شدن استفاده از اجزای نرمافزار میشود، بلکه امکان توسعهی مستقل اجزای سیستم، تستپذیری بالا و رعایت اصل جداسازی مسئولیتها را نیز فراهم میکند. این اصل، بهویژه در طراحی سیستمهای بزرگ، چارچوبها (frameworks) و توسعهی رابطهای عمومی (API) نقش بسیار کلیدی دارد.
abstraction به ما اجازه میدهد که «چه کاری باید انجام شود» را تعریف کنیم، نه «چگونه انجام شود».
این رویکرد باعث جداسازی کامل interface از implementation میشود. بهعنوان مثال، وقتی یک کلاس abstract base تعریف میکنیم که شامل چند متد انتزاعی است، ما فقط انتظارات و ساختار کلی رفتار آن کلاس را مشخص میکنیم. پیادهسازی جزئیات به کلاسهای فرزند واگذار میشود، بدون اینکه کلاس پایه از آنها اطلاع داشته باشد. این دقیقا مفهومی است که پشت اصل dependency inversion در اصول SOLID وجود دارد.
از لحاظ فنی، abstraction در پایتون با استفاده از abc.ABC و متدهای تزئینشده با @abstractmethod پیادهسازی میشود. اما در پروژههای پیچیدهتر، ممکن است علاوه بر این از abstract properties، abstract static methods و abstract class methods نیز استفاده شود. این موارد به ترتیب با دکوریتورهای ترکیبی مانند @property @abstractmethod، @staticmethod @abstractmethod و @classmethod @abstractmethod تعریف میشوند.
نمونهای از این ترکیبها به صورت زیر خواهد بود:
from abc import ABC, abstractmethod
class Repository(ABC):
@property
@abstractmethod
def connection(self):
pass
@classmethod
@abstractmethod
def get_instance(cls):
pass
این ساختار به ما امکان میدهد قراردادهای سطح بالا برای کلاسهایی که وظیفه اتصال به دیتابیس یا کار با دادهها را دارند، تعیین کنیم. در واقع abstraction در این سطح، کمک میکند تا لایههای مختلف پروژه مثل data access layer، business logic و user interface بدون وابستگی مستقیم به یکدیگر طراحی شوند.
از منظر تستنویسی (testability)، abstraction کمک میکند تا mockها یا stub هایی برای کلاسهای انتزاعی بسازیم و منطق لایههای بالاتر را بدون نیاز به پیادهسازی واقعی اجزای پایینتر تست کنیم. بهعنوان مثال، در یک تست واحد میتوان از یک کلاس ساختگی، که از یک کلاس abstract ارثبری میکند استفاده کرد تا عملکرد کلی ماژول تست شود.
در پروژههای مقیاسپذیر، abstraction نقش بنیادینی در طراحی plugin-based systems، factories، strategy patterns و service-oriented architecture ایفا میکند. این اصل کمک میکند وابستگی به پیادهسازیهای خاص کاهش پیدا کند و برنامه در مواجهه با تغییرات انعطافپذیرتر باقی بماند.
به طور خلاصه، abstraction نه فقط برای پنهان کردن پیچیدگیها، بلکه برای تعریف استانداردها، ایجاد سازگاری بین اجزا و ساخت معماری پایدار و تستپذیر استفاده میشود.
این اصل، همراه با encapsulation، inheritance و polymorphism، چهار ستون کلیدی object-oriented design در پایتون را تشکیل میدهد.
در این پست پارس وب سرور با مفهوم و اصول اصلی شی گرایی در پایتون آشنا شدیم.
از اینکه با ما در این پست همراه بودید سپاسگزاریم.
می توانید نسخه pdf این پست را از لینک زیر دانلود کنید.
دانلود فایل pdf پست “مفهوم شی گرایی در پایتون چیست؟ (به همراه مثال)”




