چرا ما به الگوهای طراحی نیازمندیم؟

 چرا ما به الگوهای طراحی نیازمندیم؟ آیا با الگوهای طراحی آشنایی دارید؟ در این مطلب قصد داریم کمی بیشتر با الگوهای طراحی و نحوه استفاده از آنها و همینطور مزایای آن ها صحبت کنیم.

در این مقاله که توسط Praseed Pai و Shine Xavier نویسندگان کتاب الگوهای طراحی نوشته شده است، ما سعی خواهیم کرد تا ضرورت انتخاب رویکرد مبتنی بر الگوهای طراحی برای توسعه نرم افزار را به خوبی بیان کنیم تا بتوانید آن را به صورت کامل درک کنید. ما این مطلب را با برخی از اصول توسعه نرم افزار شروع خواهیم کرد که ممکن است در هنگام انجام پروژه های بزرگ توسط الگوهای طراحی مفید باشد،  مثالی که ما با آن در این مقاله کار خود را انجام می دهیم با یک سری از الزامات خاص شروع می شود و به سمت اجرای اولیه می رود. سپس سعی خواهیم کرد که راه حل را با استفاده از الگوهای طراحی بهبود بخشیده شده، اصلاح کنیم و طرح خوبی را ارائه دهیم که از اینترفیس های برنامه نویسی به خوبی پشتیبانی کند.

در این فرایند ما چه خواهیم کرد؟

در این فرایند ما در مورد برخی از اصول توسعه نرم افزار که در ادامه آمده است صحبت خواهیم کرد و یاد می گیریم که چگونه در الگوهای طراحی  از آنها پیروی کنیم:

اصول SOLID برای OOP در الگوهای طراحی

سه نکته اساسی برای استفاده از الگوهای طراحی

الگوهای Archetype / neustadt

اشیا، مقادیر و اشیا انتقال داده ها

استفاده از .NET به صورت API برای معماری پلاگین در الگوهای طراحی

برخی از اصول توسعه نرم افزار در الگوهای طراحی

بدون استفاده از اصول پایه برنامه نویسی که در دست شما قرار دارد نوشتن یک کد نرم افزاری با کیفیت تقریبا غیر ممکن است، هدف از این بخش این است که ذوق و شوق خواننده را در رابطه با الگوهای طراحی به خود جلب کند. اگر به دنبال توضیحات مفصل درباره این اصول هستید می توانید به کتاب مربوط به آن مراجعه کنید. نویسندگان سعی کرده اند اصول کلیدی نرم افزاری را که به شما در نوشتن یه کد با کیفیت کمک می کند بیان کنند.

Kiss: keep it simple, stupid (برنامه خود را ساده نگه دارید)

DRY:  don’t repeat yourself ( از کارهای تکراری جلوگیری کنید)

YAGNI: You aren’t gonna need it(شما به آن نیازی ندارید)

Low coupling: Minimize coupling between classes (وابستگی میان کلاس ها را کاهش دهید)

Solid principles: Principles: Principles for better OOP ( اصول مورد نیاز برای شی گرایی بهتر)

ویلیام اوخام

ویلیام اوخام شخصی بود که قانون KISS که همان قانون برنامه خود را ساده نگه دارید بود را به حد اعلای خود رساند. به عبارت دیگر در برنامه نویسی می توان این قانون را به این شکل بیان کرد که " نوشتن برنامه به شیوه ای بسیار ساده و راحت که فقط با تمرکز بر روی یک راه حل خاص مسئله را حل می کند".

عبارت حد اعلی در این جا بسیار مهم است چرا که معمولا توسعه دهندگان نرم افزار به این دام می افتند که کدهای خود را کاملا به صورت کلی می نویسند تا از گسترش آن جلوگیری کنند. حتی اگر این کار در ابتدا کاملا جذاب به نظر برسد همه چیز به آرامی و کم کم به هم می ریزد. پیچیدگی های تصادفی در پایه کد معرفی می شوند و هدف از تعریف آنها در الگوهای طراحی این است که در به وجود آمدن سناریوهای غیر ممکن از آنها استفاده کنیم، گاهی اوقات این کار توسط کاهش خوانایی و نگهداری کد صورت می گیرد. اصل KISS را می توان برای هر تلاش انسانی به کار برد.

قانون Don’t repeat yourself

