وراثت و مفاهیم پیشرفته شی گرایی

تا اینجا یاد گرفته‌اید که چگونه با استفاده از کلاس (Class) و شی (Object) برنامه‌های خود را ساختارمندتر کنید. اما در پروژه‌های واقعی معمولاً کلاس‌هایی وجود دارند که ویژگی‌ها و رفتارهای مشترک زیادی دارند. در اینصورت آیا باید تمام این کدها را دوباره بنویسیم؟

خوشبختانه اینطور نیست. پایتون قابلیتی به نام وراثت (Inheritance) دارد که به ما اجازه می‌دهد یک کلاس جدید را بر اساس یک کلاس دیگر ایجاد کنیم. در این حالت کلاس جدید تمام ویژگی‌ها و متدهای کلاس قبلی را به ارث می‌برد و در صورت نیاز می‌تواند قابلیت‌های جدیدی نیز به آن اضافه کند.

به کمک وراثت:

  • از تکرار کد جلوگیری می‌شود.
  • نگهداری برنامه ساده‌تر خواهد شد.
  • توسعه پروژه‌های بزرگ بسیار راحت‌تر می‌شود.
  • ساختار برنامه حرفه‌ای‌تر خواهد بود.

فهرست مطالب

وراثت را با یک مثال واقعی درک کنیم

فرض کنید در حال طراحی یک نرم‌افزار مدرسه هستید. تمام افراد مدرسه دارای اطلاعات مشترکی هستند:

  • نام
  • سن
  • کد ملی

اما هر گروه ویژگی‌های مخصوص خود را نیز دارد. مثلا گروه دانش‌آموزان دارای اطلاعاتی مانند پایه تحصیلی و معدل هستند. در صورتیکه گروه معلمان دارای اطلاعات دیگری مانند رشته تدریس و حقوق هستند.

اگر بخواهیم برای هرکدام یک کلاس جداگانه بنویسیم، مجبور می‌شویم اطلاعات مشترک را چندین بار تکرار کنیم.

مثلاً:

class Student:
    name
    age
    national_id
    grade
    average


class Teacher:
    name
    age
    national_id
    subject
    salary

مشاهده می‌کنید که سه ویژگی اول در هر دو کلاس تکرار شده‌اند. در اینجا ما باید از وراثت استفاده کنیم.

کلاس والد (Parent) و کلاس فرزند (Child)

ابتدا یک کلاس کلی به نام Person می‌سازیم. تمام ویژگی‌های مشترک را داخل آن قرار می‌دهیم. سپس کلاس‌های Student و Teacher را از روی آن ایجاد می‌کنیم.

             Person
          /          \
     Student      Teacher

در این حالت:

  • Student تمام ویژگی‌های Person را دارد.
  • Teacher نیز تمام ویژگی‌های Person را دارد.
  • هر کدام می‌توانند ویژگی‌های مخصوص خودشان را نیز اضافه کنند.

به همین دلیل به Person می‌گوییم کلاس والد (Parent Class) و به Student و Teacher می‌گوییم کلاس فرزند (Child Class)

ایجاد وراثت در پایتون

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

class Parent:
    pass


class Child(Parent):
    pass

اکنون کلاس Child تمام اعضای Parent را در اختیار دارد.

یک مثال عملی

فرض کنید یک کلاس برای حیوانات داریم.

class Animal:
    def speak(self):
        print("Animal is speaking")

اکنون یک کلاس جدید ایجاد می‌کنیم.

class Dog(Animal):
    pass

و یک شی از آن می‌سازیم.

dog = Dog()
dog.speak()

خروجی:

Animal is speaking
 

توجه کنید که متد speak() داخل کلاس Dog نوشته نشده است، اما چون Dog از Animal ارث برده، به این متد دسترسی دارد.

چه چیزهایی به ارث می‌رسند؟

وقتی یک کلاس از کلاس دیگری ارث می‌برد، تقریباً همه اعضای عمومی آن را در اختیار خواهد داشت، از جمله:

  • ویژگی‌ها (Attributes)
  • متدها (Methods)
  • سازنده (__init__) (در شرایطی که در کلاس فرزند بازنویسی نشده باشد)
  • سایر اعضای عمومی کلاس

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

