پرش به مطلب اصلی

یک پست با برچسب "ارث‌بری"

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

مشاهده تمام برچسب‌ها

هر چیزی که شبیه چیز دیگر است، جایگزین آن نیست

· ۱۰ دقیقه مطالعه
مهدی مالوردی
مهندس نرم‌افزار و نویسندهٔ این سایت

چند قطعه‌ی شبیه به هم که فقط یکی از آن‌ها واقعاً در جای قطعه‌ی اصلی می‌نشیند.

فرض کنید در یک سامانه‌ی پرداخت، کلاسی داریم که قرار است رسید پرداخت را تولید و ذخیره کند. برای همین، یک قرارداد ساده تعریف کرده‌ایم: هر «ذخیره‌ساز رسید» باید بتواند رسید را بگیرد و بدون غافل‌گیری آن را ذخیره کند.

interface ReceiptStore {
save(receipt: Receipt): void
}

حالا یک پیاده‌سازی معمولی داریم:

class DatabaseReceiptStore implements ReceiptStore {
save(receipt: Receipt) {
db.receipts.insert(receipt)
}
}

تا این‌جا همه‌چیز روشن است. اما بعدتر کسی می‌گوید: «ما یک نسخه‌ی فقط‌خواندنی هم لازم داریم. همان را هم از همین اینترفیس ارث ببریم.» و نتیجه چیزی شبیه این می‌شود:

class ReadOnlyReceiptStore implements ReceiptStore {
save(receipt: Receipt) {
throw new Error('This store is read-only')
}
}

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

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