یک قانون که بیشتر برنامه نویسان معمولا در هنگام اجرای منطق دامنه وب سایت خود آن را فراموش می کنند. بیشتر اوقات در یک سناریو برنامه نویسی اشتراکی کدها به صورت ناگهانی کپی می شوند که دلیل این اتفاق عدم ارتباط و ویژگی های طراحی مناسب می باشد. این پدیده پایه کد است که باعث ایجاد اختلالات ظریف و ایجاد تغییرات بسیار دشوار می شود. با پیروی حداکثری از قانون DRY در تمام مراحل توسعه ما می توانیم از تلاش های اضافی جلوگیری کنیم و کدهای اشتراکی را به صورت کاملا سازگار طراحی کنیم. اگر برخلاف این قانون عمل کنید به احتمال زیاد هر چیزی را دو بار خواهید نوشت که به این کار wet گفته می شود.

قانون YAGNI در الگوهای طراحی 

این قانون در واقع قانون KISS را کامل می کند، این اصل در واقع یک هشدار برای کسانی است که سعی دارند کدهای خود را به صورت کلی بنویسند. در بیشتر اوقات در عمل از این کدها برای کارایی بیشتر در برنامه نویسی استفاده نمی شود. در هنگام برنامه نویسی باید اطمینان حاصل کنید که ارجاع های سخت افزاری به کلاس های به هم چسبیده صورت نگیرد. این یک اصل کلیدی است که بسیاری از الگوهای طراحی استفاده می شوند تا یک رفتار را در هنگام اجرا بدست آورند. یک فریم ورک dependency injection باید مورد استفاده قرار گیرد تا بتواند اتصال بین کلاس ها را کاهش دهد.

اصول SOLID

اصول SOLID در الگوهای طراحی  در واقع دستورالعمل هایی هستند که برای نوشتن نرم افزارهای شی گرا مورد استفاده قرار می گیرند. در واقع این اصل مخفف 5 قانون زیر می باشد:

Single Responsibility Principle(SRP):

کلاسی است که فقط باید دارای یک مسئولیت باشد. اگر این کلاس بیش از یک کار غیر مرتبط به هم را انجام دهد ما نیاز داریم که کلاس را جدا کنیم.

Open Close Principle(OPC):

این کلاس باید برای افزونه ها باز شود و برای عملیات های اصلاح باید بسته شود.

Liskov Substitution Principle( LSP):

این دستور پس از Barbara Liskov و برنده جایزه تورینگ به این نام نامگذاری شد، او معتقد است که کلاس فرزند ( کلاسی که ارث برده است) می تواند کلاس پدر را که همان کلاس پایه است بدون تاثیر بر عملکردش جایگزین کند. اگر چه به نظر می رسد این موضوع واضح است اما اغلب پیاده سازی های آن دارای نکاتی است که معمولا این اصل را نقض می کند.

سایر قوانین اصول SOLID در الگوهای طراحی

اصل Interface segregation principle یا اصل ISP:

این اصل بیشتر علاقه مند است که چندین اینترفیس برای یک کلاس وجود داشته باشد ( مانند کلاس ها که می توانند کامپوننت نیز نامیده شوند). به دلیل داشتن یک اینترفیس Uber این اصل می تواند تمامی متدها را برای شما اجرا کند ( هر دو متدهای مربوط و غیر مربوط به همراه راه حل).

اصل Dependency Inversion یا همان اصل DI:

این یک اصل است که برای طراحی فریم ورک بسیار مفید است، در مورد فریم ورک ها کدهای سمت کلاینت توسط کدهای سمت سرور فراخوانی می شوند، درست برخلاف پردازش هایی که کدهای سمت سرور توسط کدهای سمت کاربر فراخوانی می شوند. اصل اساسی در این مورد این است که abstraction به هیچ وجه نباید به جزئیات بستگی داشته باشد، بلکه جزئیات باید به صورت کامل به abstraction بستگی داشته باشند. این اصل در واقع اصل هالیوود نامیده می شود.

چرا الگوهای طراحی مورد نیاز هستند؟

به گفته نویسندگان سه مزیت اصلی توسعه نرم افزار بر اساس الگوهای طراحی عبارت اند از:

1-  یک راه حل ( یا زبان برنامه نویسی یا یک پلتفرم) برای برقراری ارتباطات بین نرم افزارها

