اصل باز – بسته در اصول SOLID
اصل Open/Closed Principle در اصول SOLID چیست؟
Open/Closed Principle یا به اختصار OCP که در فارسی به آن «اصل باز – بسته» گفته میشود، یک اصل مهندسی نرمافزار و اصل دوم از اصول طراحی SOLID است.
این اصل بیان میکند که موجودیتهای یک نرمافزار (کلاسها، ماژولها، توابع و …) باید برای توسعه باز باشند اما برای اصلاح بسته باشند. این بدان معنی است که رفتار یک ماژول یا کلاس را میتوان از طریق وراثت (inheritance) یا ترکیب (composition)، بدون تغییر کد منبع خود ماژول یا کلاس، گسترش داد.
به زبان ساده، طبق اصل «باز – بسته» کلاس باید همزمان هم بسته باشد و هم باز! یعنی همزمان که توسعه داده میشود (باز بودن)، تغییر نکند و دستکاری نشود (بسته بودن).
مفهوم کلاس باز چیست؟
به کلاسی که بتوانیم آن را توسعه بدهیم، پراپرتیها و متدهای جدید به آن اضافه کنیم و همچنین ویژگیهای رفتار آن را تغییر بدهیم، کلاس باز میگوییم.
به عبارت دیگر، مفهوم “کلاس باز” به کلاسی اطلاق میشود که به گونهای طراحی شده است که امکان گسترش را فراهم کند، اما کد منبع آن را تغییر ندهد. یک کلاس باز معمولاً به گونهای پیادهسازی میشود که میتوان رفتارهای جدیدی را از طریق وراثت یا ترکیب، بدون تغییر خود کلاس به آن اضافه کرد.
به عنوان مثال، یک کلاس باز ممکن است روشهای خاصی را به صورت مجازی یا انتزاعی در معرض نمایش بگذارد و به کلاسهای مشتق شده اجازه دهد تا آن رفتار را گسترش دهند یا بازنویسی کنند. این کار، کلاس را قادر می سازد تا بدون نیاز به تغییر کد منبع اصلی، مجدداً مورد استفاده قرار گیرد و با نیازهای جدید سازگار شود. در نتیجه این سیستم نرمافزاری انعطاف پذیرتر، قابل نگهداری و مقیاس پذیرتر است و ویرایش یا گسترش آن در طول زمان آسانتر است.
مفهوم کلاس بسته چیست؟
به کلاسی که تکمیل شده باشد، کاملا تست شده باشد و سایر کلاسها درحال استفاده از این کلاس باشند، کلاس بسته میگوییم. این کلاس پایدار است و دیگر تغییر نخواهد کرد. در برخی زبانهای برنامه نویسی مانند PHP، یکی از راههای بسته نگه داشتن یک کلاس، استفاده از کلمه کلیدی final است.
به عبارت دیگر، مفهوم “کلاس بسته” به کلاسی اشاره دارد که طوری طراحی شده است که اجازه تغییر کد منبع آن را نمیدهد. یک کلاس بسته معمولاً به گونه ای پیادهسازی می شود که رفتار آن ثابت است و نمیتوان آن را تغییر داد، حتی از طریق وراثت یا ترکیب.
یک کلاس بسته هنوز می تواند توسط سایر بخش های سیستم مورد استفاده مجدد قرار گیرد و گسترش یابد، اما فقط از طریق ترکیب (composition) یا واگذاری مسئولیت (delegation)، نه با تغییر کد منبع آن. این کمک می کند تا اطمینان حاصل شود که رفتار کلاس سازگار و قابل پیشبینی باقی میماند و از پیامدهای ناخواسته تغییرات در کد جلوگیری میکند. در نتیجه این سیستم نرمافزاری، قویتر و قابل نگهداریتر است، زیرا تغییرات فقط به روشهای تعریف شده انجام میشود و کد منبع اصلی بدون تغییر باقی میماند.
این اصل موجب میشود ماژولها و کلاسهایی را طراحی کنیم که انعطافپذیر، قابل استفاده مجدد و قابل نگهداری هستند، زیرا بهجای اصلاح کد موجود، میتوانیم کلاسهای جدیدی ایجاد کنیم که آن کلاس را گسترش بدهد. این کار موجب جلوگیری از بوجود آمدن مشکلات یا عواقب ناخواسته میشود و مدیریت و حفظ کد را در طول زمان آسانتر میکند.
اصل باز/بسته یک اصل اساسی در برنامهنویسی شیگرا است که با رعایت این اصل، توسعهدهندگان میتوانند سیستمهای نرمافزاری را ایجاد کنند که انعطافپذیرتر، مقیاسپذیرتر و قابل نگهداری هستند و تغییر و تکامل در طول زمان آسانتر است.
یک مثال از اصل باز – بسته در دنیای واقعی
اجازه بدهید اصل باز – بسته را با یک مثال از دنیای واقعی توضیح بدهم تا آن را بهتر متوجه شوید. پس تصویر زیر را در نظر بگیرید.

