در دنیای برنامهنویسی، طراحی کدهای تمیز، قابل نگهداری و قابل پیشبینی یکی از چالشهای اصلی است. یکی از اصول کلیدی که به دستیابی به این هدف کمک میکند، اصل جداسازی دستور و پرسوجو (Command Query Separation – CQS) است. این اصل، که توسط برتراند میر (Bertrand Meyer) در کتاب “Object-Oriented Software Construction” معرفی شد، تأکید دارد که متدهای یک کلاس باید یا دستور (Command) باشند که حالت سیستم را تغییر میدهند، یا پرسوجو (Query) که اطلاعاتی را برمیگردانند، اما نه هر دو به طور همزمان. در این مقاله، به طور جامع و کاربردی به بررسی این اصل میپردازیم، با تمرکز بر مثال عملی از کد PHP، مزایا، کاربردها و نکات پیادهسازی. این مقاله برای برنامهنویسان مبتدی تا پیشرفته مفید است و میتواند به بهبود کیفیت کدهای شما کمک کند.
معرفی به اصل CQS
اصل CQS بر پایه ایده جداسازی مسئولیتها استوار است. به عبارت سادهتر:
- دستور (Command): متدهایی که حالت شیء یا سیستم را تغییر میدهند، اما هیچ مقداری برنمیگردانند (معمولاً نوع بازگشت آنها
voidاست). این متدها ممکن است عوارض جانبی (Side Effects) داشته باشند، مانند تغییر مقادیر خصوصیات یا ذخیرهسازی در پایگاه داده. - پرسوجو (Query): متدهایی که اطلاعاتی را از شیء یا سیستم استخراج میکنند و آن را برمیگردانند، بدون اینکه هیچ تغییری ایجاد کنند. این متدها باید بدون عوارض جانبی باشند تا رفتار سیستم پیشبینیپذیر باقی بماند.
چرا این جداسازی مهم است؟ اگر یک متد هم تغییر ایجاد کند و هم اطلاعاتی برگرداند، کد پیچیدهتر میشود و احتمال خطا افزایش مییابد. برای مثال، اگر یک متد getBalance() در حین بازگشت موجودی حساب، همزمان موجودی را تغییر دهد، این میتواند به مشکلات غیرمنتظره منجر شود، مانند زمانی که چندین بار فراخوانی میشود.
این اصل با دیگر اصول طراحی مانند اصل مسئولیت واحد (Single Responsibility Principle – SRP) و اصل باز/بسته (Open/Closed Principle) همخوانی دارد و در معماریهای نرمافزاری مانند CQRS (Command Query Responsibility Segregation) گسترش یافته است، که برای سیستمهای بزرگتر استفاده میشود.
مثال عملی: کلاس حساب بانکی در PHP
برای درک بهتر، بیایید یک مثال ساده از کد PHP را بررسی کنیم که اصل CQS را رعایت کرده است. این کد یک کلاس BankAccount را تعریف میکند که عملیات واریز و استعلام موجودی را مدیریت میکند.
کد مثال
class BankAccount {
private $balance;
public function __construct($balance = 0) {
$this->balance = $balance;
}
// Query: استعلام موجودی بدون تغییر حالت
public function getBalance(): int {
return $this->balance;
}
// Command: واریز مبلغ بدون بازگشت مقدار
public function deposit($amount): void {
$this->balance += $amount;
}
}
// استفاده از کلاس
$account = new BankAccount(100);
echo $account->getBalance(); // خروجی: 100
$account->deposit(50);
echo $account->getBalance(); // خروجی: 150توضیح کد
- خصوصیت خصوصی
$balance: این متغیر موجودی حساب را ذخیره میکند و از دسترسی مستقیم خارجی جلوگیری میکند (Encapsulation). - سازنده (
__construct): موجودی اولیه را تنظیم میکند. این متد یک دستور است زیرا حالت شیء را تغییر میدهد. - متد
getBalance(): این یک پرسوجو است. فقط موجودی را برمیگرداند و هیچ تغییری ایجاد نمیکند. نوع بازگشتintاست تا مشخص باشد که چه چیزی انتظار میرود. - متد
deposit($amount): این یک دستور است. مبلغ را به موجودی اضافه میکند وvoidبرمیگرداند، یعنی هیچ مقداری بازنمیگرداند. این متد عوارض جانبی دارد (تغییر$balance).
در استفاده عملی، ابتدا یک حساب با موجودی ۱۰۰ ایجاد میشود، موجودی استعلام میشود (۱۰۰)، سپس ۵۰ واریز میشود و دوباره موجودی استعلام میشود (۱۵۰). این جداسازی باعث میشود کد خواناتر و testableتر باشد.
چرا این کد CQS را رعایت میکند؟
getBalance()هیچ عوارض جانبی ندارد و فقط خواندنی است.deposit()تغییر ایجاد میکند اما چیزی برنمیگرداند.
اگر بخواهیم این اصل را نقض کنیم، میتوانستیمdeposit()را طوری بنویسیم که موجودی جدید را برگرداند، اما این کار کد را پیچیدهتر میکند و ممکن است منجر به سوءاستفاده شود (مثلاً فراخوانی مکرر بدون نیاز).
مزایای استفاده از CQS
پیادهسازی CQS مزایای متعددی دارد که آن را به یک اصل کاربردی تبدیل میکند:
- پیشبینیپذیری کد: برنامهنویسان میدانند که پرسوجوها ایمن هستند و میتوان آنها را بدون نگرانی از تغییرات فراخوانی کرد.
- آسانی تست: تست پرسوجوها ساده است زیرا فقط خروجی را بررسی میکنید. برای دستورها، میتوانید حالت قبل و بعد را مقایسه کنید.
- کاهش عوارض جانبی: جلوگیری از تغییرات ناخواسته، که در برنامههای چندنخی (Multi-threaded) حیاتی است.
- بهبود خوانایی: کد واضحتر میشود و مسئولیت هر متد مشخص است.
- هماهنگی با معماریهای بزرگ: در سیستمهای توزیعشده مانند میکروسرویسها، CQS به CQRS تبدیل میشود، جایی که دستورها و پرسوجوها در سرویسهای جداگانه مدیریت میشوند تا scalability افزایش یابد.
کاربردهای عملی CQS
این اصل در زمینههای مختلفی کاربرد دارد:
- برنامههای وب و APIها: در RESTful APIها، متدهای GET پرسوجو هستند (بدون تغییر) و POST/PUT دستورها (تغییر حالت). برای مثال، در یک API بانکی،
/balanceیک پرسوجو است و/depositیک دستور. - برنامههای موبایل و دسکتاپ: در کلاسهای مدل (مانند MVVM یا MVC)، جداسازی کمک میکند تا UI بدون تأثیر بر دادهها بهروزرسانی شود.
- سیستمهای بزرگ مانند CQRS: در اپلیکیشنهای enterprise، پرسوجوها از پایگاه داده خواندنی (Read Replica) و دستورها از پایگاه نوشتاری استفاده میکنند. این برای عملکرد بالا مفید است، مثلاً در فروشگاههای آنلاین جایی که استعلام موجودی کالا زیاد است اما تغییرات کمتر.
- مثالهای دیگر: در بازیها، متد
getScore()پرسوجو است وaddPoints()دستور. در سیستمهای مالی، این اصل امنیت را افزایش میدهد.
چالشها و نکات پیادهسازی
- استثناها: گاهی متدهایی مانند
pop()در لیستها هم تغییر میدهند و هم برمیگردانند، اما در طراحی شیءگرا بهتر است از آنها اجتناب شود. - زبانهای برنامهنویسی: در PHP، Java یا C# آسان است، اما در زبانهای تابعی مانند Haskell، این جداسازی طبیعیتر است.
- بهبود کد موجود: اگر کدی دارید که CQS را نقض میکند، آن را به دو متد جدا تقسیم کنید: یکی برای تغییر و یکی برای بازگشت.
- ابزارها: از ابزارهایی مانند PHPUnit برای تست در PHP استفاده کنید تا مطمئن شوید پرسوجوها بدون عوارض هستند.
نتیجهگیری
اصل CQS یک ابزار قدرتمند برای نوشتن کدهای حرفهای است که نه تنها کیفیت را افزایش میدهد، بلکه نگهداری بلندمدت را آسان میکند. با شروع از مثالهای ساده مانند کلاس حساب بانکی، میتوانید این اصل را در پروژههای بزرگتر اعمال کنید. اگر برنامهنویس هستید، پیشنهاد میکنم در کد بعدی خود این اصل را امتحان کنید – تفاوت را خواهید دید! برای مطالعه بیشتر، کتاب برتراند میر یا منابع آنلاین مانند Martin Fowler’s blog را بررسی کنید.