در این مقاله قصد دارم شما را با اصول Solid
آشنا کنم که هر برنامه نویسی برای ساخت برنامه ای بهتر باید آن را بداند و انجام دهد.
Solid
در واقع یک سریع دستورالعمل هست که اگر شما از آن استفاده کنید ( به صورت اصولی و صحیح) کدهای نوشته شده ساده، توسعه پذیر، منطقی و واضح هستند به عبارتی بهتر باعث میشود از طراحی بد اجتناب کنید.
در بعضی توضیحات از کلمه ماژول و در بعضی از موارد کلاس استفاده شده، ما اکثرا از کلمه کلاس در طول مقاله استفاده میکنیم.
Solid
شامل موارد زیر میباشد :
- Single Responsibility Principle (SRP) : یکی از مهم ترین قواعد
Solid
که بیان میکند تنها یک مسئولیت (وظیفه) واحد توسط هر کلاس ارائه میشود. - Open-Close Principle : این اصل بیان میکند که کلاس باید گسترش پذیر باشد اما برای اصلاح بسته باشد.
- Liskov Substitution : کلاس های مشتق شده میتوانند در مکان هایی که کلاس پایه استفاده شده است جایگزین شوند.
- Interface Segregation : ما باید
interface
های خود را بهinterface
های کوچک تری تبدیل کنیم تا در هر بخش تنها همان نیاز کاربر را بر طرف کنیم. - Dependency Inversion : این اصل به عبارتی ساده بیان میکند که کلاس های سطح بالا نباید به کلاس سطح پایین وابستگی داشته باشن بلکه هردوی آنها باید به
abstractions
وابسته باشند.
برای بهتر درک کردن این موضوع هر بخش را با جزیئات بیشتر به همراه مثال تشریح میکنیم. مثال ها از موارد مختلفی زده شده است تا بتوانید مفاهیم را راحتر درک کرده و همچنین در کد های خود اعمال کنید.
Single Responsibility
در یک کلاس شما تنها یک هدف دارید و تمامی متدها برای یک وظیفه واحد اجرا میشوند. اگر کلاسی دارید که چندین هدف یا وظیفه دارد باید به چند کلاس تبدیل شود.
برای نمونه ما کد زیر را داریم :
class Setting{
public function setSettings()
{
// set settings
}
public function getSettings()
{
// get settings
}
public function showSettings()
{
// show output
}
}
توجه کنید که هر تابع قراره تنها یک کار انجام بده و قراره هر کلاس یا شی یک هدف واحد داشته باشه، کدها رو به شکل زیر تغییر میدیم:
class Setting{
public function setSettings()
{
// set settings
}
public function getSettings()
{
// get settings
}
}
class Output
{
public function show($data)
{
// show output
}
}
خوب تغییرات بالا باعث شد تا هر کلاس یک هدف خاص رو دنبال کنه و همچنین در آینده اگر نیاز به تغییر یا توسعه باشه با تغییرات اعمال شده اینکار برای ما راحت تر هست.
Open-Close
برنامه باید به صورتی نوشته شود که توسعه پذیر باشید اما قابل تغییر نباشد به این صورت که بدون هیچگونه تغییر در ساختار کلاس اصلی بتوانیم کلاس را توسعه دهیم.
برای مثال ما کد زیر برای ورود اطلاعات به ۲ نوع پایگاه داده رو داریم :
class DB
{
public function insert($connection, $into, $cell, $data)
{
if (is_a($connection, 'mysqli')) {
// connect and insert into mysql and return id
} elseif (is_a($connection, 'mongodb')) {
// connect and insert into mongodb and return id
}
throw new DatabaseInvalidConnection;
}
}
خوب این کد توسعه پذیر نیست و برای هر پایگاه داده ما باید بدنه کلاس رو تغییر بدیم، با توجه به اصل بیان شده کد به شکل زیر تغییر میکنه :
interface Database{
public function insert($into, $cell, $data);
public function connect();
}
class Mysql implements Database
{
public function connect()
{
// connect to mysql database
}
public function insert($into, $cell, $data)
{
// insert into mysql and return id
}
}
class NoSql implements Database
{
public function connect()
{
// connect to mongodb database
}
public function insert($into, $cell, $data)
{
// insert into mongodb and return id
}
}
class DB
{
public function insert($connection, $into, $cell, $data)
{
$connection->connect();
$connection->insert($into, $cell, $data);
}
}
حالا برای هر پایگاه داده تنهای نیازه کلاس بر اساس اینترفیس دیتابیس پیاده سازی بشه بدون هیچ تغییری کد با پایگاه داده جدید کار خواهد کرد.
Liskov Substitution
با توجه به این اصل ما باید در کلاس فرزند قادر باشیم هرگونه رفتاری که کلاس والد دارد را پیاده سازی کنیم. به عبارت دیگر ما باید قادر باشیم تا در هر نقطه کلاس فرزند را جایگزین کلاس والد کنیم.
برای مثال ما کد زیر را داریم :
interface OutputInterface{
public function show($data);
}
class Output implements OutputInterface{
public function show($data)
{
// show data
}
}
class Formatter extends Output
{
public function jsonFormat($data)
{
// return json data
}
public function show($data)
{
$data = $this->jsonFormat($data);
// show Data
}
}
در این کد اصول Solid
رعایت نشده است یا به عبارت بهتر Liskov substitution
به درستی اعمال نشده است. کلاس Formatter
فرزند کلاس Output
میباشد و باید تمامی فعالیت های کلاس والد را بتواند انجام دهد. اگر قسمتی از کد که از کلاس Output
استفاده شده است را تغییر دهیم به صورتی که از کلاس Formatter
استفاده کند خروجی مد نظر ما داده نمیشود زیرا در این کلاس متد show
را ما مجددا بازنویسی کردیم و خروجی به صورت json
نمایش داده میشود.
Interface Segregation
ما باید Interface
را به گونه طراحی کنیم که به صورت تقسیم شده باشد نه به صورت جامع و کلی ،به دلیل اینکه دیگران در صورت استفاده از اینترفیس تنها متدهای مد نظر خودشان را پیاده سازی کنند تا از نوشته شدن کدهای بیهوده جلوگیری شود.
کد زیر را در نظر بگیرید:
interface ImagesInterface
{
public function setWatermark($src);
public function saveFile($file, $path);
}
class Image implements ImagesInterface
{
public function setWatermark($src)
{
// set watermark
}
public function saveFile($file, $path)
{
// save file
}
}
اگر بخواهیم اصل گفته شده را در این کد پیاده کنیم خروجی کد زیر میباشد:
interface ImagesInterface
{
public function setWatermark($src);
}
interface FileInterface
{
public function saveFile($file, $path);
}
class Image implements ImagesInterface,FileInterface
{
public function setWatermark($src)
{
// set watermark
}
public function saveFile($file, $path)
{
// save file
}
}
Dependency Inversion
یکی اصل مهم از Solid
میباشد که بیان میکند که یک ماژول سطح بالا نباید به یک ماژول سطح پایین وابسته باشد بلکه هر دوی آنها باید به abstractions
وابسته باشند.
abstractions
نباید به جزئیات وابسته باشد بلکه جزیئات باید به abstractions
وابسته باشد.
درک این مفهوم ممکنه برای شما از درک مفاهیم بیان شده سخت تر باشه، با یک مثال این اصل رو واضح تر میکنیم. فرض کنید شما قطعه کد زیر را برای ایندکس کردن داده ها در وب دارید :
class Crawler
{
public function crawlPage()
{
// crawl Page with DOMDocument
}
}
class Engines
{
public function indexing(Crawler $crawler)
{
$crawler->crawlPage();
}
}
اگر در قطع کد بالا ما در بدنه کلاس Crawler
تغییر ایجاد کنیم ، کلاس Engines
هم تحت تاثیر قرار میگرد. برای حل این مسئله کد را به شکل زیر تغییر میدهیم:
interface CrawlerInterface
{
public function crawlPage();
}
class Crawler implements CrawlerInterface
{
public function crawlPage()
{
// crawl Page with DOMDocument
}
}
class Engines
{
public function indexing(CrawlerInterface $crawler)
{
$crawler->crawlPage();
}
}
نتیجه گیری :
امیدوارم در این مقاله تونسته باشم به خوبی شما را با این اصول آشنا کنم. رعایت اصول Solid
شاید در ابتدا کمی دشوار باشد اما با رعایت تمامی موارد گفته شده در کنار هم مزایای زیادی برای شما به وجود می آورد مانند: توسعه پذیری کدها، انعطاف پذیری بیشتر، یکپارچگی، پایداری و … .