برنامه نویسی و طراحی وب

اصل جایگزینی لیسکوف در اصول SOLID

اصول SOLID در برنامه نویسی شئ‌گرا چیست؟
اصل تک مسئولیتی (Single Responsibility Principle)
اصل باز – بسته (Open/Closed Principle)
اصل جایگزینی لیسکوف (Liskov Substitution Principle)
اصل تفکیک رابط‌ها (Interface Segregation Principle)
اصل وارونگی وابستگی (Dependency Inversion Principle)

اصل Liskov Substitution Principle در SOLID چیست؟

Liskov Substitution Principle یا به اختصار LSP که در فارسی به آن «اصل جایگزینی لیسکوف» یا «اصل جانشینی لیسکوف» گفته می‌شود، یک اصل مهندسی نرم‌افزار و اصل سوم از اصول طراحی SOLID است.

این اصل بیان می‌کند که اشیاء یک سوپرکلاس باید بدون تغییر در صحت برنامه با اشیاء یک زیرکلاس جایگزین شوند. به عبارت دیگر، اگر شئِ کلاسِ پدر قادر به انجام کاری باشد، پس همه اشیاء کلاسِ فرزند نیز باید قادر به انجام آن کار باشند. اگر کلاس فرزند قادر به انجام کاری نباشد که کلاس پدر می‌تواند آن را انجام دهد، اصل جایگزینی Loskov را نقض کرده‌ایم.

LSP مبتنی بر مفهوم زیرگروه سازیِ رفتاری است، که در آن زیرگروه‌ها با رفتارهایشان (behaviors) تعریف می شوند، نه با پیاده‌سازی (implementation).

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

یک مثال از اصل جایگزینی لیسکوف در زبان برنامه نویسی

در اینجا یک کلاس «وسیله نقلیه» به نام Vehicle داریم که دارای سه متد «روشن کردن موتور» startEngine() ، «حرکت کردن» doMovement() و «پرواز کردن» fly() است.

حالا این کلاس سه فرزند با عناوین «ماشین» Car ، «دوچرخه» Bicycle و «هواپیما» Airplane دارد.

حالا کلاس وسیله نقلیه با نام Vehicle را به صورت زیر داریم:

class Vehicle {
    public function startEngine() {
        return "Start the engine";
    }

    public function doMovement() {
        return "move";
    }

    public function doMovement() {
        return "fly";
    }
}

این کلاس دارای سه متد برای «روشن کردن موتور» ، «حرکت کردن» و «پرواز کردن است». پس اگر هواپیما فرزند این کلاس باشد، می‌تواند به صورت زیر نوشته شود:

class Airplane extends Vehicle {
}

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

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

class Airplane extends Vehicle {
    public function beep() {
        return "Beep beep!";
    }
}

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

حالا فرض کنید دوچرخه می‌خواهد فرزند کلاس Vehicle باشد. اما دوچرخه موتور ندارد و همچنین نمی‌تواند پرواز کند. کد زیر را در نظر بگیرید:

class Bicycle extends Vehicle {
    // The bicycle has no engine
	// leave it without implementation or throw exception
}

در اینجا دوچرخه نمی‌تواند دو متد از کلاسِ پدر را به ارث ببرد، پس اصل جایگزینی لیسکوف را نقض می‌کند. پس در کلاس دوچرخه یک Exception را برمیگرداند که اعلام کند دوچرخه موتور ندارد و پرواز کردن دوچرخه هم ممکن نیست.

در اینجا چه اتفاقی رخ خواهد داد؟

کاربری که در حال استفاده از کلاس Bicycle می‌باشد، از این تغییرات اطلاعی ندارد و ناگهان یک چیز غیرمنتظره یا یک Exception در برنامه او رخ خواهد داد که مجبور است برای حل آن مشکل، برنامه خود را تغییر بدهد.

اینجا اصل LSP نقض می‌شود. زیرا کلاس Bicycle، رفتار و ویژگی‌های کلاسِ پدر را تغییر داده است و برنامه نویس مجبور شده است برای حل این مشکل، کد خود را ویرایش کند و در برنامه خود تغییراتی اعمال کند.

راه حل بهتر برای حل مسئله با اصل جایگزینی لیسکوف

برای اینکه اصل لیسکوف را نقض نکنیم، حالا کد خود را دوباره بازنویسی می‌کنیم.

class Vehicle {
    public function doMovement() {
        return "move";
    }
}

class DeviceWithoutEngine extends Vehicle {
    public function startEngine() {
        return "Basic functionality of moving for device without engine";
    }
}

class DeviceWithEngine extends Vehicle {
    public function startEngine() {
        return "Basic engine start functionality";
    }
}

در کد بالا، کلاس Vehicle دارای متد doMovement() است. همان آپشن حرکت کردن.