2-  یک ابزار برای اصلاح ایده ها

3-  طراحی بهتری API در الگوهای طراحی

با ظهور الگوهای طراحی جامعه برنامه نویسان باید یک زبان اصلی برای برقراری ارتباط در مورد طراحی نرم افزار، معماری و پیاده سازی آن پیدا کرد. توسعه نرم افزار مانند یک وسیله نقلیه است که راه های زیادی برای حرکت آن وجود دارد. الگوهای طراحی موفق شده اند که این راه ها را یکپارچه کنند.

نکته ای مهم درباره نیازمندی به الگوهای طراحی

امروزه اکثر برنامه نویسان در سراسر دنیا که کارهای ارزشمندی انجام داده اند می توانند این زبان را متوجه شوند و به آن زبان صحبت کنند. ما معتقدیم که شما در پایان این مقاله می توانید همین کار را انجام دهید، در پایان از شما خواسته می شود که در مورد اجرای اخیر خود، موارد زیر را بیان کنید:

برای مثال در محاسبات مالی ما از یکی از الگوهای طراحی استفاده می کنیم تا به منطق محاسبه رسیدگی کنیم. دستورات ( یا همان هندلرها) با استفاده از یک فایل XML پیکربندی می شوند. ما دستورات را ذخیره می کنیم و از مقداردهی اشیا بیشتر از طریق اعمال محدودیت های تک به تک در فراخوانی جلوگیری می کنیم. دستورات مربوط به اشیا یک پیاده سازی پایه دارند که دستورات به هم متصل شده اشیا از متد خالی استفاده می کند تا متدها را دوباره نویسی کند.

زبان الگوهای طراحی

برای بعضی از توسعه دهندگان توصیف زبان یا پلتفرم مستقل از نرم افزار که شروع به برنامه نویسی شده است به اندازه کافی قابل فهم است تا برنامه نویس رویکرد استفاده شده را متوجه شود. این کار باعث افزایش بهره وری برنامه نویسان ( در تمامی مراحل SDLC از جمله توسعه، نگهداری و پشتیبانی) خواهد شد، چرا که برنامه نویسان قادر خواهند بود مدل ذهنی خوبی از کد پایه را ایجاد کنند. بدون الگوهای طراحی توصیف مختصری از این طراحی تقریبا غیر ممکن است. در یک سناریو توسعه نرم افزار Agile، ما نرم افزار را به صورت دوره ای ایجاد می کنیم. زمانی که ما به یک بلوغ خاصی در ماژول می رسیم، برنامه نویسان کدهای خود را تغییر می دهند، استفاده از الگوهای طراحی می تواند به شما کمک کند تا منطق برنامه را سازماندهی کنید.

محاسبه مالیات بر درآمد شخصی( یک مورد مطالعاتی)

به جای این که درباره مزایای الگوهای طراحی صحبت کنیم مثال زیر به ما کمک می کند تا همه چیز را در عمل ببینیم. محاسبه مالیات بر درآمد سالانه یک مسئله شناخته شده در سراسر جهان است. ما یک زمینه بسیار کاربردی را انتخاب کردیم که به خوبی به مسائل مربوط به توسعه نرم افزار آشنا شوید و بتوانید بر روی این مسائل تمرکز کنید. اپلیکیشن ما باید ورودی های مربوط به مشخصات جمعیت شناسی را دریافت کند که این مشخصات عبارت اند از UID، نام، سن، جنسیت، محل سکونت. بعد از دریافت این مشخصات از یک شهروند باید اقدام به دریافت اطلاعات مربوط به درآمد هر شخص از جمله CESS، BRA، Basic و Deducations کنید.

توضیحات جانبی درباره این مثال

سیستم ما باید بر اساس مشخصات جمعیت شناسی کار کند و یک منطق جداگانه برای شهروندان سالخورده، نوجوانان، افراد معلول، زنان سالخورده و دیگران داشته باشد. در واقع در این سیستم عوامل جمعیت شناختی تاثیر بالایی دارند، پارامترهایی مانند سن، جنس و مکان باید مورد بررسی قرار گیرند و افراد بر اساس این پارامترها دسته بندی شوند و محاسبات خاصی بر روی آنها صورت گیرد. به عنوان اولین کار ما منطق شهروندان ارشد و شهروندان عادی را اجرا خواهیم کرد. پس از بحث های اولیه برنامه نویس ما باید یک نمونه اولیه را مانند شکل زیر بسازد:

