اصل جایگزینی لیسکوف (Liskov Substitution Principle یا LSP) یکی از اصول SOLID است که در طراحی شیگرا نقش مهمی دارد. این اصل تأکید میکند که اگر یک کلاس فرزند از کلاس والد ارثبری میکند، باید بتواند بدون ایجاد اختلال در عملکرد برنامه، جایگزین کلاس والد شود.
در این مقاله، مفهوم LSP را توضیح میدهیم و با مثالهای عملی و منطقی در پایتون، هم دلایل نقض این اصل و هم راهحلهای درست را بررسی میکنیم.
تعریف ساده LSP در پایتون
اصل LSP بیان میکند که زیرکلاسها باید بتوانند بدون تغییر رفتار قابلمشاهده برنامه، جایگزین کلاس والد شوند.
در غیر این صورت، ما با مشکلاتی مثل وابستگیهای شدید و رفتارهای غیرمنتظره روبهرو خواهیم شد.
LSP به زبان ساده:
اگر کلاس والد شما یک قرارداد (interface) مشخص تعریف کند، همه زیرکلاسها باید به آن قرارداد پایبند باشند و رفتارشان نباید از والد منحرف شود.
مثال: نقض LSP در سیستم کاربری
فرض کنید یک سیستم مدیریت کاربران داریم. کاربران میتوانند دو نوع باشند:
- کاربران معمولی
- مدیران (Admin)
کلاس پایه User رفتار مشترک همه کاربران را مشخص میکند:
class User:
def __init__(self, username):
self.username = username
def access_level(self):
return "Basic Access"
تا اینجا مشکلی وجود ندارد. این کلاس تنها یک رفتار عمومی (access_level) برای کاربران معمولی ارائه میدهد.
در کلاس فرزند (Admin)، رفتار اضافهای مانند حذف کاربران اضافه شد:
class Admin(User):
def access_level(self):
return "Admin Access"
def delete_user(self, user):
print(f"User {user.username} deleted by {self.username}")
مشکل کجا پیش میآید؟
مشکل زمانی رخ میدهد که در کلاس والد (User) متدی مانند delete_user تعریف کنیم، اما این متد برای تمام کاربران منطقی نباشد:
class User:
def __init__(self, username):
self.username = username
def delete_user(self, user):
raise NotImplementedError("Only admins can delete users.")
این طراحی مشکلساز است، زیرا:
عدم تطابق رفتار: اگر یک شیء از کلاس
Userیا دیگر زیرکلاسهای غیرمدیر (مثلاً یک کاربر معمولی) به متدdelete_userدسترسی پیدا کند، برنامه دچار خطا میشود (زیراraise NotImplementedErrorاجرا میشود). این رفتار نقضکنندهی LSP است، زیرا کلاس فرزند نمیتواند بهصورت شفاف و کامل جایگزین کلاس والد شود.اصل LSP میگوید که همهی زیرکلاسها باید رفتار کلاس والد را حفظ کنند. در اینجا، کلاس
Adminرفتاری دارد که کلاس والد نمیتواند ارائه دهد، و این تفاوت باعث عدم جایگزینی صحیح میشود.رفتار غیربدیهی: وجود متدی مانند
delete_userدر کلاس والد باعث ایجاد سردرگمی میشود. کاربری که کد را میخواند، انتظار دارد تمام زیرکلاسها از این متد پشتیبانی کنند. اما در اینجا، این متد برای کاربران معمولی تعریف نشده و عملاً برای آنها بیمعنی است.

راهحل صحیح
بهترین راه برای رفع این مشکل، حذف متد delete_user از کلاس والد است. بهجای آن، این رفتار فقط در کلاس Admin تعریف میشود، جایی که منطقی است:
class User:
def __init__(self, username):
self.username = username
def access_level(self):
return "Basic Access"
class Admin(User):
def access_level(self):
return "Admin Access"
def delete_user(self, user):
print(f"User {user.username} deleted by {self.username}")
در این طراحی:
- کلاس
Adminفقط رفتاری اضافه میکند و رفتار کلاس والد (User) را تغییر نمیدهد. - کلاس والد فقط رفتارهایی را تعریف میکند که برای تمام زیرکلاسها منطقی و مشترک است.
چرا این تغییر LSP را رعایت میکند؟
اگر از این طراحی استفاده کنید، زیرکلاسها همچنان میتوانند بهجای کلاس والد استفاده شوند، بدون اینکه رفتار غیرمنتظره یا خطا ایجاد شود.
مثال:
def show_access_level(user):
print(f"{user.username} has {user.access_level()}")
user = User("john_doe")
admin = Admin("admin_user")
show_access_level(user) # خروجی: john_doe has Basic Access
show_access_level(admin) # خروجی: admin_user has Admin Access
# اما فقط مدیران میتوانند کاربر حذف کنند:
admin.delete_user(user) # خروجی: User john_doe deleted by admin_user
در اینجا:
- کلاس
Adminبهطور کامل جایگزین کلاس والد (User) میشود، زیرا رفتارهای والد را بدون تغییر حفظ کرده است. - درعینحال، رفتار خاص حذف کاربران فقط در کلاس
Adminتعریف شده و در کلاس والد وجود ندارد، که از نقض LSP جلوگیری میکند.
چکیده مشکل و راهحل
مشکل اصلی:
متدی (delete_user) در کلاس والد تعریف شده بود که برای برخی از زیرکلاسها بیمعنی یا غیرمنطقی بود. این باعث نقض LSP شد، زیرا زیرکلاسها نمیتوانستند بهطور کامل جایگزین والد شوند.راهحل:
حذف متد غیرمشترک از کلاس والد و انتقال آن به زیرکلاس مناسب (Admin). این تغییر تضمین میکند که تمام رفتارهای تعریفشده در کلاس والد برای همه زیرکلاسها منطقی و معتبر است.
این طراحی نهتنها اصل LSP را رعایت میکند، بلکه کد را خواناتر، منطقیتر و قابلنگهداریتر میسازد.