ساخت کلاس والد و فرزند با سازنده (__init__)

در مثال‌های قبلی دیدیم که کلاس فرزند می‌تواند متدهای کلاس والد را به ارث ببرد. اما در پروژه‌های واقعی، کلاس‌ها معمولاً دارای سازنده (__init__) هستند تا هنگام ایجاد شی، اطلاعات اولیه را دریافت و ذخیره کنند. بیایید یک مثال واقعی را بررسی کنیم.

کلاس والد

ابتدا یک کلاس به نام Person می‌سازیم.

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show_info(self):
        print("Name:", self.name)
        print("Age:", self.age)

این کلاس دو ویژگی دارد: name و age و یک متد برای نمایش اطلاعات.

ساخت کلاس فرزند

اکنون می‌خواهیم کلاس Student را از روی Person ایجاد کنیم.

class Student(Person):
    pass

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

student = Student("Ali", 18)
student.show_info()

خروجی:

Name: Ali
Age: 18

دقت کنید که کلاس Student هیچ متدی ندارد، اما چون از Person ارث برده است، هم سازنده (__init__) و هم متد show_info() را در اختیار دارد.

اتفاقی که پشت صحنه رخ می‌دهد

زمانی که این دستور اجرا می‌شود:

student = Student("Ali", 18)

پایتون ابتدا بررسی می‌کند که آیا کلاس Student دارای متد __init__ است یا خیر. از آنجا که چنین متدی وجود ندارد، به کلاس والد یعنی Person مراجعه می‌کند و سازنده‌ی آن را اجرا می‌کند. به همین دلیل مقادیر name و age به‌درستی مقداردهی می‌شوند.

افزودن ویژگی جدید به کلاس فرزند

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

class Student(Person):

    def __init__(self, name, age, grade):
        self.grade = grade

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

تابع super() در پایتون

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

اولین مثال با super()

کلاس والد Person را دوباره در نظر بگیرید.

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show_info(self):
        print("Name:", self.name)
        print("Age:", self.age)

اکنون کلاس دانش‌آموز را می‌نویسیم.

class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade

به این خط دقت کنید.

super().__init__(name, age)

این دستور باعث می‌شود سازنده کلاس Person اجرا شود و ویژگی‌های name و age مقداردهی شوند. سپس ویژگی جدید grade نیز به کلاس فرزند اضافه می‌شود.

ایجاد شی

student = Student("Ali", 18, 12)
student.show_info()
print("Grade:", student.grade)

خروجی:

Name: Ali
Age: 18
Grade: 12
 

چرا از super() استفاده می‌کنیم؟

فرض کنید کلاس والد دارای ۱۰ ویژگی باشد. اگر از super() استفاده نکنیم، مجبور هستیم تمام آن‌ها را دوباره در کلاس فرزند بنویسیم.

مثلاً:

self.name = name
self.age = age
self.address = address
self.phone = phone
...

این کار چند مشکل ایجاد می‌کند:

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

اما با یک خط کد:

super().__init__(...)

تمام این مقداردهی‌ها توسط کلاس والد انجام می‌شوند.

یک مثال واقعی‌تر

فرض کنید در یک شرکت اطلاعات تمام کارکنان در کلاس Employee ذخیره می‌شود.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

اکنون می‌خواهیم کلاس برنامه‌نویس را ایجاد کنیم.

class Programmer(Employee):
    def __init__(self, name, salary, language):
        super().__init__(name, salary)
        self.language = language

ایجاد شی:

programmer = Programmer(
    "Sara",
    80000,
    "Python"
)

print(programmer.name)
print(programmer.salary)
print(programmer.language)

خروجی:

Sara
80000
Python
نکته مهم

