یک سالی میشود که روی یک فریمورک رابط کاربری بصورت
متن باز به اسم HandyControl مشارکت دارم، این فریمورک در درجه اول مخصوص
برنامه نویسهای چین طراحی شده بود اما با بازخوردهایی که دریافت کرد،
جامعه انگلیسی زبان را هم پشتیبانی میکند. بدلیل اینکه هدف این فریمورک
ارائه کنترلهای ساده و بدور از پیچیدگی هست، Nabian با
اضافه کردن کنترلهای پیچیده مخالف هست. به همین دلیل تصمیم گرفتم تا شاخه
جدایی از این پروژه را ایجاد کنم و کنترلها و ویژگیهای مختلفی که سازگار
با برنامه نویسهای ایرانی باشد را اضافه کنم. درحال حاضر تقویم شمسی،
ساعت_تقویم شمسی، پشتیبانی از کد ملی، حروف فارسی در نوع ورودی TextBox
،استایلهای جدید بارگذاری، کنترل BusyIndicator، کنترل سرعت شمار
(SpeedoMeter) و چندین کلاس هلپر جهت کار با رجیستری، رمزنگاری و... را به
پروژه اضافه کردم.(لیست ویژگیهای اضافه شده را میتوانید از این قسمت مشاهده
کنید) (جهت مشاهده کنترلها و ویژگیهای اضافه شده در نسخه شخصی سازی شده
بعد از اجرای دمو از بخش Controls بخش PersianToolkit را انتخاب کنید)
صفحات پروژه اصلی===> سورس برنامه | صفحه ناگت | صفحه مارکت پلیس | مستندات چینی | مستندات انگلیسی | مستندات ویکی
در ادامه مطالب مربوط به برنامه نویسی تابعی، قصد دارم بیشتر وارد کد شویم و مباحث عنوان شده را در دنیای کد پیاده سازی کنیم. هدف این قسمت، refactor کردن کد موجود به یک معماری immutable هست. پیشتر درباره immutable ها صحبت کردیم. ابتدا برای یکسان سازی ادبیات مورد استفاده، چند کلمه را مجددا تعریف خواهیم کرد:
- Immutability: عدم توانایی تغییر داده
- State: دادههایی که در طول زمان تغییر میکنند
- Side Effect: تغییری که روی دادهها اتفاق میافتد
در قطعه کد زیر سعی شدهاست تفاوت یک کلاس Stateless و stateful را به سادگی نشان دهیم:
//Stateful public class UserProfile { private User _user; private string _address; public void UpdateUser(int userId, string name) { _user = new User(userId, name); } } //Stateless public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
چرا Immutable بودن مهم است؟
هر عمل mutable معادل کدی غیر شفاف است. در واقع وابستگی هر عملی که انجام میدهیم به state، باعث میشود که شرایط ناپایداری را در کد داشته باشیم. به طور مثال در یک عملیات چند نخی تصور کنید که چندین نخ به طور همزمان میتوانند state را تغییر دهند و مدیریت این قضیه باعث به وجود آمدن کدهایی ناخوانا و تحمیل پیچیدگی بیشتر به کد خواهد شد.
در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنهی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی state کل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honest بازنویسی میکنیم:
public class UserProfile { private readonly User _user; private readonly string _address; public UserProfile(User user,string address) { _user = user; _address = address; } public UserProfile UpdateUser(int userId, string name) { var newUser = new User(userId, name); return new UserProfile(newUser,_address); } } public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
در این مثال متد UpdateUser به جای void، یک شی از جنس کلاس UserProfile را بر میگرداند. کلاس UserProfile هم برای وهله سازی نیاز به یک شیء از جنس User و Address را دارد. بنابراین مطمئن هستیم که مقدار دهی شدهاند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.
Immutable
بودن باعث میشود:
- خوانایی کد افزایش پیدا کند
- جای واحدی برای Validate کردن داشته باشیم
- به صورت ذاتی Thread Safe باشیم
در مورد محدودیتهایی که در کار با اشیاء Immutable باید در نظر داشته باشیم، میتوان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutable امکاناتی در نظر گرفته شده که این هزینه را کاهش میدهند. به طور مثال میتوانیم از کلاس ImmutableList استفاده کنیم و از ایجاد اشیاء اضافهتر و تحمیل بار اضافی به GC جلوگیری کنیم. یک مثال:
//Create Immutable List ImmutableList<string> list = ImmutableList.Create<string>(); ImmutableList<string> list2 = list.Add("Salam"); //Builder ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>(); builder.Add("avali"); builder.Add("dovomi"); builder.Add("sevomi"); ImmutableList<string> immutableList = builder.ToImmutable();
چطور با side effect کنار بیایم؟
یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Query است. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Command در نظر میگیریم. Command ها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Query ها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماریهای خاصی مانند Domain Driven میباشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماریها وجود ندارد.
به مثال زیر دقت کنید. سعی کردم قسمتهای Command و Query را از هم جدا کنم:
در واقع هر برنامه میتواند شامل دو قسمت باشد:
قسمتی که در آن منطق تجاری برنامه پیاده سازی میشود و باید به صورت Immutable
باشد که یک خروجی را تولید میکند و قسمت دیگر برنامه که خروجی تولید شده را برای ذخیره
سازی وضعیت سیستم استفاده میکند.
در واقع یک هسته Immutable، ورودی را دریافت کرده و خروجیهای مورد نیاز را تولید میکند و همه اینها در دل یک پوستهMutable پیاده سازی میشوند که ما در اینجا به آن اصطلاحا Mutable Shell میگوییم.
برای مسائلی که در بالا صحبت شد، نمونهای را آماده کردهام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبتها را در فایلی ذخیره و بازیابی میکند ( mutate ) و منطق مربوط به نوبتها و زمان ویزیت آن میتواند به صورت immutable پیاده سازی شود. این کد در دو حالت functional و غیر functional پیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کدها، آنها را روی گیتهاب قرار داده و شما میتوانید از اینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programming توانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در قسمتهای بعدی به مواردی مانند مدیریت exception ها و کار با null ها و ... خواهیم پرداخت.
در مطلب پیشین به پیرامون خود نگاه کردیم و اشیاء گوناگونی را مشاهده کردیم که در حقیقت دنیای ما را تشکیل داده اند و فعالیتهای روزمره ما با استفاده از آنها صورت میگیرد. ایده ای به ذهنمان رسید. اشیاء و مفاهیم مرتبط به آن میتواند روش بهتر و موثرتری برای تقسیم کدهای برنامه باشد. مثلاً اگر کل کدهای برنامه که مسئول حل یکی از مسئلههای کوچک یاد شده است را یکجا بسته بندی کنیم و اصولی که از اشیاء واقعی پیرامون خود آموختیم را در مورد آن رعایت کنیم به برنامه بسیار با کیفیتتری از نظر خوانایی، راحتی در توسعه، اشکال زدایی سادهتر و بسیاری موارد دیگر خواهیم رسید.
توسعه دهندگان زبانهای برنامه نویسی که با ما در این مورد هم عقیده بوده اند دست به کار شده و دستورات و ساختارهای لازم برای پیاده کردن این ایده را در زبان برنامه نویسی قرار دادند و آن را زبان برنامه نویسی شیء گرا نامیدند. حتی جهت برخورداری از قابلیت استفاده مجدد از کد و موارد دیگر به جای آنکه کدها را در بسته هایی به عنوان یک شیء خاص قرار دهیم آنها را در بسته هایی به عنوان قالب یا نقشه ساخت اشیاء خاصی که در ذهن داریم قرار میدهیم. یعنی مفهوم کلاس یا رده که پیشتر اشاره شد. به این ترتیب یک بار مینویسیم و بارها استفاده میکنیم. مانند همان مثال بازیکن در بخش نخست. هر زمان که لازم باشد با استفاده از دستورات مربوطه از روی کدهای کلاس که نقشه یا قالب ساخت اشیاء هستند شیء مورد نظر را ساخته و در جهت حل مسئله مورد نظر به کار میبریم.
حال برای آنکه به طور عملی بتوانیم از ایده شیء گرایی در برنامه هایمان استفاده کنیم و مسائل بزرگ را حل کنیم لازم است ابتدا مقداری با جزییات و دستورات زبان در این مورد آشنا شویم.
تذکر: دقت کنید برای آنکه از ایده شیء گرایی در برنامهها حداکثر استفاده را ببریم مفاهیمی در مهندسی نرم افزار به آن اضافه شده است که ممکن است در دنیای واقعی نیازی به طرح آنها نباشد. پس لطفاً تلاش نکنید با دیدن هر مفهوم تازه بلافاصله سعی در تطبیق آن با محیط اطراف کنید. هر چند بسیاری از آنها به طور ضمنی در اشیاء پیرامون ما نیز وجود دارند.
زبان برنامه نویسی مورد استفاده برای بیان مفاهیم برنامه نویسی در این سری مقالات زبان سی شارپ است. اما درک برنامههای نوشته شده برای علاقه مندان به زبانهای دیگری مانند وی بی دات نت نیز دشوار نیست. چراکه اکثر دستورات مشابه است و تبدیل Syntax نیز به راحتی با اندکی جستجو میسر میباشد. لازم به یادآوری است زبان سی شارپ به بزرگی یا کوچکی حروف حساس است.
تشخیص و تعریف کلاسهای برنامه
کار را با یک مثال شروع میکنیم. فرض کنید به عنوان بخشی از راه حل یک مسئله بزرگ، لازم است محیط و مساحت یک سری چهارضلعی را محاسبه کنیم و قصد داریم این وظیفه را به طور کامل بر عهده قطعه کدهای مستقلی در برنامه قرار دهیم. به عبارت دیگر قصد داریم متناظر با هر یک از چهارضلعیهای موجود در مسئله یک شیء در برنامه داشته باشیم که قادر است محیط و مساحت خود را محاسبه و ارائه نماید. کلاس زیر که با زبان سی شارپ نوشته شده امکان ایجاد اشیاء مورد نظر را فراهم میکند.public class Rectangle { public double Width; public double Height; public double Area() { return Width*Height; } public double Perimeter() { return 2*(Width + Height); } }
در این قطعه برنامه نکات زیر قابل توجه است:
- کلاس با کلمه کلیدی class تعریف میشود.
- همان طور که مشاهده میکنید تعریف کلاس با کلمه public آغاز شده است. این کلمه محدوده دسترسی به کلاس را تعیین میکند. در اینجا از کلمه public استفاده کردیم تا بخشهای دیگر برنامه امکان استفاده از این کلاس را داشته باشند.
- پس از کلمه کلیدی class نوبت به نام کلاس میرسد. اگرچه انتخاب نام مورد نظر امری اختیاری است اما در آینده حتماً اصول و قراردادهای نامگذاری در داتنت را مطالعه نمایید. در حال حاضر حداقل به خاطر داشته باشید تا انتخاب نامی مناسب که گویای کاربرد کلاس باشد بسیار مهم است.
- باقیمانده کد، بدنه کلاس را تشکیل میدهد. جاییکه ویژگی ها، رفتارها و ... یا به طور کلی اعضای کلاس تعریف میشوند.
ایجاد شیء از یک کلاس و نحوه دسترسی به شیء ایجاد شده
شیء و کلاس چیزهای متفاوتی هستند. یک کلاس نوع یک شیء را تعریف میکند. اما یک شیء یک موجودیت عینی و واقعی بر اساس یک کلاس است. در اصطلاح از شیء به عنوان یک نمونه (Instance) یا وهله ای از کلاس مربوطه یاد میکنیم. همچنین به عمل ساخت شیء نمونه سازی یا وهله سازی گوییم.برای ایجاد شیء از کلمه کلیدی new و به دنبال آن نام کلاسی که قصد داریم بر اساس آن یک شیء بسازیم استفاده میکنیم. همان طور که اشاره شد کلاس یک نوع را تعریف میکند. پس از آن میتوان همانند سایر انواع مانند int, string, … برای تعریف متغیر استفاده نمود. به مثال زیر توجه کنید.
Rectangle rectangle = new Rectangle();
Rectangle rectangle;
Rectangle rectangle1 = new Rectangle(); Rectangle rectangle2 = rectangle1;
حالا میتوان شیء ساخته شده را با استفاده از ارجاعی که به آن داریم به کار برد.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5; rectangle.Height = 10; double a = rectangle.Area();
فیلدها
اگر به تعریف کلاس دقت کنید مشخص است که دو متغییر Width و Height را با سطح دسترسی عمومی تعریف کرده ایم.به متغیرهایی از هر نوع که مستقیماً درون کلاس تعریف شوند (و نه مثلاً داخل یک تابع درون کلاس) فیلد میگوییم. فیلدها از اعضای کلاس دربردارنده آنها محسوب میشوند.
تعریف فیلدها مستقیماً در بدنه کلاس با یک Access Modifier شروع میشود و به دنبال آن نوع فیلد و سپس نام دلخواه برای فیلد میآید.
تذکر: نامگذاری مناسب یکی از مهمترین اصولی است که یک برنامه نویس باید همواره به آن توجه کافی داشته باشد و به شدت در بالا رفتن کیفیت برنامه موثر است. به خاطر داشته باشید تنها اجرا شدن و کار کردن یک برنامه کافی نیست. رعایت بسیاری از اصول مهندسی نرم افزار که ممکن است نقش مستقیمی در کارکرد برنامه نداشته باشند موجب سهولت در نگهداری و توسعه برنامه شده و به همان اندازه کارکرد صحیح برنامه مهم هستند. بنابراین مجدداً شما را دعوت به خواندن مقاله یاد شده بالا در مورد اصول نامگذاری صحیح میکنم. هر مفهوم تازه ای که میآموزید میتوانید به اصول نامگذاری همان مورد در مقاله پیش گفته مراجعه نمایید. همچنین افزونه هایی برای Visual Studio وجود دارد که شما را در زمینه نامگذاری صحیح و بسیاری موارد دیگر هدایت میکنند که یکی از مهمترین آنها Resharper نام دارد.
مثال:
// public field (Generally not recommended.) public double Width;
فیلدها معمولاً با سطح دسترسی خصوصی و برای نگهداری از دادههایی که مورد نیاز بیش از یک متد (یا تابع) درون کلاس است و آن دادهها باید پس از خاتمه کار یک متد همچنان باقی بمانند استفاده میشود. بدیهی است در غیر اینصورت به جای تعریف فیلد میتوان از متغیرهای محلی (متغیری که درون خود تابع تعریف میشود) استفاده نمود.
همان طور که پیشتر اشاره شد برای دسترسی به یک فیلد ابتدا یک نقطه پس از نام شیء درج کرده و سپس نام فیلد مورد نظر را مینویسیم.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5;
public class Rectangle { public double Width = 5; // ... }
متدها
متدها قطعه کدهایی شامل یک سری دستور هستند. این مجموعه دستورات با فراخوانی متد و تعیین آرگومانهای مورد نیاز اجرا میشوند. در زبان سی شارپ به نوعی تمام دستورات در داخل متدها اجرا میشوند. در این زبان تمامی توابع در داخل کلاسها تعریف میشوند و بنابراین همه متد هستند.متدها نیز مانند فیلدها در داخل کلاس تعریف میشوند. ابتدا یک Access Modifier سطح دسترسی را تعیین مینماید. سپس به ترتیب نوع خروجی، نام متد و لیست پارامترهای آن در صورت وجود درج میشود. به مجموعه بخشهای یاد شده امضای متد میگویند.
پارامترهای یک متد داخل یک جفت پرانتز قرار میگیرند و با کاما (,) از هم جدا میشوند. یک جفت پرانتز خالی نشان دهنده آن است که متد نیاز به هیچ پارامتری ندارد.
بار دیگر به بخش تعریف متدهای کلاسی که ایجاد کردیم توجه نمایید.
public class Rectangle { // ... public double Area() { return Width*Height; } public double Perimeter() { return 2*(Width + Height); } }
همچنین توجه نمایید این شیء برای محاسبه مساحت و محیط خود نگاهی به ویژگیهای خود یعنی عرض و ارتفاعش که در فیلدهای آن نگهداری میکنیم میاندازد.
فراخوانی متد یک شیء همانند دسترسی به فیلد آن است. ابتدا نام شیء سپس یک نقطه و به دنبال آن نام متد مورد نظر به همراه پرانترها. آرگومانهای مورد نیاز در صورت وجود داخل پرانتزها قرار میگیرند و با کاما از هم جدا میشوند. که البته در این مثال متد ما نیازی به آرگومان ندارد. به همین دلیل برای فراخوانی آن تنها یک جفت پرانتز خالی قرار میدهیم.
در این بخش به دو مفهوم پارامتر و آرگومان اشاره شد. تفاورت آنها چیست؟
در هنگام تعریف یک متد نام و نوع پارامترهای مورد نیاز را تعیین و درج مینماییم. حال وقتی قصد فراخوانی متد را داریم باید مقادیر واقعی که آرگومان نامیده میشود را برای هر یک از پارامترهای تعریف شده فراهم نماییم. نوع آرگومان باید با نوع پارامتر تعریف شده تطبیق داشته باشد. اما اگر یک متغیر را به عنوان آرگومان در هنگام فراخوانی متد استفاده میکنیم نیازی به یکسان بودن نام آن متغیر و نام پارامتر تعریف شده نیست.
متدها میتوانند یک مقدار را به کدی که آن متد را فراخوانی کرده است بازگشت دهند.
Rectangle rectangle = new Rectangle(); rectangle.Width = 10.5; rectangle.Height = 10; double p = rectangle.Perimeter();
نکته: کلمه return علاوه بر بازگشت مقدار مورد نظر سبب پایان اجرای متد نیز میشود. حتی در صورتی که نوع خروجی یک متد void تعریف شده باشد استفاده از کلمه return بدون اینکه مقداری به دنبال آن بیاید میتواند برای پایان اجرای متد، در صورت نیاز و مثلاً برقراری شرطی خاص مفید باشد. بدون کلمه return متد زمانی پایان مییابد که به پایان قطعه کد بدنه خود برسد. توجه نمایید که در صورتی که نوع خروجی متد چیزی به جز void است استفاده از کلمه return به همراه مقدار مربوطه الزامی است.
مقدار خروجی یک متد را میتوان هر جایی که مقداری از همان نوع مناسب است مستقیماً به کار برد. همچنین میتوان آن را در یک متغیر قرار داد و سپس از آن استفاده نمود.
به عنوان مثال کلاس ساده زیر را در نظر بگیرید که متدی دارد برای جمع دو عدد.
public class SimpleMath { public int AddTwoNumbers(int number1, int number2) { return number1 + number2; } }
SimpleMath obj = new SimpleMath(); Console.WriteLine(obj.AddTwoNumbers(1, 2)); int result = obj.AddTwoNumbers(1, 2); Console.WriteLine(result);
در بخشهای بعدی بحث ما در مورد سایر اعضای کلاس و برخی جزییات پیرامون اعضای پیش گفته خواهد بود.
خطای زیر را مشاده کردم .
An assembly specified in the application dependencies manifest (MyWebApp.deps.json) was not found: package: 'Microsoft.ApplicationInsights.AspNetCore', version: '2.1.1' path: 'lib/netstandard1.6/Microsoft.ApplicationInsights.AspNetCore.dll' This assembly was expected to be in the local runtime store as the application was published using the following target manifest files:
<PropertyGroup> <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest> </PropertyGroup>
Domain Model یا Business Layer
پیاده سازی را از منطق تجاری یا Business Logic آغاز میکنیم. در روش کد نویسی Smart UI، منطق تجاری در Code Behind قرار میگرفت اما در روش لایه بندی، منطق تجاری و روابط بین دادهها در Domain Model طراحی و پیاده سازی میشوند. در مطالب بعدی راجع به Domain Model و الگوهای پیاده سازی آن بیشتر صحبت خواهم کرد اما بصورت خلاصه این لایه یک مدل مفهومی از سیستم میباشد که شامل تمامی موجودیتها و روابط بین آنهاست.
الگوی Domain Model جهت سازماندهی پیچیدگیهای موجود در منطق تجاری و روابط بین موجودیتها طراحی شده است.
شکل زیر مدلی را نشان میدهد که میخواهیم آن را پیاده سازی نماییم. کلاس Product موجودیتی برای ارائه محصولات یک فروشگاه میباشد. کلاس Price جهت تشخیص قیمت محصول، میزان سود و تخفیف محصول و همچنین استراتژیهای تخفیف با توجه به منطق تجاری سیستم میباشد. در این استراتژی همکاران تجاری از مشتریان عادی تفکیک شده اند.
Domain Model را در پروژه SoCPatterns.Layered.Model پیاده سازی میکنیم. بنابراین به این پروژه یک Interface به نام IDiscountStrategy را با کد زیر اضافه نمایید:
public interface IDiscountStrategy { decimal ApplyExtraDiscountsTo(decimal originalSalePrice); }
علت این نوع نامگذاری Interface فوق، انطباق آن با الگوی Strategy Design Pattern میباشد که در مطالب بعدی در مورد این الگو بیشتر صحبت خواهم کرد. استفاده از این الگو نیز به این دلیل بود که این الگو مختص الگوریتم هایی است که در زمان اجرا قابل انتخاب و تغییر خواهند بود.
توجه داشته باشید که معمولا نام Design Pattern انتخاب شده برای پیاده سازی کلاس را بصورت پسوند در انتهای نام کلاس ذکر میکنند تا با یک نگاه، برنامه نویس بتواند الگوی مورد نظر را تشخیص دهد و مجبور به بررسی کد نباشد. البته به دلیل تشابه برخی از الگوها، امکان تشخیص الگو، در پاره ای از موارد وجود ندارد و یا به سختی امکان پذیر است.
الگوی Strategy یک الگوریتم را قادر میسازد تا در داخل یک کلاس کپسوله شود و در زمان اجرا به منظور تغییر رفتار شی، بین رفتارهای مختلف سوئیچ شود.
حال باید دو کلاس به منظور پیاده سازی روال تخفیف ایجاد کنیم. ابتدا کلاسی با نام TradeDiscountStrategy را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class TradeDiscountStrategy : IDiscountStrategy { public decimal ApplyExtraDiscountsTo(decimal originalSalePrice) { return originalSalePrice * 0.95M; } }
سپس با توجه به الگوی Null Object کلاسی با نام NullDiscountStrategy را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class NullDiscountStrategy : IDiscountStrategy { public decimal ApplyExtraDiscountsTo(decimal originalSalePrice) { return originalSalePrice; } }
از الگوی Null Object زمانی استفاده میشود که نمیخواهید و یا در برخی مواقع نمیتوانید یک نمونه (Instance) معتبر را برای یک کلاس ایجاد نمایید و همچنین مایل نیستید که مقدار Null را برای یک نمونه از کلاس برگردانید. در مباحث بعدی با جزئیات بیشتری در مورد الگوها صحبت خواهم کرد.
با توجه به استراتژیهای تخفیف کلاس Price را ایجاد کنید. کلاسی با نام Price را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class Price { private IDiscountStrategy _discountStrategy = new NullDiscountStrategy(); private decimal _rrp; private decimal _sellingPrice; public Price(decimal rrp, decimal sellingPrice) { _rrp = rrp; _sellingPrice = sellingPrice; } public void SetDiscountStrategyTo(IDiscountStrategy discountStrategy) { _discountStrategy = discountStrategy; } public decimal SellingPrice { get { return _discountStrategy.ApplyExtraDiscountsTo(_sellingPrice); } } public decimal Rrp { get { return _rrp; } } public decimal Discount { get { if (Rrp > SellingPrice) return (Rrp - SellingPrice); else return 0; } } public decimal Savings { get{ if (Rrp > SellingPrice) return 1 - (SellingPrice / Rrp); else return 0; } } }
کلاس Price از نوعی Dependency Injection به نام Setter Injection در متد SetDiscountStrategyTo استفاده نموده است که استراتژی تخفیف را برای کالا مشخص مینماید. نوع دیگری از Dependency Injection با نام Constructor Injection وجود دارد که در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.
جهت تکمیل لایه Model، کلاس Product را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class Product { public int Id {get; set;} public string Name { get; set; } public Price Price { get; set; } }
موجودیتهای تجاری ایجاد شدند اما باید روشی اتخاذ نمایید تا لایه Model نسبت به منبع داده ای بصورت مستقل عمل نماید. به سرویسی نیاز دارید که به کلاینتها اجازه بدهد تا با لایه مدل در اتباط باشند و محصولات مورد نظر خود را با توجه به تخفیف اعمال شده برای رابط کاربری برگردانند. برای اینکه کلاینتها قادر باشند تا نوع تخفیف را مشخص نمایند، باید یک نوع شمارشی ایجاد کنید که به عنوان پارامتر ورودی متد سرویس استفاده شود. بنابراین نوع شمارشی CustomerType را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public enum CustomerType { Standard = 0, Trade = 1 }
برای اینکه تشخیص دهیم کدام یک از استراتژیهای تخفیف باید بر روی قیمت محصول اعمال گردد، نیاز داریم کلاسی را ایجاد کنیم تا با توجه به CustomerType تخفیف مورد نظر را اعمال نماید. کلاسی با نام DiscountFactory را با کد زیر ایجاد نمایید:
public static class DiscountFactory { public static IDiscountStrategy GetDiscountStrategyFor (CustomerType customerType) { switch (customerType) { case CustomerType.Trade: return new TradeDiscountStrategy(); default: return new NullDiscountStrategy(); } } }
در طراحی کلاس فوق از الگوی Factory استفاده شده است. این الگو یک کلاس را قادر میسازد تا با توجه به شرایط، یک شی معتبر را از یک کلاس ایجاد نماید. همانند الگوهای قبلی، در مورد این الگو نیز در مباحث بعدی بیشتر صحبت خواهم کرد.
لایهی سرویس با برقراری ارتباط با منبع داده ای، دادههای مورد نیاز خود را بر میگرداند. برای این منظور از الگوی Repository استفاده میکنیم. از آنجایی که لایه Model باید مستقل از منبع داده ای عمل کند و نیازی به شناسایی نوع منبع داده ای ندارد، جهت پیاده سازی الگوی Repository از Interface استفاده میشود. یک Interface به نام IProductRepository را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public interface IProductRepository { IList<Product> FindAll(); }
الگوی Repository به عنوان یک مجموعهی در حافظه (In-Memory Collection) یا انباره ای از موجودیتهای تجاری عمل میکند که نسبت به زیر بنای ساختاری منبع داده ای کاملا مستقل میباشد.
کلاس سرویس باید بتواند استراتژی تخفیف را بر روی مجموعه ای از محصولات اعمال نماید. برای این منظور باید یک Collection سفارشی ایجاد نماییم. اما من ترجیح میدهم از Extension Methods برای اعمال تخفیف بر روی محصولات استفاده کنم. بنابراین کلاسی به نام ProductListExtensionMethods را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public static class ProductListExtensionMethods { public static void Apply(this IList<Product> products, IDiscountStrategy discountStrategy) { foreach (Product p in products) { p.Price.SetDiscountStrategyTo(discountStrategy); } } }
الگوی Separated Interface تضمین میکند که کلاینت از پیاده سازی واقعی کاملا نامطلع میباشد و میتواند برنامه نویس را به سمت Abstraction و Dependency Inversion به جای پیاده سازی واقعی سوق دهد.
حال باید کلاس Service را ایجاد کنیم تا از طریق این کلاس، کلاینت با لایه Model در ارتباط باشد. کلاسی به نام ProductService را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class ProductService { private IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public IList<Product> GetAllProductsFor(CustomerType customerType) { IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStrategyFor(customerType); IList<Product> products = _productRepository.FindAll(); products.Apply(discountStrategy); return products; } }
در اینجا کدنویسی منطق تجاری در Domain Model به پایان رسیده است. همانطور که گفته شد، لایهی Business یا همان Domain Model به هیچ منبع داده ای خاصی وابسته نیست و به جای پیاده سازی کدهای منبع داده ای، از Interfaceها به منظور برقراری ارتباط با پایگاه داده استفاده شده است. پیاده سازی کدهای منبع داده ای را به لایهی Repository واگذار نمودیم که در بخشهای بعدی نحوه پیاده سازی آن را مشاهده خواهید کرد. این امر موجب میشود تا لایه Model درگیر پیچیدگیها و کد نویسیهای منبع داده ای نشود و بتواند به صورت مستقل و فارغ از بخشهای مختلف برنامه تست شود. لایه بعدی که میخواهیم کد نویسی آن را آغاز کنیم، لایهی Service میباشد.
در کد نویسیهای فوق از الگوهای طراحی (Design Patterns) متعددی استفاده شده است که به صورت مختصر در مورد آنها صحبت کردم. اصلا جای نگرانی نیست، چون در مباحث بعدی به صورت مفصل در مورد آنها صحبت خواهم کرد. در ضمن، ممکن است روال یادگیری و آموزش بسیار نامفهوم باشد که برای فهم بیشتر موضوع، باید کدها را بصورت کامل تست نموده و مثالهایی را پیاده سازی نمایید.
اولین نکته مربوط به تاریخ هر مدخل (entry) میشود. این تاریخ نباید شمسی باشد! این تاریخ باید حتما استاندارد باشد. عموما یکی از دو استاندارد زیر باید مورد استفاده قرار گیرد:
RFC #822
http://www.ietf.org/rfc/rfc0822.txt
Standard for ARPA Internet Text Messages (Date and Time Specification)
RFC #3339
http://www.ietf.org/rfc/rfc3339.txt
Date and Time on the Internet (Timestamps)
برای مثال در دات نت برای تولید این فرمت استاندارد میتوان به صورت زیر عمل کرد:
DateTime.Now.ToUniversalTime().ToString("r")
چند مثال از این دست: (سورس صفحه را در مرورگر مطالعه نمائید)
http://www.faradade.com/Xml/RSS.xml
و یا
http://www.ayande.ir/atom.xml
و یا
http://www.tci-sk.ir/Rss.aspx
(ایشان بهتر است علاوه بر این مورد، از XmlTextWriter استفاده کنند و خروجی را به صورت یک فایل xml و نه html در مرورگر Flush کنند)
و یا بدتر از این بعضی از سایتها آموزشهای غلطی را هم ارائه میدهند:
http://www.faradade.com/Article.aspx?code=a726ae6a-f8e1-4b29-88b4-8e7a04e6d06d
به قسمت pubDate دقت کنید.
مطابق معمول این آموزش الان در 200 سایت کپی و پیست شده! عنوان آموزش را در گوگل جستجو کنید!
این کد آموزش داده شده یک ایراد دیگر هم دارد. آیا الزامی دارد که حتما قسمت con.Close به همین ترتیب نوشته شده اجرا شود؟ اگر این بین خطایی رخ دهد تکلیف این کانکشن باز و سایر موارد چه خواهد شد؟ کلا استفاده از try و finally و یا استفاده از using را برای چه هدفی اختراع کردهاند؟
و یا بعضی از سایتها این مورد را رعایت میکنند اما به صورت نصفه و نیمه. برای مثال: (تاریخ ارائه شده کامل نیست. بنابراین استاندارد تلقی نخواهد شد)
http://www.srco.ir/Articles/RSSArticles.xml
برای آزمایش میزان استاندارد بودن خروجی فید خود میتوان از سرویس زیر استفاده کرد:
http://validator.w3.org/feed/
مطلب دیگر ایراد نیست بلکه نکتهای است که حداقل از IE7 به بعد رعایت میشود:
لطفا زبان فید را مشخص کنید! بله، اگر این مورد را مشخص کنید، از IE7 به بعد فید فارسی به صورت خودکار از راست به چپ نمایش داده میشود و این امر سبب سهولت خواندن مطالب فارسی سایت شما خواهد شد.
مشاهده اصل مطلب که توسط یکی از اعضای تیم مربوطه مایکروسافت نوشته شده:
مشاهده
اصلاحیه برای RSS فارسی:
<language>fa-IR</language>
<feed xml:lang="fa">
http://www.codeplex.com/Argotic
با تشکر از همکاری شما!
چرا TypeScript؟
- TypeScript زبان توصیه شدهی توسعهی برنامههای AngularJS 2 است و همچنین با سایر کتابخانههای معروف جاوا اسکریپتی مانند ReactJS و jQuery نیز سازگاری دارد. بنابراین اگر قصد دارید به AngularJS 2 مهاجرت کنید، اکنون فرصت خوبی است تا زبان TypeScript را نیز بیاموزید. همچنین WinJS نیز با TypeScript نوشته شدهاست.
- superset زبان JavaScript بودن به این معنا است که تمام کدهای جاوا اسکریپتی موجود، به عنوان کد معتبر TypeScript نیز شناخته میشوند و همین مساله مهاجرت به آنرا سادهتر میکند. زبانهای دیگری مانند Dart و یا CoffeeScript ، نسبت به JavaScript بسیار متفاوت به نظر میرسند؛ اما Syntax زبان TypeScript شباهت بسیار زیادی به جاوا اسکریپت و خصوصا ES 6 دارد. در اینجا تنها کافی است پسوند فایلهای js را به ts تغییر دهید و از آنها به عنوان کدهای معتبر TypeScript استفاده کنید.
- strong typing و معرفی نوعها، کدهای نهایی نوشته شده را امنتر میکنند. به این ترتیب کامپایلر، پیش از اینکه کدهای شما در زمان اجرا به خطا بر بخورند، در زمان کامپایل، مشکلات موجود را گوشزد میکند. همچنین وجود نوعها، سرعت توسعه را با بهبود ابزارهای مرتبط با برنامه نویسی، افزایش میدهند؛ از این جهت که مفهوم مهمی مانند Intellisense، با وجود نوعها، پیشنهادهای بهتر و دقیقتری را ارائه میدهد. همچنین ابزارهای Refactoring نیز در صورت وجود نوعها بهتر و دقیقتر عمل میکنند. این موارد مهمترین دلایل طراحی TypeScript جهت توسعه و نگهداری برنامههای بزرگ نوشته شدهی با JavaScript هستند.
- Syntax زبان TypeScript به شدت الهام گرفته شده از زبان سیشارپ است. به همین جهت اگر با این زبان آشنایی دارید، درک مفاهیم TypeScript برایتان بسیار ساده خواهد بود.
- بهترین قسمت TypeScript، کامپایل شدن آن به ES 5 است (به این عملیات Transpile هم میگویند). در زبان TypeScript به تمام امکانات پیشرفتهی ES 6 مانند کلاسها و ماژولها دسترسی دارید، اما کد نهایی را که تولید میکند، میتواند ES 5 ایی باشد که هم اکنون تمام مرورگرهای عمده آنرا پشتیبانی میکنند. با تنظیمات کامپایلر TypeScript، امکان تولید کدهای ES 3 تا ES 5 و همچنین ES 6 نیز وجود دارد. نمونهی آنلاین این ترجمه را در TypeScript playground میتوانید مشاهده کنید.
- TypeScript چندسکویی است. امکانات و کامپایلر این زبان، برای ویندوز، مک و لینوکس طراحی شدهاند.
- TypeScript سورس باز است. طراحان اصلی آن، همان طراحان زبان سیشارپ در مایکروسافت هستند و هم اکنون این زبان به صورت سورس باز توسط این شرکت توسعه داده شده و در GitHub نگهداری میشود.
آماده سازی محیطهای کار با TypeScript
برای کار با TypeScript، یک ادیتور متنی ساده، به همراه کامپایلر آن کفایت میکند. اما همانطور که عنوان شد، یکی از مهمترین دلایل وجودی TypeScript، بهبود ابزارهای برنامه نویسی مرتبط با JavaScript است و اگر قرار باشد صرفا از یک ادیتور متنی ساده استفاده شود، فلسفهی وجودی آن زیر سؤال میرود.
نصب TypeScript در ویژوال استودیو
در نگارشهای جدید ویژوال استودیو، از VS 2013 Update 2 به بعد، قسمت ویژهی TypeScript نیز قابل مشاهدهاست. البته این قسمت با به روز رسانیهای TypeScript، نیاز به به روز رسانی دارد. به همین جهت به سایت رسمی آن مراجعه کرده و بستههای جدید مخصوص VS 2013 و یا 2015 آنرا دریافت و نصب کنید.
همچنین افزونهی Web Essentials نیز امکانات بیشتری را جهت کار با TypeScript به همراه دارد و امکان مشاهدهی خروجی جاوا اسکریپت تولیدی را در حین کار با فایل TypeScript فعلی میسر میکند. در سمت چپ صفحه TypeScript را خواهید نوشت و در سمت راست، خروجی JavaScript نهایی را بلافاصله مشاهده میکنید.
تصویر فوق مربوط به VS 2015 است. همچنین گزینهی افزودن یک فایل و آیتم جدید نیز امکان افزودن فایلهای TS را به همراه دارد.
نصب و تنظیم TypeScript در ویژوال استودیو کد
ویژوال استودیو کد، نگارش رایگان، سورس باز و چندسکویی ویژوال استودیو است که بر روی ویندوز، مک و لینوکس قابل اجرا است. ویژوال استودیو کد نیز به همراه پشتیبانی بسیار خوبی از TypeScript است، تا حدی که تمام ارائههای معرفی Anugular 2 توسط تیم مربوطهی آن از گوگل، توسط ویژوال استودیو کد و یکپارچگی آن با TypeScript انجام شدند.
ویژوال استودیو کد بر مبنای فولدرها کار میکند و با گشودن یک پوشه در آن (با کلیک بر روی دکمهی open folder آن)، امکان کار کردن با آن پوشه و فایلهای موجود در آن را خواهیم یافت.
نکتهی مهم اینجا است که پس از نصب VS Code، برای فایلهای با پسوند ts بلافاصله Intellisense مرتبط نیز مهیا است و نیاز به هیچگونه تنظیم اضافهتری ندارد. همچنین قابلیتهای type safety این زبان نیز در این ادیتور به نحو واضحی مشخص هستند:
در ادامه ابتدا یک پوشهی جدید خالی را ایجاد کنید و سپس این پوشه را در VS Code باز نمائید (از طریق منوی فایل، گزینهی گشودن پوشه). سپس ماوس را بر روی نام این پوشه حرکت دهید:
همانطور که مشاهده میکنید، دکمهی new file ظاهر میشود. در اینجا میتوانید فایل جدیدی را به نام test.ts اضافه کنید.
در ادامه با فشردن دکمههای ctrl+shift+p، امکان انتخاب یک task runner را جهت کامپایل فایلهای ts خواهیم داشت:
در اینجا ابتدا عبارت task< را وارد کنید و سپس از منوی باز شده، گزینهی rub build task را انتخاب کنید:
پس از آن، در بالای صفحه مشاهده خواهید کرد که عنوان شده: «هنوز هیچ task runner ایی برای اینکار تنظیم نشدهاست»
برای این منظور بر روی دکمهی configure task runner تصویر فوق که با رنگ آبی مشخص شدهاست، کلیک کنید. به این ترتیب یک فایل جدید به نام task.json ایجاد میشود که در پوشهای به نام vscode. در ریشهی پروژه (یا همان پوشهی جاری) قرار میگیرد:
فایل task.json دارای تعاریفی است که کامپایلر TypeScript یا همان tsc را فعال میکند:
{ "version": "0.1.0", // The command is tsc. Assumes that tsc has been installed using npm install -g typescript "command": "tsc", // The command is a shell script "isShellCommand": true, // Show the output window only if unrecognized errors occur. "showOutput": "silent", // args is the HelloWorld program to compile. "args": ["HelloWorld.ts"], // use the standard tsc problem matcher to find compile problems // in the output. "problemMatcher": "$tsc" }
در اینجا قسمتی که نیاز به تنظیم دارد، خاصیت args است. مقادیر آن، پارامترهایی هستند که به کامپایلر typescript ارسال میشوند. برای نمونه آنرا به صورت ذیل تغییر دهید:
"args": [ "--target", "ES5", "--outdir", "js", "--sourceMap", "--watch", "test.ts" ],
برای اجرای کامپایلر، ابتدا از منوی view گزینهی toggle output را انتخاب کنید تا بتوان خروجی نهایی کامپایلر را مشاهده کرد. سپس گزینهی view->command pallet و اجرا tasks< را انتخاب کنید. در ادامه همانند مرحلهی قبل، یعنی گزینهی run build task را اجرا کنید (که خلاصهی این عملیات ctrl+shift+B است).
به این ترتیب پوشهی js که در خاصیت args مشخص کردیم، تولید میشود:
البته این خطا هم در قسمت output نمایش داده میشود:
error TS5023: Unknown option 'watch' Use the '--help' flag to see options.
علت اینجا است که در تنظیمات فوق، خاصیت command به tsc تنظیم شدهاست و همانطور که در کامنت آن عنوان شدهاست، کامپایلر typescript را از طریق دستور npm install -g typescript دریافت میکند و نیازی به ذکر مسیر آن در اینجا نیست. بنابراین لازم است تا با npm و نصب typescript از طریق آن آشنا شد و به این ترتیب کامپایلر آنرا به روز کرد تا دستور watch را شناسایی کند.
نصب TypeScript از طریق npm
همانطور که عنوان شد، TypeScript چندسکویی است و این مورد را از طریق npm یا NodeJS package manager انجام میدهد. برای این منظور به آدرس https://nodejs.org/en مراجعه کرده و فایل نصاب آنرا مخصوص سیستم عامل خود دریافت و سپس نصب کنید. Node.js یک runtime سمت سرور اجرای برنامههای جاوا اسکریپتی است. از آنجائیکه TypeScript در نهایت به JavaScript تبدیل میشود، استفاده از node.js انتخاب مناسبی جهت اجرا و توزیع آن در تمام سیستم عاملها بودهاست.
پس از نصب node.js، از package manager آن که npm نام دارد، جهت نصب TypeScript استفاده میشود. چون node.js به Path و مسیرهای اصلی ویندوز اضافه میشود، تنها کافی است دستور npm install -g typescript را در خط فرمان صادر کنید. در اینجا سوئیچ g به معنای global و دسترسی عمومی است.
همانطور که در این تصویر مشخص است، پس از صدور دستور نصب TypeScript، نگارش 1.8.9 آن نصب شدهاست. اما زمانیکه کامپایلر tsc را با پارامتر version اجرا میکنیم، شماره نگارش قدیمی 1.0.3.0 را نمایش میدهد. برای رفع این مشکل به مسیر C:\Program Files (x86)\Microsoft SDKs\TypeScript مراجعه کرده و پوشهی 1.0 را به 1.0-old تغییر نام دهید.
اکنون اگر مجددا بررسی کنیم، نگارش صحیح قابل مشاهده است:
پس از این تغییرات اگر مجددا به VS Code باز گردیم و ctrl+shift+B را صادر کنیم (جهت اجرای مجدد task runner و اجرای tsc تنظیم شده) ، پیام ذیل مشاهده میشود:
15:33:52 - Compilation complete. Watching for file changes.
در اینجا چون پارامتر watch فعال شدهاست، هر تغییری که در فایل ts داده شود، بلافاصله کامپایل شده و در فایل js منعکس خواهد شد.
تنظیم VS Code جهت دیباگ کدهای TypeScript
در نوار ابزار کنار صفحهی VS Code، بر روی دکمهی دیباگ کلیک کنید:
سپس بر روی دکمهی چرخدندهی موجود که کار انجام تنظیمات را توسط آن میتوان ادامه داد، کلیک کنید. بلافاصله منویی ظاهر میشود که درخواست انتخاب محیط دیباگ را دارد:
در اینجا node.js را انتخاب کنید. با اینکار فایل جدیدی دیگری به نام launch.json به پوشهی vscode. اضافه میشود. اگر به این فایل دقت کنید دو خاصیت name به نامهای Launch و Attach در آن موجود هستند. این نامها در یک دراپ داون، در کنار دکمهی start دیباگ نیز ظاهر میشوند:
- در فایل launch.json، باید خاصیت "program": "${workspaceRoot}/app.js" را ویرایش کرد و app.js آنرا به test.ts مثال جاری تغییر داد.
- سپس خاصیت "sourceMaps" آن نیز باید تغییر کرده و جهت استفادهی از source mapهای تولیدی به true تنظیم شود.
- در آخر باید مسیر پوشهی خروجی js را نیز تنظیم کرد: "outDir": "${workspaceRoot}/js"
همچنین باید دقت داشت چون externalConsole به false تنظیم شدهاست، خروجی این کنسول به output ویژوال استودیوکد منتقل میشود.
اکنون اگر بر روی دکمهی سبز رنگ start کلیک کنید (دکمهی F5)، امکان دیباگ سطر به سطر کد TypeScript را خواهید یافت:
فایلهای نهایی json یاد شدهی در متن را از اینجا میتوانید دریافت کنید:
VSCodeTypeScript.zip
آشنایی با قابلیت های SQL Server 2016
همانطور که اطلاع دارید نسخه آزمایشی SQL Server 2016 قرار از تابستان امسال (2015) در دسترس باشد. قابلیتهای جدیدی به این محصول اضافه شده است. تعدادی از آنها عبارتند از
1- امکان استفاده از Clustered Column Store Index در جداول Memory Optmized
2- َAlways Encrypted
3- پشتیبانی از JSON
4- پشتیبانی از زبان R در SQL Server
و ...