Archetype ها و الگوی تجاری Archetype

روان شناس افسانه ای کشور سوئیس، کارل گوستاو یونگ، مفهوم Archetype ها را برای توضیح نهادهای اساسی که از یک مخزن مشترک تجربیات انسانی استفاده می کنند به وجود آورد. امروزه مفهوم Archetype از صنعت روان شناسی به صنعت نرم افزار رسیده است. الگوهای طراحی Arlow/Neustadt الگوهای طراحی تجاری مانند Party، Customer Call، Product، Money، Unit و Inventory  را توضیح می دهند. یک نمونه بسیار جامع از Archetype ها Maven Archetype می باشد که به ما کمک می کند تا پروژه هایی با طبیعت مختلف بسازیم. به عنوان مثال می توانیم برنامه های J2EE، افزونه های اکلیپس و پروژه های OSGI را بسازیم. الگوهای طراحی و همینطور روش های مایکروسافت از Archetype ها استفاده می کنند تا ساختارهایی مانند برنامه های کاربردی وب، برنامه های کاربردی کلاینت، برنامه های کاربردی موبایل و برنامه های کاربردی خدمات را مورد هدف قرار دهد.

معرفی برخی از Archetype هایی که از آنها استفاده خواهیم کرد

در این مورد ما برخی از Archetype هایی که بیشتر رایج هستند را تعریف می کنیم، برخی از این Archetype ها عبارت اند از:

Senior Citizen Female:

متقاضیان مالیاتی که زن هستند و بالای 60 سال سن دارند

Senior Citizen:

متقاضیان مالیاتی که مرد هستند و بالای 60 سال سن دارند

Ordinary Citizen:

متقاضیان مالیاتی که مرد یا زن هستند و بالای 18 سال سن دارند

Disabled Citizen:

متقاضیان مالیاتی که دارای معلولیت جسمانی هستند

Military Personnel:

متقاضیان مالیاتی که پرسنل نظامی هستند

Juveniles:

متقاضیان مالیاتی که سن آنها کمتر از 18 سال است

ما از پارامترهای جمعیت شناختی به عنوان تابع تشخیصی استفاده می کنیم تا Archetype های مربوط به هر موجودیت را شناسایی کنیم، تمام ایده استفاده از Archetype ها این است که منطق محاسبات مالی را در اطراف آنها سازماندهی کنیم.

اشیا مربوط به شخصیت، مقادیر و انتقال داده ها

ما قصد داریم یک کلاس ایجاد کنیم که یک شهروند را نشان دهد، از آنجایی که شهروندان نیاز به یک کد شناسایی منحصر به فرد دارند، ما قصد داریم یک شی را ایجاد کنیم که شی ارجاعی نامیده می شود ( بر اساس کاتالوگ DDD). شناسه جهانی یا همان UID در واقع یک هندل است که یک برنامه کاربردی به آن اشاره دارد. اشیا مربوط به شخصیت بر اساس ویژگی های آنها شناسایی نمی شود، زیرا ممکن است دو نفر به یک نام وجود داشته باشد. شناسه منحصر به فرد یک شی شخصیت را شناسایی می کند.

کدهای مربوط به این بخش
 

public class Taxable Entity

{

 public int Id { get; set; }

 public string Name { get; set; }

 public int Age { get; set; }

 public char Sex { get; set; }

 public string Location { get; set; }

 public TaxParamVO tax params { get; set; }

}

ادامه بحث قبلی

در تعریف کلاس قبلی شناسه منحصر به فرد شی یک شخص خاص را پیدا می کرد، حال Tax Param یک شی مقداری است که مربوط به شخصیت می شود. اشیا مقادیر، دارای هویت مفهومی نیستند، بلکه تنها برخی از صفات اشخاص را نگهداری می کنند و آنها را توصیف می کنند. تعریف Tax Params به صورت زیر می باشد:

public class Tax ParamVO

{

 public double Basic {get;set;}

 public double DA { get; set; }

 public double HRA { get; set; }

 public double Allowance { get; set; }

 public double Deductions { get; set; }

 public double Cess { get; set; }

