این سایت برای ie9 طراحی نشده است

لطفا دستگاه خود را بچرخانید.

الگوی طراحی – Command

۱۳ اردیبهشت ۱۳۹۶ حسین صداقت
بدون دیدگاه

به نام خدا

همانطور که در مطلب قبل اشاره کردیم در این مطلب میخواهیم که الگوی طراحی Command را با هم دیگه بررسی کنیم.با استفاده از این Design Pattern می توانیم فراخوانی یا Call کردن یک متد را بسته بندی (Encapsulate) کنیم که این کار فواید زیادی دارد .

قبل از شروع توضیح این Design Pattern نیاز داریم که کمی راجع به Encapsulation  یا بستنه بندی سازی  در برنامه نویسی شی گرا با هم صحبت کنیم.

به زبان ساده Encapsulation  یا بسته بندی سازی به معنی از دسترس خارج کردن جزئیات داخلی که مورد نیاز نیست از کاربر است.به طور مثال در دنیای واقعی وقتی ما یک دارویی که به شکل کپسول می باشد را خریداری میکنیم ما به جزئیات داخلی آن دسترسی نداریم و ما فقط با قورت می توانیم از آن استفاده کنیم!در دنیای برنامه نویسی هم همین قضیه وجود دارد.فرض کنیم کاربر به یک سری اطلاعات نیاز دارد که کلاس A وظیفه در اختیار گذاشتن این داده رو به کاربر برعهده دارد.در کلاس A دو متد m1 و m2 وجود دارد.وظیفه متد m1 تولید داده است  و وظیفه متد m2 بازگرداندن داده تولید شده توسط متد m1 می باشد.برای کاربر ما مهم این است که داده در اختیارش قرار بگیرد .پس اینکه به متد m1 دسترسی داشته باشد بی معنی است .یکی از راه های بسته بندی سازی یا Encapsulation در زبان های برنامه نویسی استفاده از Access Specifireها( Public و Private و ..)هستش.سطح های مختلفی برای بسته بندی یا Encapsulation وجود دارد.میتوان دسترسی به یک کلاس را مدیریت کرد یا دسترسی به متد های یک کلاس را مدیریت کرد و یا Property ها و فیلد های یک کلاس رو مدیریت کرد.

حالا به بحث خودمون یعنی الگوی طراحی Command بر می گردیم.ما با استفاده از این Design Pattern فراخوانی یا call کردن یک متد را Encapsulate میکنیم و با استفاده از این کپسوله کردن ویژگی های زیادی را در اختیار ما میگذارد از جمله میتوانیم آن را ذخیره کنیم و دوباره فرخوانی کنیم و یا امکان redo  یا undo برای call کردن یک متد ایجاد کنیم.

نمودار UML این Pattern به صورت زیر می باشد:

Untitled

نقش های شرکت کننده در این Design Pattern موارد زیر است:

  • Command: تعریف یک اینترفیس برای اجرای یک عملیات
  • Concrete Command: تعریف اتصال بین یک دریافت کننده شی و یک عمل
  • Client: یک شی از ConcreteCommand ایجاد میکند و آن را برای دریافت کننده ست میکند.
  • Invoker: از Command میخواد که Request را انجام دهد.
  • Receiver: می داند که چطور عملیات مرتبط با انجام Request را انجام دهد.

مثال:

فرض کنید ما قصد داریم یک ماشین حساب ساده با قابلیت Redo و Undo  با استفاده از این الگوی طراحی ایجاد کنیم.در ادامه به بررسی پیاده سازی این برنامه با استفاده از Design Pattern ا Command می پردازیم.

 

کلاس Command ما که همان نقش Command که در بالا تعریف کردیم را دارد یک کلاس Abstarct هستش که دو متد  Execute و Unexecute  به صورت Abstract در آن تعریف شده.

abstract class Command
{
  public abstract void Execute();
  public abstract void UnExecute();
}

 

 

کلاس ConcereteCalculator که همان نقش ConcereteCommand را بر عهده دارد از Command ارث برده است .این کلاس دارای سه فیلد هستش که که آن ها را در Constructor مقدار دهی کرده است.

فیلد _operator که از نوع کاراکتر است و در این برنامه یکی از چهار عمل اصلی در آن مقدار دهی میشود.(- + / * )

فیلد _calculator که از نوع کلاس Calculator هستش .کلاس Calculator وظیفه انجام منطق ریاضی برنامه ما را بر عهده دارد.

فیلد Operand عددی که قرار است بر روی آن محاسبه انجام شود در آن می نشیند.

حالا به بررسی متد های این کلاس می پردازیم.

متد Execute با پاس دادن دو فیلد _operator و _operand به متد operation کلاس Calculator (در فیلد _Calculator قبلا یک object از این کلاس داشتیم) عملیات ریاضی آن را انجام میدهد.

متد Unexecute با استفاده از متد Undo  و برعکس کردن _operand عملیات ریاضی انجام می دهد.(این متد برای عملیات Undo استفاده میشود)

 

class CalculatorCommand : Command
  {
    private char _operator;
    private int _operand;
    private Calculator _calculator;
 
    // Constructor
    public CalculatorCommand(Calculator calculator,
      char @operator, int operand)
    {
      this._calculator = calculator;
      this._operator = @operator;
      this._operand = operand;
    }
 

 
 
 
    // Execute new command
    public override void Execute()
    {
      _calculator.Operation(_operator, _operand);
    }
 
    // Unexecute last command
    public override void UnExecute()
    {
      _calculator.Operation(Undo(_operator), _operand);
    }
 
    // Returns opposite operator for given operator
    private char Undo(char @operator)
    {
      switch (@operator)
      {
        case '+': return '-';
        case '-': return '+';
        case '*': return '/';
        case '/': return '*';
        default: throw new
         ArgumentException("@operator");
      }
    }
  }

 

