اصل Command Query separation
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

در ادامه مطلب قبلی، یکی از مشکلاتی که طراحی Builder از آن رنج می‌برد، نقض کردن قانون command query separation است که در ادامه درباره‌ی این اصل بیشتر بحث خواهیم کرد.

اصل Command query separation یا به اختصار CQS، در کتاب Object-Oriented Software Construction توسط Bertrand Meyer معرفی شد‌ه‌است. بر اساس آن، عملیات‌های سیستم باید یا Command باشند و یا Query و نه هر دوی آن‌ها. وقتی یک کلاینت به امضای یک متد توجه می‌کند، اینکه این متد چه کاری را انجام میدهد Commands نام داشته و به شیء فرمان می‌دهد تا کاری را انجام بدهد. این عملیات وضعیت خود شیء و یا اشیاء دیگر را تغییر می‌دهد. در اینجا Queries به شیء فرمان می‌دهند تا نتیجه‌ی سؤال ( ویا درخواست) را برگرداند.

در آن سوی دیگر، متدهایی را که وضعیت شیء را تغییر می‌‌دهند، به عنوان Command در نظر میگیریم (بدون آنکه مقداری را برگردانند). اگر این نوع متدها، مقداری را برگردانند، باعث سردرگمی کلاینت می‌شوند؛ زیرا کلاینت نمی‌داند این متد باعث تغییر شیء شده‌است و یا Query؟

 همانطور که میدانیم، متد‌ها می‌توانند هر دو کار را با هم انجام دهند؛ یعنی مقداری را برگردانند و همچنین وضعیت شیء را تغییر دهند و همین مورد باعث سردرگمی و نقض می‌شود. وقتی متد‌های Command را از Query جدا میکنیم، ما را به سمت یک طراحی قابل فهم هدایت می‌کند. متدهایی که مقدار  void برمی گردانند، Command و سایر آنهایی که نوعی (type ) را برمی‌گردانند، Query هستند.
به کد زیر توجه فرمایید:
public class FileStore
    {
        public string WorkingDirectory { get; set; }

        public string Save(int id, string message)
        {
            var path = Path.Combine(this.WorkingDirectory + id + ".txt");
            File.WriteAllText(path, message);
            return path;
        }

        public event EventHandler<MessageEventArgs> MessageRead;

        public void Read(int id)
        {
            var path = Path.Combine(this.WorkingDirectory + id + ".txt");
            var msg = File.ReadAllText(path);
            this.MessageRead(this, new MessageEventArgs { Message = msg });    
        }
    }
اولین مشکلی که در طراحی این کلاس وجود مربوط به متد Read است؛ زیرا این متد void برمی‌گرداند. پس درنتیجه از نوع Command است. ولی اگر بیشتر به این متد توجه فرمایید احساس خواهید کرد که متد Read باید به صورت Query باشد. زیرا این متد قرار بوده مقداری را برگرداند؛ ولی اینجا به صورت void پیاده سازی شده‌است. در عوض  متد Save به صورت Query پیاده سازی شده است.
برای حل این مشکل کافی است تا امضای متد Read را به این صورت تغییر دهیم:
 public string Read(int id)
 {
     var path = Path.Combine(this.WorkingDirectory + id + ".txt");
     var msg = File.ReadAllText(path);
     this.MessageRead(this, new MessageEventArgs { Message = msg });
     return msg;
  }
خوب؛ اولین سوالی که پیش می‌آید این است که آیا این Query چیزی را تغییر می‌دهد؟ (تغییر شیء یا اشیایی دیگر) 
در ادامه متوجه خواهید شد این کد باعث فراخواندن یک event می‌شود. حالا آیا این event از نوع Command است یا Query؟ از نوع Command است؛ چون EventHandler  مانند متد‌هایی هستند که مقدار void را بر می‌گردانند و همانطور که میدانید، متدهایی که مقدار void را بر می‌گردانند، از نوع Command میباشند که وضعیت شیء را تغییر می‌دهند و برای اینکه از اصل CQS پیروی کنیم، باید این event را حذف کنیم تا متد Read از نوع Query باشد.
اگر به امضای متد Save  دقت کنید، به صورت یک Query است. ولی اگر به پیاده سازی آن دقت کنید، بیشتر شبیه به یک Command است تا یک Query و مهمترین ویژگی یک Command این است که مقدار void را بر می‌گرداند و برای حل این مشکل، متد Save را به صورت زیر تغییر می‌دهیم:
public void Save(int id, string message)
{
    var path = Path.Combine(this.WorkingDirectory + id + ".txt");
    File.WriteAllText(path, message);
}
همانطور که متوجه شدید، با این تغییر دیگر ما دسترسی به  مقدار path نخواهیم داشت و شاید مقدار path برای کلاینت مهم باشد. برای حل این مشکل متد جدیدی را به نام GetFileName به کلاس اضافه می‌کنیم؛ تا کلاینت به مقدار Path دسترسی داشته باشد. توجه داشته باشید که امضای متد GetFileName به صورت query پیاده سازی شده‌است.
public class FileStore
    {
        public string WorkingDirectory { get; set; }

        public void Save(int id, string message)
        {
            var path = GetFileName(id);  //ok to query from Command
            File.WriteAllText(path, message);            
        }

        public string Read(int id)
        {
            var path = GetFileName(id);
            var msg = File.ReadAllText(path);
            return msg;
        }
     
        public string GetFileName(int id)
        {
            return Path.Combine(this.WorkingDirectory , id + ".txt");     
        }
    }
تنها نکته‌ای که در اینجا بد نیست به آن اشاره کنیم این است که متدهایی که از نوع command هستند، می‌توانند بدون هیچگونه مشکلی متد‌های query را فراخوانی کنند. زیرا مهمترین ویژگی query‌ها این هستند که وضعیت شیء را تغییر نمی‌دهند و در نتیجه در هر بار فراخوانی، همان نتیجه را بازگشت می‌دهند.

چکیده:

هدف اصلی از طراحی نرم افزار، غالب شدن بر پیچیدگی‌ها می‌باشد. اصل CQS متد‌ها را به دو دسته‌ی Command و Query تقسیم می‌کند که Query ، اطلاعاتی را از وضعیت سیستم بر می‌گرداند، ولی command  وضعیت سیستم را تغییر می‌دهد و مهمترین دستاورد CQS ما را به سمت کدی تمیز‌تر و با قابلیت درک بهتر می‌رساند.