 public double Tax Liability { get; set; }

 public bool Computed { get; set; }

}

از زمانی که اپلیکیشن Smalltalk نوشته شده است MVC یا همان Model-view-controller بیشترین مورد استفاده برای ساخت برنامه های کاربردی بوده است. نرم افزار به یک لایه مدل ( که اغلب با داده ها کار دارد)، یک لایه گرافیکی و نمایشی ( که به عنوان لایه نمایش به کاربر مورد استفاده قرار می گیرد) و یک لایه کنترل کننده ( متصل کننده دو لایه قبلی) تقسیم می شود. در سناریو توسعه وب، این قسمت ها به لحاظ سخت افزاری در سراسر دستگاه شما تقسیم می شوند. برای انتقال داده ها در بین لایه ها الگوهای طراحی J2EE از کاتالوگ DTO را شناسایی می کند تا داده ها را بین لایه ها جابجا کند. شی DTO به شرح زیر تعریف می شود:
 

public class Tax DTO

{

 public int id { }

 public TaxParamVO tax params { }

}

موتور محاسباتی

ما نیاز داریم که پردازش گرافیکی برنامه، اعتبار سنجی ورودی ها و محاسبه تولید یک نتیجه که بتواند تمامی نیازمندی های کاربر را رفع کند را از هم جدا می کنیم. موتور محاسباتی منطق متفاوتی را اجرا خواهد کرد که این منطق بستگی به دستوراتی دارد که برنامه دریافت می کند. دستور Gof که یکی از الگوهای طراحی است برای اجرای منطق برنامه بر اساس دستور دریافتی قابل استفاده می باشد. دستورات از 4 بخش مجزا تشکیل می شوند که عبارت اند از:

1- اشیا دستورات

2- پارامترها

Command Dispatcher- 3

4- کلاینت

 

توضیحاتی درباره موتور محاسباتی( بخش اول)

اینترفیس اشیا دستورات دارای یک متد اجرا می باشد، پارامترها از طریق یک بسته به اشیا دستورات ارسال می شوند، کلاینت شی دستور را به وسیله ارسال یک بسته از پارامترها به وسیله Command dispatcher فراخوانی می کند. پارامترها به شی دستور به وسیله داده ساختار زیر ارسال می شوند:

public class COMPUTATIONal CONTEXT

{

 private Dictionary symbols = new

 Dictionary();

 public void Put(string k, Object value) {

    symbols.Add(k, value);

 }

 public Object Get(string k) { return symbols[k]; }

}

اینترفیس دستور محاسباتی که تمام اشیا فرمان را اجرا می کند فقط دارای یک متد اجرا می باشد که بعدا آن را معرفی می کنیم. متد اجرا یک بسته از پارامترها را دریافت می کند. داده ساختار CPMPUTATIONal CONTEXT در اینجا به عنوان بسته عمل می کند.

Interface Computation Command

{

 bool Execute(COMPUTATIONal CONTEXT ctx);


}

توضیحات درباره موتور محاسباتی( بخش دوم)

از آنجایی که ما یک اینترفیس فرمان را اجرا کردیم و یک بسته از پارامترها را نیز ارسال کردیم، حالا زمان آن است که ما یک شی فرمان را اجرا کنیم. برای سادگی بیشتر ما دو دستور را اجرا خواهیم کرد.

public class Senior Citizen Command : Computation Command

{

 public bool Execute(COMPUTATIONal CONTEXT ctx)

 {

    Tax DTO td = (Tax DTO)ctx.Get("tax_cargo");

    //---- Instead of computation, we are assigning

    //---- constant tax for each archetypes

    td.tax params.Tax Liability = 1000;

    td.tax params.Computed = true;

    return true;

 }

}


public class Ordinary Citizen Command : Computation Command

{

 public bool Execute(COMPUTATIONal CONTEXT ctx)

 {

    Tax DTO td = (Tax DTO)ctx.Get("tax_cargo");

    //---- Instead of computation, we are assigning

    //---- constant tax for each archetypes

    td.tax params.Tax Liability = 1500;

    td.tax params.Computed = true;

    return true;

 }

}

دستورات توسط یک شی Command dispatcher فراخوانی می شوند که یک رشته Archetype و یک شی COMPUTATIONal CONTEXT دریافت می کند. در واقع Command dispatcher به عنوان یک لایه API برای اپلیکیشن عمل می کند.

class CommandDispatcher

{

 public static bool Dispatch(string archetype,   COMPUTATIONal CONTEXT ctx)

 {

    if (archetype == "Senior Citizen")

    {

     Senior Citizen Command cmd = new Senior Citizen Command();

     return cmd.Execute(ctx);

    }

    else if (archetype == "Ordinary Citizen")

    {

      Ordinary Citizen Command cmd = new Ordinary Citizen Command();

      return cmd.Execute(ctx);

    }

    else {

     return false;

    }

 }

}

اتصال اپلیکیشن به موتور محاسباتی

داده های مربوط به UI برنامه، حال آن که در دسکتاپ باشند و یا در موتور محاسباتی کم کم به سمت موتور محاسباتی جریان پیدا می کنند. View Handler که در زیر آمده است به شما نشان می دهد که چگونه داده ها از UI اپلیکیشن شما به سمت موتور محاسباتی جریان پیدا می کنند:

public static void View Handler(Tax Calc Form tf)

{

 Tax ableEntity te = Get Entity From UI(tf);

 if (te == null){

    ShowError();

    return;

 }

 string archetype = ComputeArchetype(te);

 COMPUTATION_CONTEXT ctx = new COMPUTATIONal CONTEXT();

 Tax DTO td = new Tax DTO { id = te.id, tax params =   te.tax params};

 ctx.Put("tax_cargo",td);

 bool rs = Command Dispatcher.Dispatch(archetype, ctx);

 if ( rs ) {

    Tax DTO temp = (Tax DTO)ctx.Get("tax_cargo");

    tf.Liabilitytxt.Text =     Convert.ToString(temp.tax params.Tax Liability);

    tf.Refresh();

 }

}

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

نکاتی مهم درباره موتور محاسباتی

در ابتدا ما در این مورد از الگوهای طراحی برای شهروندان ارشد و شهروندان عادی محاسبات متفاوتی داشتیم، حال فقط کافی است ما یک Archetype جدید اضافه کنیم. در عین حال به منظور ایجاد نرم افزاری قابل گسترش ( که به آسانی به سایر اپلیکیشن ها متصل شود) و قابل نگهداری باشد، بهتر است که ما توانایی پشتیبانی از Archetype های جدید را در قسمت های مختلف این برنامه بگنجانیم.

شی Command Dispatcher برای مقابله با Archetype های اضافی اصلا مقیاس خوبی نیست، ما هر زمان که یک Archetype جدید را اضافه می کنیم باید کل این مجموعه را تغییر دهیم، زیرا منطق محاسبه مالیات برای هر Archetype متفاوت است. ما نیاز به ایجاد یک معماری قابل برنامه ریزی در الگوهای طراحی برای اضافه کردن و حذف Archetype ها داریم.

سیستم پلاگین برای این که سیستم را قابل گسترش کنید

خوشبختانه .NET Reflection API یک مکانیزم برای شما فراهم می کند که یک کلاس را در هنگام اجرا شدن بارگذاری کنید و متدهای درون آن را فراخوانی کنید. یک برنامه نویس حرفه ای کار کردن با Reflection API را یاد بگیرد تا بتواند سیستم هایی طراحی کند که به صورت دینامیک قابل تغییر باشند. در حقیقت بیشتر تکنولوژی ها مانند ASP.NetT، فریم ورک Entity ،.Net Remoting و WCF معروف شده اند و فقط به دلیل توانایی های Reflection API در پشته .NET

از الان ما از یک فایل پیکربندی XML استفاده می کنیم تا منطق برنامه محاسبه مالی خود را تعیین کنیم. یک فایل XML ساده در ادامه داده شده است. این را کپی کنید:

محتوای فایل XML را می توان به راحتی با استفاده از LINQ خواند، ما یک شی دیکشنری را با قطعه کد زیر ایجاد خواهیم کرد:

private Dictionary Load Data(string xml file)

{

 return XDocument.Load(xml file)

 .Descendants("plugins")

 .Descendants("plugin")

 .To Dictionary(p => p.Attribute("archetype").Value,

 p => p.Attribute("command").Value);

}

 

برای ارسال نظر نیاز است وارد سایت شوید. در صورت نداشتن حساب کاربری عضو شوید.