یک دوربین عکاسی را درنظر بگیرید، این دوربین برای عکس گرفتن طراحی شده است و میتواند عکس های خوبی ثبت کند. فرض کنید این دوربین میتواند با زوم 10 برابر، عکسهایی را از مکانهای دور ثبت کند اما شما میخواهید از فاصله 100 برابر این عکس ثبت شود، پس در این مورد نیازی نیست دوربین را باز کنیم و یک لنز زوم در آن قرار دهیم، برای این کار یک لنز جداگانه به صورت ماژولار طراحی شده است که وقتی میخواهیم عکس را از فاصله دورتر ثبت کنیم، کافیست آن لنز را به دوربین خود متصل کنیم.
پس این دوربین میتواند لنز های مختلفی را به صورت ماژولار داشته باشد:
لنز زوم: برای ثبت عکسها از فاصله های دورتر
لنز تله: مخصوص ثبت عکسهای بهتر برای عکاسی چهره و آتلیه
لنز واید: برای ثبت عکسهایی که به زاویه دید گستردهتری نیاز دارند
پس میتوانیم برای ثبت عکس های مختلف، لنزهای مختلفی را به دوربین متصل کنیم (باز بودن) بدون اینکه نیاز باشد دوربین را باز کنیم یا قطعهای را در آن تغییر بدهیم (بسته بودن).
یک مثال از اصل باز – بسته در زبان برنامه نویسی
فرض کنید یک کلاس به نام Shape داریم که اشکال هندسی را تعریف میکند. حالا میخواهیم دو شکل هندسی دایره (Circle) و مستطیل (Rectangle) را تعریف کنیم و آن را محاسبه کنیم.
ابتدا بدون رعایت اصل باز – بسته این کد را مینویسیم:
<?php
class Shape
{
public function area($shape, $radius = null, $width = null, $height = null)
{
if ($shape === 'circle') {
return pi() * pow($radius, 2);
} elseif ($shape === 'rectangle') {
return $width * $height;
}
}
}
class ShapeCalculator
{
public function calculateArea(Shape $shape, $shapeType, $radius = null, $width = null, $height = null)
{
return $shape->area($shapeType, $radius, $width, $height);
}
}
$calculator = new ShapeCalculator();
$shape = new Shape();
echo $calculator->calculateArea($shape, 'circle', 10) . PHP_EOL;
echo $calculator->calculateArea($shape, 'rectangle', null, 5, 10) . PHP_EOL;
در این مثال، کلاس Shape یک متد داریم که مساحت دایره یا مستطیل را بر اساس مقدار پارامتر $shape محاسبه میکند. کلاس ShapeCalculator از کلاس Shape برای محاسبه مساحت استفاده می کند و پارامتر $shape را به همراه ابعاد مربوطه به متد area منتقل میکند.
در این مثال بدون پیروی از اصل Open/Closed، کلاس Shape یک متد تک ناحیهای دارد که مساحت چندین شکل را محاسبه میکند و هر زمان که شکل جدیدی اضافه میشود، آن را برای ویرایش کردن یا گسترش دادن باز میکند. این می تواند منجر به ایجاد کدهایی شود که نگهداری آنها سختتر و مستعد بوجود آمدن خطا هستند، زیرا تغییرات در کد می تواند عواقب ناخواستهای در سایر بخشهای سیستم داشته باشد.
علاوه بر این، این طراحی فاقد انعطافپذیری و مقیاسپذیری است. اگر شکل جدیدی مانند مربع اضافه شود، متد area در کلاس Shape باید برای تطبیق شکل جدید اصلاح شود که باعث افزایش حجم و پیچیدگی کد میشود. علاوه بر این، کلاس ShapeCalculator نیز باید برای پشتیبانی از شکل جدید اصلاح شود، که میتواند منجر به تغییرات در سراسر سیستم شود.
با پیروی از اصل باز/بسته، میتوان کد را به گونهای نوشت که انعطافپذیر، مقیاسپذیر و قابل نگهداری باشد، و به آن اجازه میدهد تا در آینده، بدون نیاز به ویرایش کد، اشکال هندسی جدید به آن اضافه کند.
پس حالا همین کد را با پیروی از اصل باز – بسته مینویسیم:
<?php
abstract class Shape
{
abstract public function area();
}
class Circle extends Shape
{
private $radius;
public function __construct($radius)
{
$this->radius = $radius;
}
public function area()
{
return pi() * pow($this->radius, 2);
}
}
class Rectangle extends Shape
{
private $width;
private $height;
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
}
public function area()
{
return $this->width * $this->height;
}
}
class ShapeCalculator
{
public function calculateArea(Shape $shape)
{
return $shape->area();
}
}
$calculator = new ShapeCalculator();
$circle = new Circle(10);
$rectangle = new Rectangle(5, 10);
echo $calculator->calculateArea($circle) . PHP_EOL;
echo $calculator->calculateArea($rectangle) . PHP_EOL;
در این مثال، کلاس Shape یک کلاس انتزاعی (abstract) است که متد area را تعریف میکند، اما آن را پیاده سازی نمیکند.
کلاس های Circle و Rectangle کلاس Shape را گسترش میدهند و با استفاده از متد area پیاده سازی را بر اساس شکل مورد نیازِ خود انجام میدهند.
کلاس ShapeCalculator از طریق چندریختی (polymorphism) از کلاس Shape استفاده میکند و میتواند با هر کلاسی که کلاس Shape را گسترش می دهد کار کند.
این طرح از اصل Open/Closed پیروی می کند، زیرا کلاس Shape برای اصلاح بسته است، اما برای گسترش باز است. کلاسهای Circle و Rectangle رفتار کلاس Shape را بدون تغییر کد منبع آن گسترش میدهند و کلاس ShapeCalculator قادر است با هر کلاسی که کلاس Shape را گسترش میدهد کار کند و آن را انعطافپذیر و مقیاسپذیر میکند.
اگر شکل جدیدی مانند مربع اضافه شود، می توان کلاس جدیدی ایجاد کرد که کلاس Shape را گسترش میدهد و پیادهسازی خود را از متد area ارائه میکند، بدون اینکه خود کلاس Shape را تغییر دهد.