استفاده از super() فقط به سازنده (__init__) محدود نیست. با استفاده از آن می‌توان هر متدی از کلاس والد را نیز فراخوانی کرد. این موضوع زمانی اهمیت پیدا می‌کند که بخواهیم متدی را در کلاس فرزند تغییر دهیم اما همچنان بخشی از رفتار کلاس والد را حفظ کنیم. این مفهوم را بازنویسی متدها (Method Overriding) می‌نامند که در بخش بعدی با مثال‌های کاربردی آن آشنا خواهیم شد.

بازنویسی متدها (Method Overriding)

گاهی اوقات کلاس فرزند می‌خواهد یکی از متدهای کلاس والد را به شکل متفاوتی اجرا کند. در این حالت همان متد را دوباره داخل کلاس فرزند می‌نویسیم. به این کار بازنویسی متد یا Method Overriding گفته می‌شود.

به بیان ساده کلاس فرزند می‌تواند رفتار یک متد را تغییر دهد، بدون اینکه سایر قابلیت‌های کلاس والد را از دست بدهد.

اولین مثال

فرض کنید کلاس والد دارای متدی برای تولید صدا باشد.

class Animal:
    def speak(self):
        print("Some animal sound")

حالا کلاس سگ را ایجاد می‌کنیم.

class Dog(Animal):
    def speak(self):
        print("Woof Woof")

و سپس:

dog = Dog()
dog.speak()

خروجی:

Woof Woof

چه اتفاقی افتاد؟

کلاس Dog متدی با همان نام speak() ایجاد کرده است. بنابراین زمانی که این دستور اجرا می‌شود:

dog.speak()

پایتون ابتدا کلاس Dog را بررسی می‌کند. چون متد موردنظر در کلاس فرزند وجود دارد، دیگر به کلاس والد مراجعه نمی‌کند. به همین دلیل خروجی "Woof Woof" نمایش داده می‌شود.

مثال دوم

class Bird:
    def move(self):
        print("Bird is moving")

کلاس فرزند:

class Eagle(Bird):
    def move(self):
        print("Eagle is flying")

اجرای برنامه:

eagle = Eagle()
eagle.move()

خروجی:

Eagle is flying
 

آیا متد کلاس والد حذف می‌شود؟

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

استفاده از متد والد و فرزند به‌صورت هم‌زمان

گاهی اوقات نمی‌خواهیم متد کلاس والد را کاملاً حذف کنیم. بلکه می‌خواهیم ابتدا کارهای کلاس والد انجام شود و سپس قابلیت‌های جدیدی اضافه کنیم. در اینجا دوباره super() به کمک ما می‌آید.

class Animal:
    def speak(self):
        print("Animal makes a sound")

کلاس فرزند:

class Dog(Animal):
    def speak(self):
        super().speak()
        print("Dog says Woof")

اجرای برنامه:

dog = Dog()
dog.speak()

خروجی:

Animal makes a sound
Dog says Woof

نکته مهم

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

یک مثال واقعی

فرض کنید در حال طراحی یک فروشگاه اینترنتی هستیم. تمام محصولات دارای یک متد برای نمایش اطلاعات هستند.

class Product:
    def show_info(self):
        print("Product Information")

اکنون کلاس لپ‌تاپ را ایجاد می‌کنیم.

class Laptop(Product):
    def show_info(self):
        super().show_info()
        print("Category: Laptop")
        print("Brand: Lenovo")

اجرای برنامه:

laptop = Laptop()
laptop.show_info()

خروجی:

Product Information
Category: Laptop
Brand: Lenovo

در این مثال، اطلاعات عمومی محصول توسط کلاس والد نمایش داده می‌شود و سپس اطلاعات اختصاصی لپ‌تاپ توسط کلاس فرزند اضافه می‌شود.

چه زمانی از Method Overriding استفاده می‌کنیم؟

بازنویسی متدها زمانی کاربرد دارد که کلاس فرزند بخواهد رفتار یک متد را متناسب با نیاز خود تغییر دهد، بدون اینکه ساختار کلی کلاس والد را از بین ببرد. از این قابلیت در موارد زیر بسیار استفاده می‌شود:

  • تغییر نحوه نمایش اطلاعات
  • تغییر نحوه محاسبات
  • افزودن قابلیت‌های جدید به متدهای موجود
  • سفارشی‌سازی رفتار کلاس‌های فرزند
نکته

Method Overriding با Method Overloading متفاوت است. در زبان‌هایی مانند Java یا C++ می‌توان چند متد با یک نام ولی پارامترهای متفاوت تعریف کرد که به آن Overloading گفته می‌شود. اما در پایتون، مفهوم Overloading به آن شکل پشتیبانی نمی‌شود و بیشتر با استفاده از پارامترهای پیش‌فرض (default arguments) یا آرگومان‌های متغیر (*args و **kwargs) پیاده‌سازی می‌شود.

چندریختی (Polymorphism) در پایتون

کلمه Polymorphism از دو واژه یونانی تشکیل شده است: Poly یعنی چند و Morph یعنی شکل

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

یک مثال ساده

فرض کنید سه حیوان داریم. همه آن‌ها می‌توانند صدا تولید کنند، اما صدای هر کدام متفاوت است.

class Dog:
    def speak(self):
        print("Woof")
 
class Cat:
    def speak(self):
        print("Meow")
 
class Cow:
    def speak(self):
        print("Moo")

هر سه کلاس دارای متدی به نام speak() هستند. اما خروجی آن‌ها متفاوت است.

استفاده از چندریختی

اکنون تابعی می‌نویسیم که نوع حیوان برایش مهم نباشد.

def animal_sound(animal):
    animal.speak()

حالا:

dog = Dog()
cat = Cat()
cow = Cow()

animal_sound(dog)
animal_sound(cat)
animal_sound(cow)

خروجی:

 
Woof
Meow
Moo
 

توجه کنیدکه تابع animal_sound() فقط یک دستور دارد:

animal.speak()

اما بسته به اینکه شیء از چه کلاسی باشد، خروجی تغییر می‌کند. اگر شیء از کلاس Dog باشد:

Woof

اگر از کلاس Cat باشد:

Meow

و اگر از کلاس Cow باشد:

Moo

در واقع، یک متد با یک نام ثابت وجود دارد، اما رفتار آن برای هر کلاس متفاوت است.

مثال واقعی‌تر

فرض کنید در حال طراحی نرم‌افزار پرداخت هستیم. روش‌های پرداخت مختلفی وجود دارند. همه دارای متدی به نام pay() هستند.

class CreditCard:
    def pay(self):
        print("Paid with credit card")
 
class BankTransfer:
    def pay(self):
        print("Paid by bank transfer")
 
class Cash:
    def pay(self):
        print("Paid with cash")

تابع پرداخت:

def process_payment(method):
    method.pay()

استفاده:

process_payment(CreditCard())
process_payment(BankTransfer())
process_payment(Cash())

خروجی:

Paid with credit card
Paid by bank transfer
Paid with cash

ارتباط Polymorphism با وراثت

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

مثلاً:

class Animal:
    def speak(self):
        print("Animal sound")
 
class Dog(Animal):
    def speak(self):
        print("Woof")
 
class Cat(Animal):
    def speak(self):
        print("Meow")

اکنون:

animals = [
    Dog(),
    Cat()
]

for animal in animals:
    animal.speak()

خروجی:

Woof
Meow

در این مثال، متغیر animal در هر بار اجرای حلقه به شیئی از کلاس متفاوت اشاره می‌کند، اما برنامه بدون هیچ شرط (if یا elif) متد مناسب را اجرا می‌کند.

مزایای Polymorphism

استفاده از چندریختی باعث می‌شود:

  • کدها ساده‌تر شوند.
  • نیاز به شرط‌های متعدد (if و elif) کاهش یابد.
  • اضافه کردن کلاس‌های جدید آسان‌تر باشد.
  • توسعه و نگهداری پروژه راحت‌تر شود.
  • برنامه انعطاف‌پذیرتر باشد.

✏️ تمرین ۱: ساخت سیستم کارکنان

یک کلاس والد به نام Employee ایجاد کنید که ویژگی‌های زیر را داشته باشد:
  • نام کارمند (name)
  • حقوق (salary)
سپس دو کلاس فرزند ایجاد کنید: Manager:
  • ویژگی اضافه: تعداد کارمندان زیرمجموعه (team_size)
Developer:
  • ویژگی اضافه: زبان برنامه‌نویسی (language)
برای مقداردهی ویژگی‌های کلاس والد از تابع super() استفاده کنید.

✏️ تمرین ۲: محصولات فروشگاه

یک کلاس والد به نام Product بسازید که شامل موارد زیر باشد:
  • نام محصول (name)
  • قیمت (price)
سپس دو کلاس فرزند ایجاد کنید: Laptop:
  • برند لپ‌تاپ (brand)
Phone:
  • سیستم عامل (os)
یک متد به نام show_info() ایجاد کنید و اطلاعات هر محصول را نمایش دهید.

✏️ تمرین ۳: استفاده از Method Overriding

یک کلاس والد به نام Vehicle ایجاد کنید. یک متد به نام: move() داخل آن قرار دهید که پیام: Vehicle is moving را چاپ کند. سپس سه کلاس فرزند ایجاد کنید:
  • Car
  • Bike
  • Airplane
متد move() را در هر کلاس بازنویسی کنید تا حرکت مخصوص خود را نمایش دهد.

✏️ تمرین ۴: سیستم پرداخت

سه کلاس ایجاد کنید:
  • CreditCard
  • PayPal
  • Cash
هر کلاس یک متد با نام: pay() داشته باشد. سپس یک لیست از روش‌های پرداخت ایجاد کنید و با استفاده از یک حلقه، متد pay() هر شیء را اجرا کنید. هدف تمرین: پیاده‌سازی مفهوم Polymorphism بدون استفاده از شرط‌ها.

✏️ تمرین ۵: تکمیل پروژه مدرسه

یک سیستم مدرسه طراحی کنید. کلاس والد: Person ویژگی‌ها:
  • name
  • age
کلاس‌های فرزند: Student
  • grade
Teacher
  • subject
یک متد به نام: introduce() ایجاد کنید و آن را در کلاس‌های فرزند بازنویسی کنید. در پایان، چند شیء از دانش‌آموز و معلم بسازید و اطلاعات آن‌ها را نمایش دهید.

کپسوله‌سازی (Encapsulation) در پایتون

کپسوله‌سازی (Encapsulation) یکی از اصول مهم شی‌گرایی است که به ما کمک می‌کند دسترسی به ویژگی‌ها و متدهای یک کلاس را کنترل کنیم. با استفاده از این مفهوم، می‌توانیم مشخص کنیم کدام اطلاعات به‌صورت عمومی در دسترس باشند و کدام بخش‌ها فقط از داخل خود کلاس قابل استفاده باشند.

گام بعدی : کپسوله سازی

برای شرکت در آزمون آماده‌اید؟

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

توابع در پایتون

توابع در پایتون اگر هنگام برنامه‌نویسی مجبور شوید یک قطعه کد را بارها تکرار کنید، برنامه شما طولانی، شلوغ و

ادامه مطلب »

مجموعه در پایتون

مجموعه (Set) در پایتون به زبان ساده، Set مجموعه‌ای از عناصر منحصربه‌فرد (Unique) است که ترتیب مشخصی ندارد و اجازه

ادامه مطلب »

تاپل در پایتون

تاپل (Tuple) در پایتون بعد از آشنایی با لیست (List)، نوبت به یکی دیگر از ساختارهای داده در پایتون یعنی

ادامه مطلب »

لیست در پایتون

لیست (List) در پایتون پرکاربردترین ساختار داده​ تا اینجا یاد گرفتید که هر متغیر فقط یک مقدار را در خود

ادامه مطلب »

آموزش نصب پایتون

آموزش نصب پایتون و ابزارهای موردنیاز (راهنمای کامل برای ویندوز) اگر تصمیم گرفته‌اید برنامه‌نویسی را یاد بگیرید، احتمالاً نام زبان

ادامه مطلب »