کلاس بعدی ما کلاس Calculator می باشد که همانطور که قبلا هم اشاره کردیم مغز ریاضی برنامه ما هستش و عملکرد آن ساده می باشد.

class Calculator
{
  private int _curr = 0;
 
  public void Operation(char @operator, int operand)
  {
    switch (@operator)
    {
      case '+': _curr += operand; break;
      case '-': _curr -= operand; break;
      case '*': _curr *= operand; break;
      case '/': _curr /= operand; break;
    }
    Console.WriteLine(
      "Current value = {0,3} (following {1} {2})",
      _curr, @operator, operand);
  }
}

 

 

آخرین کلاسی که ما در این برنامه مورد بررسی قرار می دهیم کلاس User می باشد.در حقیقت Client برنامه ما با این کلاس در ارتباط می باشد.در این کلاس منطق Redo و  Undo و همچنین ذخیره سازی روند محاسبات ما انجام می شود.در این کلاس سه فیلد وجود دارد.

فیلد _calculator که یک شی از کلاس Calculator  هستش.

فیلد بعدی فیلد _commands است که نوع آن لیستی از اینترفیس Command می باشد.در این Property هر باری که متد Compute این کلاس فراخوانی مشود یک شی از این فراخوانی ساخته می شود و در این Property ذخیره می شود.

فیلد آخر هم فیلد _current هستش که مقدار آن با صفر مقدار دهی شده است و وظیفه نگهداری Index فعلی ما در متغیر _commands که یک لیست می باشد را رو را بر عهده دارد.

حال به سراغ بررسی متد های این کلاس می پردازیم.

متد Compute وظیفه انجام محاسبه با استفاده از مغز ریاضی برنامه (_calculator)و ذخیره این فراخوانی به عنوان یک Object از نوع command در متغیر _commands را بر عهده دارد.ورودی های این متد یک متغیر با اسم @operator از جنس char  است و ورودی بعدی یک متغیر از جنس int به اسم operand می باشد که مقداری که قرار است عملیت @operator انجام دهد را مشخص می کند.این متد یک شی از کلاس CalculatorCommand را ساخته ور در Constrouctor آن Calculator ، operator و operand را مقدار دهی میکند.سپس متد execute شی را فراخوانی می کند تا عمل محاسبه انجام گردد و در نهایت object را به فیلد _commands اضافه میکند و در نهایت  فیلد _current که ایندکس فعلی _coomands را نگهداری میکند یک واحد افزایش می یابد.

متد بعدی متد undo میباشد.ورودی این متد یک متغیر به اسم levels از نوع int  است که به این اشاره میکند که client تا چند سطح میخواهد عملیات undo را انجام دهد.سپس در متد یک حلقه for افزایش با حداکثر مقدار levels ساخته می شود و تا زمانی که _current بزرگ تر از صفر است (_current مساوی صفر یعنی حالت اولیه و شروع ما)لیست _commands را با کم کردن مقدار _current پیمایش میکند و در هر پیماش متد Unexecute را را اجرا می کند تا عملیات ریاضی برعکس آنچه انجام شده بود برای رسیدن به مقدار اولیه اجرا شود و این کار تا زمانی که مقدار _current ما بزرگ تر از صفر است ادامه پیدا می کند.

 

متد Redo همانطور که هم از اسمش پیداست وظیفه Redo کردن  را برعهده دارد.عملکرد این متد نیز شبیه متد Undo می باشد با این تفاوت که در هر پیمایش متد execute کلاس CalcultorCommand که نوع آن از جنس اینترفیس Command است فراخوانی مشود.

 

  class User
  {
    // Initializers
    private Calculator _calculator = new Calculator();
    private List<Command> _commands = new List<Command>();
    private int _current = 0;
 
    public void Redo(int levels)
    {
      Console.WriteLine("\n---- Redo {0} levels ", levels);
      // Perform redo operations
      for (int i = 0; i < levels; i++)
      {
        if (_current < _commands.Count - 1)
        {
          Command command = _commands[_current++];
          command.Execute();
        }
      }
    }
 
    public void Undo(int levels)
    {
      Console.WriteLine("\n---- Undo {0} levels ", levels);
      // Perform undo operations
      for (int i = 0; i < levels; i++)
      {
        if (_current > 0)
        {
          Command command = _commands[--_current] as Command;
          command.UnExecute();
        }
      }
    }
 
    public void Compute(char @operator, int operand)
    {
      // Create command operation and execute it
      Command command = new CalculatorCommand(
        _calculator, @operator, operand);
      command.Execute();
 
      // Add command to undo list
      _commands.Add(command);
      _current++;
    }
  }
}

 

و در نهایت Client ما می تواند با استفاده از کلاس User عملیت های Compute ، Redo و Undo را انجام دهد.

 

class MainApp
 {
   /// <summary>
   /// Entry point into console application.
   /// </summary>
   static void Main()
   {
     // Create user and let her compute
     User user = new User();

     // User presses calculator buttons
     user.Compute('+', 100);
     user.Compute('-', 50);
     user.Compute('*', 10);
     user.Compute('/', 2);

     // Undo 4 commands
     user.Undo(4);

     // Redo 3 commands
     user.Redo(3);

     // Wait for user
     Console.ReadKey();
   }
 }

 

 

این یک توضیح کلی و روان از الگوی طراحی Command بودش.با سرچی ساده در اینترنت می توانید مثال های بیشتری از این Pattern پیدا بکنید.همچنین فصل ۶ کتاب head_first_design_patterns کلا به توضیح این Design Pattern پرداخته است.

در مطلب بعدی به بررسی الگوی طراحی Interpreter خواهیم پرداخت.

موفق باشید