کلاس DeviceWithoutEngine مخصوص وسایل نقلیه‌ای است که بدون موتور حرکت می‌کنند، همچنین فرزند کلاس Vehicle است و متد doMovement() را هم به ارث می‌برد.

کلاس DeviceWithEngine مخصوص وسایل نقلیه‌ای است که موتور دارند و برای حرکت باید موتور خود را روشن کنند، همچنین فرزند کلاس Vehicle است و متد doMovement() را هم به ارث می‌برد.

پس کلاس های «ماشین»، «دوچرخه» و «هواپیما» را به صورت زیر می‌نویسیم:

class Car extends DeviceWithEngine {
    public function openRoof() {
        return "Roof opened.";
    }
}
class Bicycle extends DeviceWithoutEngine {
    public function beep() {
        return "Beep beep!";
    }
}
class Airplane extends DeviceWithEngine {
    public function fly() {
        return "Fly.";
    }
}

در کد بالا سه عنصر را داریم که هر کدام را به صورت جداگانه تحلیل می‌کنیم:

کلاس ماشین: این کلاس فرزند کلاس DeviceWithEngine است. یعنی علاوه بر اینکه متد openRoof() را در خود دارد، متد startEngine() برای وسایل نقلیه‌ای که موتور دارند و متدdoMovement() را هم به ارث می‌برد.

کلاس دوچرخه: این کلاس فرزند کلاس DeviceWithoutEngine است. یعنی علاوه بر اینکه متد beep() را در خود دارد، متد startEngine() برای وسایل نقلیه‌ای که موتور ندارند و متد doMovement() را هم به ارث می‌برد.

کلاس هواپیما: این کلاس فرزند کلاس DeviceWithEngine است. یعنی علاوه بر اینکه متد fly() را در خود دارد، متد startEngine() برای وسایل نقلیه‌ای که موتور دارند و متدdoMovement() را هم به ارث می‌برد.

حالا می‌توانیم خروجی را با کد زیر چاپ کنیم:

$car = new Car();
$bicycle = new Bicycle();
$airplane = new Airplane();

echo $car->doMovement() . "\n";
echo $car->startEngine() . "\n";
echo $car->openRoof() . "\n";
echo "\n";
echo $bicycle->doMovement() . "\n";
echo $bicycle->startEngine() . "\n";
echo $bicycle->beep() . "\n";
echo "\n";
echo $airplane->doMovement() . "\n";
echo $airplane->startEngine() . "\n";
echo $airplane->fly() . "\n";

خروجی کد به شکل زیر خواهد بود:

move
Basic engine start functionality
Roof opened.

move
Basic functionality of moving for device without engine
Beep beep!

move
Basic engine start functionality
Fly.

سیدرضا بازیار

من مهندس فناوری اطلاعات و توسعه دهنده Back end هستم. حس کنجکاوی، تمایل به کشف دنیاهای جدید و علاقه زیادی به حل چالش‌های گوناگون در زمینه‌های مختلف داشتم باعث شد وارد حرفه‌ی پرچالش و عمیق برنامه‌نویسی بشوم و هر روز بیشتر در این دنیای بزرگ غرق می‌شوم. در حال حاضر با مهارت هایی نرم مانند کار تیمی، قدرت مذاکره، خوش برخوردی، پرورش ایده و مهارت های سخت مانند PHP, OOP, Clean Code, Design Patterns و ... با علاقه مشغول به فعالیت در جامعه متن‌باز هستم. معتقدم هر روز بیشتر از دیروز، عمده کارهای انسان‌ها توسط ربات‌ها انجام خواهد شد، به همین دلیل سعی میکنم اسکریپت‌های زیادی با PHP ، Shell Scripting و Bash Scripting بنویسم و سعی می‌کنم کارهایی که برای انسان‌ها سخت و زمانبر هستند،‌ با ربات‌ها در سریعترین زمان و کمترین هزینه ممکن انجام بدهم. در این مسیر با زبان‌های برنامه نویسی مانند C++ و پایتون هم کمی کار کرده‌ام و با سیستم های مدیریت محتوای زیادی مانند وردپرس، جوملا، ویبولتین و... هم به صورت حرفه‌ای درگیر بوده‌ام. گهگاهی سعی میکنم ربات‌هایی طراحی کنم که اطلاعات عظیمی را از طریق اسکرپینگ به دیتابیس های مختلف منتقل میکنند و از طریق API در پلتفرم های مختلف پردازش می‌شوند. در 10 سال گذشته سابقه زیادی در طراحی سایت و فروشگاه‌های اینترنتی، سئو و بهینه سازی، تست امنیت وب‌سایت‌ها، دیجیتال مارکتینگ و... داشته‌ام. خوشحال میشم بتونم تجربیات خودم رو از طریق این وبلاگ در اختیار همه شما عزیزان قرار بدهم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *