ثبت پیاده سازیهای متعدد یک اینترفیس در سیستم تزریق وابستگیهای NET Core.
داشتن چندین پیاده سازی از یک اینترفیس، شاید یکی از اهداف اصلی وجود آنها باشد. برای نمونه قرار داد ارسال پیامی را در برنامه، مانند IMessageService به صورت زیر طراحی و سپس بر اساس آن، دو پیاده سازی ارسال پیامها را از طریق ایمیل و SMS، اضافه میکنیم:
namespace CoreIocServices { public interface IMessageService { void Send(string message); } public class EmailService : IMessageService { public void Send(string message) { // ... } } public class SmsService : IMessageService { public void Send(string message) { //todo: ... } } }
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMessageService, EmailService>(); services.AddTransient<IMessageService, SmsService>(); }
using CoreIocServices; using Microsoft.AspNetCore.Mvc; namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly IMessageService _emailService; private readonly IMessageService _smsService; public MessagesController(IMessageService emailService, IMessageService smsService) { _emailService = emailService; _smsService = smsService; } } }
همانطور که مشاهده میکنید، هر دو پارامتر، با وهلهای از آخرین سرویس معرفی شدهی از نوع IMessageService مقدار دهی شدهاند؛ یعنی SmsService در اینجا. در ادامه روشهای مختلفی را برای رفع این مشکل بررسی میکنیم.
روش اول: سیستم تزریق وابستگیهای NET Core. از سازندههای IEnumerable پشتیبانی میکند
برای مدیریت دریافت سرویسهایی که از یک اینترفیس مشتق شدهاند، خود NET Core. بدون نیاز به تنظیمات اضافهتری، روش تزریق IEnumerableها را در سازندههای کلاسها پشتیبانی میکند:
using System.Collections.Generic; using CoreIocServices; using Microsoft.AspNetCore.Mvc; namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly IEnumerable<IMessageService> _messageServices; public MessagesController(IEnumerable<IMessageService> messageServices) { _messageServices = messageServices; }
به این ترتیب لیست وهلههای تمام سرویسهایی از نوع IMessageService در اختیار ما قرار گرفتهاست و برای اهدافی مانند پیاده سازی الگوهایی در ردهی chain of responsibility و یا الگوی استراتژی، مفید است. در این حالت اگر نیاز به سرویس از نوع خاصی وجود داشت، میتوان از روش زیر استفاده کرد:
var emailService = _messageServices.OfType<EmailService>().First();
var messageServices = serviceProvider.GetServices<IMessageService>();
روش دوم: دریافت شرطی وهلههای مورد نیاز با «دخالت در مراحل وهله سازی اشیاء توسط IoC Container»
روش «دخالت در مراحل وهله سازی اشیاء توسط IoC Container» را در قسمت قبل بررسی کردیم. یکی دیگر از مثالهایی را که در این مورد میتوان ارائه داد، شرطی کردن بازگشت وهلهها است که برای سناریوی مطلب جاری بسیار مفید است:
الف) برای شرطی کردن بازگشت وهلهها، بهتر است این شرط را بجای رشتهها و یا اعدادی خاص، بر اساس یک enum مشخص انجام دهیم:
namespace CoreIocServices { public enum MessageServiceType { EmailService, SmsService }
ب) سپس هر دو سرویس مشتق شدهی از یک اینترفیس را به صورت معمولی ثبت میکنیم:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<EmailService>(); services.AddTransient<SmsService>();
ج) اکنون میتوان مرحلهی شرطی کردن بازگشت این وهلهها را به صورت زیر، با دخالت در مراحل وهله سازی اشیاء، پیاده سازی کرد:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<EmailService>(); services.AddTransient<SmsService>(); services.AddTransient<Func<MessageServiceType, IMessageService>>(serviceProvider => key => { switch (key) { case MessageServiceType.EmailService: return serviceProvider.GetRequiredService<EmailService>(); case MessageServiceType.SmsService: return serviceProvider.GetRequiredService<SmsService>(); default: throw new NotImplementedException($"Service of type {key} is not implemented."); } });
د) مرحلهی آخر کار، روش استفادهی از این امضایء ویژهی Lazy load شدهاست:
namespace CoreIocSample02.Controllers { public class MessagesController : Controller { private readonly Func<MessageServiceType, IMessageService> _messageServiceResolver; public MessagesController(Func<MessageServiceType, IMessageService> messageServiceResolver) { _messageServiceResolver = messageServiceResolver; }
public IActionResult Index() { var emailService = _messageServiceResolver(MessageServiceType.EmailService); //use emailService ... return View(); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: CoreDependencyInjectionSamples-07.zip
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return FirstName + " " + LastName; } } }
public string FullName { get { return FirstName + " " + LastName; } }
{ get { return your expression; } }
=> your expression;
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName => FirstName + " " + LastName; }
public string this[int number] { get { if (number >= 0 && number < _values.Length) { return _values[number]; } return "Error"; } }
public string this[int number] => (number >= 0 && number < _values.Length) ? _values[number] : "Error";
public override string ToString() { return FirstName; }
public override string ToString() => FirstName;
public int DoubleTheValue(int someValue) => someValue * 2;
- یکی از محدودیتهای استفاده از expression body داشتن چندین خط دستور برای بدنه متدهایمان است. در اینحالت باید از روش سابق (statement body) استفاده نمائید.
- یکی دیگر از محدودیتها عدم امکان استفاده از if, else, switch است. به عنوان مثال نمیتوان کد زیر را با داشتن if و else به صورت expression body نوشت:
public override string ToString() { if (MiddleName != null) { return FirstName + " " + MiddleName + " " + LastName; } else { return FirstName + " " + LastName; } }
public override string ToString() => (MiddleName != null) ? FirstName + " " + MiddleName + " " + LastName : FirstName + " " + LastName;
- همچنین نمیتوان از for, foreach, while, do در expression body استفاده کرد، به جای آن میتوان از عبارتهای LINQ برای بدنه تابع استفاده کرد. به عنوان مثال متد زیر:
public IEnumerable<int> SmallNumbers() { for (int i = 0; i < 10; i++) yield return i; }
public IEnumerable<int> SmallNumbers() => from n in Enumerable.Range(0, 10) select n;
public IEnumerable<int> SmallNumbers() => Enumerable.Range(0, 10).Select(n => n);
- همانطور که عنوان شد، استفاده از expression body در قسمت پراپرتیها تنها محدود به پراپرتیهای get-only (فقط خواندنی) میباشد.
- استفاده از این قابلیت برای متدهای سازنده
- استفاده در رخدادها
- استفاده در finalizers
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Person() { this.FirstName = "Sirwan"; this.LastName = "Afifi"; } }
public string FirstName { get; set; } = "Sirwan"; public string LastName { get; set; } = "Afifi";
برای پراپرتیها:
یک: به جای بازگرداندن شماره خطا، از استثناءها استفاده کنید. نمونه زیر را ببینید:
public int ReturnErrorCodes(int n1) { if(n1==0) return -1; if(n1<0) return -2; if(n1>_max) return -3; return n1; }
public int ReturnErrorCodes(int n1) { if(n1==0) throw new ZeroException(); if(n1<0) throw new MinException(); if(n1>_max) throw new MaxException(); return n1; }
امروزه دیگر برگرداندن شمارههای خطا منسوخ شده و جای آن از کلاسهای استثناء استفاده میکنند و دریافت آن را از طریق Catch کنترل میکنند. از مزایای آن میتوان به این اشاره کرد که کد اصلی، عاری از دستورات شرطی برای چک کردن میشود و کد، حالت مختصرتری به خود میگیرد. در یک نگاه کد نشان میدهد که چه اتفاقی میافتد و چه خطاهایی داریم. استثناءها بر خلاف شماره کدها، یک نوع ساده و ابتدایی نیستند. بلکه کلاس هستند و میتوانند به ما قابلیت توسعه یک کلاس خطا را بدهند. متدی یا چیزی را اضافه کنیم تا قابلیتهای آن بالاتر برود.
دو. یک شیء کامل را بازگردانید. نمونه کد زیر را ببینید:
Public Void Draw() { var x=_pointDetector.X; var y=_pointerDetector.Y; _rectangle.Draw(x,y); }
public Void Draw() { var point = _pointDetector.Point; _rectangle.Draw(point); }
سه. شروط یکسان را تابع نویسی کنید. گاهی از اوقات مانند کد زیر پیش میآید که خروجی چندین شرط شما، خروجی یکسانی دارند. جهت این کار میتوانید تمام این شرطها را به یک تابع یا متد دیگر منتقل کنید:
public void Conditions(){ if(a==0) return 0; if(b==0) return 0; if(c==0) return 0; if(d==0) return 0; }
public void Conditions() { if(allConditions()) return 0; }
چهار. چند خط کد شرطی را در یک شرط ننویسید و آنها را به متغیرهای جداگانه انتساب دهید. نمونه زیر را ببینید:
public void renderBanner() { if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0 ) { // do something } }
public void renderBanner() { bool isMacOs = platform.toUpperCase().indexOf("MAC") > -1; bool isIE = browser.toUpperCase().indexOf("IE") > -1; bool wasResized = resize > 0; if (isMacOs && isIE && wasInitialized() && wasResized) { // do something } }
پنج. معرفی اکستنشن محلی: گاهی اوقات از کلاسهایی استفاده میکنید که شامل متد یا خاصیتی که احتیاج دارید نیستند و دسترسی به سورس آن کلاس هم امکان پذیر نیست یا هزینه سنگینی دارد. در این صورت میتوانید از یک زیر کلاس یا Wrapper Class استفاده کنید.
به عنوان مثال متد NextDay جایگاه آن در DateTime است. برای افزودن این خاصیت دو راه دارید که یک زیر کلاس از DateTime ایجاد کنید و متدی را به آن اضافه کنید یا اینکه کلاس DateTime را در یک کلاس دیگر بپیچانید (محصور کنید).
public class Globalization:DateTime { public int NextDay() { } }
public class Globalization { public DateTime DT{get;set;} public int NextDay() { } }
پی نوشت: اینکه کلاس dateTime قابل ارث بری است یا خیر اهمیتی ندارد. تنها به عنوان مثال ذکر شده است.
مزیت این روش این است که شما در طول برنامه از یک کلاس یکسان استفاده میکنید و با همین یک کلاس به همه موارد دسترسی دارید و باعث وجود کد یکتایی میشوید و به جای اینکه مرتبا از کلاسهای مختلف استفاده کنید، از یک نام مشخص استفاده میکنید و خوانایی کد را در کل برنامه بالا میبرید.
بررسی برخی تغییرات در Angular 8
- کامپایل سریعتری را فراهم میکند (انتشار در انگیولار 9)
- بررسی type در قالبها، خیلی بیشتر بهبود یافته است؛ بهگونهای که میتوان خطاهای بیشتری را در زمان build گرفت که باعث میشود کاربران در زمان runtime به آن خطاها برخورد نکنند (انتشار در انگیولار 9).
- bundleهای با سایز کوچکتری در مقایسه با سایز bundleهای کامپایل شدهی جاری
- کدهای تولید شده توسط Angular compiler، بسیار آسانتر، برای خواندن و درک انسان است.
- آخرین و مهمترین ویژگی مورد علاقه من این است که میتوان قالبها (templates) را debug کرد. من یقین دارم که این ویژگی توسط تعداد زیادی از توسعه دهندگان مورد توجه قرار خواهد گرفت .
ng serve --aot
"projects": { "app-name": { "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", }, "test": { "builder": "@angular-devkit/build-angular:karma", }, "lint": { "builder": "@angular-devkit/build-angular:tslint", }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", } } } }
OpenCVSharp #14
BLOB یا Binary Large OBject به معنای گروهی از نقاط به هم پیوستهی در یک تصویر باینری هستند. Large در اینجا به این معنا است که اشیایی با اندازههایی مشخص، مدنظر هستند و اشیاء کوچک، به عنوان نویز درنظر گرفته خواهند شد و پردازش نمیشوند.
برای نمونه در تصویر ذیل، تصویر سمت چپ، تصویر اصلی است و تصویر سمت راست، نمونهی باینری سیاه و سفید آن. سپس عملیات تشخیص Blobs بر روی تصویر اصلی انجام شده و نتیجهی نهایی که مشخص کنندهی اشیاء تشخیص داده شدهاست، با دوایری قرمز در تصویر سمت راست، مشخص هستند.
معرفی کلاس SimpleBlobDetector
در کدهای ذیل، نحوهی کار با کلاس SimpleBlobDetector را مشاهده میکنید. این کلاس کار تشخیص Blobs را در تصویر اصلی انجام میدهد:
var srcImage = new Mat(@"..\..\Images\cvlbl.png"); Cv2.ImShow("Source", srcImage); Cv2.WaitKey(1); // do events var binaryImage = new Mat(srcImage.Size(), MatType.CV_8UC1); Cv2.CvtColor(srcImage, binaryImage, ColorConversion.BgrToGray); Cv2.Threshold(binaryImage, binaryImage, thresh: 100, maxval: 255, type: ThresholdType.Binary); var detectorParams = new SimpleBlobDetector.Params { //MinDistBetweenBlobs = 10, // 10 pixels between blobs //MinRepeatability = 1, //MinThreshold = 100, //MaxThreshold = 255, //ThresholdStep = 5, FilterByArea = false, //FilterByArea = true, //MinArea = 0.001f, // 10 pixels squared //MaxArea = 500, FilterByCircularity = false, //FilterByCircularity = true, //MinCircularity = 0.001f, FilterByConvexity = false, //FilterByConvexity = true, //MinConvexity = 0.001f, //MaxConvexity = 10, FilterByInertia = false, //FilterByInertia = true, //MinInertiaRatio = 0.001f, FilterByColor = false //FilterByColor = true, //BlobColor = 255 // to extract light blobs }; var simpleBlobDetector = new SimpleBlobDetector(detectorParams); var keyPoints = simpleBlobDetector.Detect(binaryImage); Console.WriteLine("keyPoints: {0}", keyPoints.Length); foreach (var keyPoint in keyPoints) { Console.WriteLine("X: {0}, Y: {1}", keyPoint.Pt.X, keyPoint.Pt.Y); } var imageWithKeyPoints = new Mat(); Cv2.DrawKeypoints( image: binaryImage, keypoints: keyPoints, outImage: imageWithKeyPoints, color: Scalar.FromRgb(255, 0, 0), flags: DrawMatchesFlags.DrawRichKeypoints); Cv2.ImShow("Key Points", imageWithKeyPoints); Cv2.WaitKey(1); // do events Cv2.WaitKey(0); Cv2.DestroyAllWindows(); srcImage.Dispose(); imageWithKeyPoints.Dispose();
در اینجا ابتدا تصویر منبع به صورتی متداول باگذاری شدهاست. سپس توسط متد CvtColor به نمونهای سیاه و سفید و به کمک متد Threshold به یک تصویر باینری تبدیل شدهاست.
اگر به تصویر ابتدای بحث دقت کنید، اشیاء موجود در منبع مفروض، رنگهای متفاوتی دارند؛ اما در تصویر نهایی تولید شده، تمام آنها تبدیل به اشیایی سفید رنگ شدهاند. این کار را متد Cv2.Threshold انجام دادهاست، تا کلاس SimpleBlobDetector قابلیت تشخیص بهتری را پیدا کند. تشخیص اشیایی یک دست، بسیار سادهتر هستند از تشخیص اشیایی با رنگها و شدت نور متفاوت.
اگر بخواهیم الگوریتم Threshold را بیان کنیم به pseudo code زیر خواهیم رسید:
if src(x,y) > thresh dst(x,y) = maxValue else dst(x,y) = 0
سپس پارامتر اصلی سازندهی کلاس SimpleBlobDetector مقدار دهی شدهاست. این تنظیمات در تشخیص اشیاء موجود در تصویر بسیار مهم هستند. برای درک بهتر واژههایی مانند Circularity، Convexity، Inertia و امثال آن، به تصویر ذیل دقت کنید:
در اینجا میتوان حداقل و حداکثر تقعر و تحدب اشیاء و یا حتی حداقل و حداکثر اندازهی اشیاء مدنظر را مشخص کرد.
اگر سازندهی کلاس SimpleBlobDetector به نال تنظیم شود، از مقادیر پیش فرض آن استفاده خواهند شد که در اینجا به معنای تشخیص اشیاء کدر دایرهای شکل هستند. به همین جهت در تنظیمات فوق FilterByColor به false تنظیم شدهاست تا اشکال سفید رنگ تصویر اصلی را بتوان تشخیص داد.
در ادامه متد simpleBlobDetector.Detect بر روی تصویر باینری بهبود داده شده، فراخوانی گشتهاست. خروجی آن نقاط کلیدی اشیاء یافت شدهاست. این نقاط را میتوان توسط متد DrawKeypoints ترسیم کرد که حاصل آن دوایر قرمز رنگ موجود در مرکز اشیاء یافت شدهی در تصویر سمت راست هستند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
کار با Kendo UI DataSource
[DisplayName("تاریخ درج")] public DateTime CreateDate { get; set; } [DisplayName("تاریخ انتشار")] public DateTime PublishDate { get; set; }
[DisplayName("نوع مطلب")] public string BlogType { get; set; }
<script type="text/javascript"> $(function () { var blogDataSource = new kendo.data.DataSource({ transport: { read: { url: "@Url.Action("GetLastBlogs", "Admin")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, parameterMap: function (options) { return kendo.stringify(options); } }, schema: { data: "data", total: "total", model: { fields: { "id": { type: "number" }, //تعیین نوع فیلد برای جستجوی پویا مهم است "title": { type: "string" }, "content": { type: "string" }, "imagepath": { type: "string" }, "attachmentid": { type: "number" }, "createdate": { type: "string" }, "publishdate": { type: "date" }, "blogtype": { type: "string" }, "lastmodified": { type: "date" }, "visitcount": { type: "number" }, "isarchived": { type: "boolean" }, "ispublished": { type: "boolean" }, "isdeleted": { type: "boolean" }, "tags": { type: "string" } } } }, error: function (e) { alert(e.errorThrown); }, pageSize: 10, sort: { field: "id", dir: "desc" }, serverPaging: true, serverFiltering: true, serverSorting: true }); $("#report-grid").kendoGrid({ dataSource: blogDataSource, autoBind: true, scrollable: false, pageable: true, sortable: true, filterable: true, reorderable: true, columnMenu: true, columns: [ { field: "id", title: "شماره", width: "130px" }, { field: "title", title: "عنوان مطلب" }, { field: "createdate", title: "تاریخ درج" }, { field: "publishdate", title: "تاریخ انتشار" }, { field: "content", title: "خلاصه مطلب" }, { field: "blogtype", title: "نوع" }, { field: "lastmodified", title: "آخرین ویرایش" }, { field: "visitcount", title: "تعداد بازدید" }, { field: "isarchived", title: "آرشیو شده", template: '<input type="checkbox" #= isarchived ? checked="checked" : "" # disabled="disabled" ></input>' }, { field: "ispublished", title: "منتشر شده", template: '<input type="checkbox" #= ispublished ? checked="checked" : "" # disabled="disabled" ></input>' } ] }); }); </script>
قصد دارم مجموعه ای کامل از اصول طراحی شیء گرا، الگوهای طراحی و best practice های مربوطه را ارائه دهم. از این رو ابتدا با اصول طراحی شروع میکنم. سپس در مقالات آتی به الگوهای مختلف خواهم پرداخت و تا جایی که معلومات اجازه دهد مشخص خواهم کرد که هر الگو متمرکز بر رعایت کدام یک از اصول است و اینکه آیا مناسب است از الگوی مورد نظر استفاده کنیم.
این مطالب نیز چکیده ای از آموزشهای Lynda, Pluralsight , SkillFeed و کتاب های Gang of four, Headfirst Design
patterns و ...
میباشد .
اصل اول: Encapsulate what varies
"آنچه را که تغییر میکند مشخص و جدا کن یا به عبارتی آنرا کپسوله کن"
برای آنکه بتوانیم کدی منعطف،
قابل استفاده مجدد و خوانا داشته باشیم، ابتدا باید بخشهای ثابت و متغیر کد را تشخیص
دهیم و کاری کنیم تا بخش ثابت، بدون تکرار در جای جای برنامه استفاده شود و سپس برای بخش
متغیر برنامه ریزی کنیم.
اصل دوم: Program to an interface not implementation
" برنامه نویسی را متمرکز بر واسط کن، نه نحوهی پیاده سازی "
برای این اصل تعابیر مختلفی ارائه شده است:
- تمرکز بر واسطها به معنای تمرکز بر رفتار است که باعث میشود انعطاف برنامه بیشتر شده و با تغییر نحوهی پیاده سازی بتوان همچنان سیستمی پایدار داشت.
- تمرکز بر "چه کاری انجام میشود" باعث میشود بدون نگرانی از "چگونگی انجام آن" بتوانیم معماری سیستم را طراحی کنیم.
- واسطها نقش پروتکل را دارند و باعث پنهان شدن نحوهی پیاده سازی از چشم کلاینت (استفاده کنندهی خدمت) میشوند و آنها را ملزم میکنند تا به ورودی و خروجی تمرکز کنند.
برای رعایت این اصل باید:
- سعی بر تعریف واسط برای اکثر کلاسها کنیم
- اشیا را از نوع واسط تعریف
کنیم، نه کلاسهای پیاده ساز آن
در کد زیر نحوهی تعریف واسط را برای کلاس و تعریف اشیاء از نوع واسط را میبینیم:
public interface IMyInterface { void DoWork(); } public class MyImplementation1 : IMyInterface { public void DoWork() { var implementationName = this.GetType().ToString(); Console.WriteLine("DoWork from " + implementationName); } } public class MyImplementation2 : IMyInterface { public void DoWork() { var implementationName = this.GetType().ToString(); Console.WriteLine("DoWork from " + implementationName); } } public class Context { IMyInterface myInterface; public void Print() { myInterface = new MyImplementation1(); myInterface.DoWork(); myInterface = new MyImplementation2(); myInterface.DoWork(); } }
اصل سوم: Favor composition over inheritance
"ترکیب را بر توارث ترجیح بده"
رابطه "دارد" (has a ) انعطاف بیشتری نسبت به ارث بری یا "از نوع ... هست" ( is a ) دارد. برای مثال فرض کنید کلاس Enemy رفتار Movable را دارد و این رفتار در طول بازی بر حسب موقعیتی تغییر میکند. اگر این رفتار را با توارث مدل کنیم، یعنی Enemy از نوع Movable باشد، آنگاه برای هر نوع رفتار Movable (هر نوع پیاده سازی) باید یک نوع Enemy داشته باشیم و تصور کنید بعضی از این پیاده سازیها در کلاس Player یکسان باشد. آنگاه کد باید دوباره تکرار شود. حال تصور کنید این موقعیت را با ترکیب مدل کنیم. آنگاه کلاس Enemy یک شیء از جنس Movable خواهد داشت و در زمان نیاز میتواند نوع این رفتار را با نمونه گیری از کلاسهای پیاده ساز آن، تغییر دهد. در واقع با اینکار اصل اول را رعایت کرده ایم و بخش ثابت را از بخش متغیر جدا نموده ایم.
در زیر مدلسازی با توارث را میبینیم:
public interface Movable { void Move(); } public class EnemyBase : Movable { // Enemy properties goes here protected int x, y; public virtual void Move() { x += 2; y += 2; } } public class EnemyWithMoveType2 : EnemyBase { // override the move method public override void Move() { x += 4; y += 4; } } public class EnemyWithMoveType3 : EnemyBase { // override the move method public override void Move() { x += 6; y += 6; } } public class PlayerBase : Movable { // Player properties goes here protected int x, y; public virtual void Move() { // same code as EnemyBase move method x += 2; y += 2; } } public class PlayerWithMoveType2 : PlayerBase { // override the move method public override void Move() { // same code as EnemyWithMoveType2 move method x += 4; y += 4; } } public class PlayerWithMoveType3 : PlayerBase { // override the move method public override void Move() { // same code as EnemyWithMoveType3 move method x += 6; y += 6; } }
در ادامه نیز مدلسازی با ترکیب را میبینیم:
public interface IMovable { void Move(ref int x, ref int y); } public class EnemyBase { // Enemy properties goes here protected int x, y; IMovable moveBehavior; public void SetMoveBehavior(IMovable _moveBehavior) { moveBehavior = _moveBehavior; } public void Move() { moveBehavior.Move(ref x,ref y); } } public class PlayerBase { // Player properties goes here protected int x, y; IMovable moveBehavior; public void SetMoveBehavior(IMovable _moveBehavior) { moveBehavior = _moveBehavior; } public void Move() { moveBehavior.Move(ref x, ref y); } } public class MoveBehavior1 { public void Move(ref int x, ref int y) { x += 2; y += 2; } } public class MoveBehavior2 : IMovable { public void Move(ref int x, ref int y) { x += 4; y += 4; } } public class MoveBehavior3 : IMovable { public void Move(ref int x, ref int y) { x += 6; y += 6; } }
همانطور که میبینید، با فراخوانی تابع SetMoveBehavior میتوان رفتار را در زمان اجرا تغییر داد.
در مقالهی بعدی به ادامهی اصول خواهم پرداخت. از شنیدن نظرات و سوالات شما خرسند خواهم شد.
ایا معمار نرم افزار هستید؟
....
Becoming a software architect isn't something that simply happens overnight or with a promotion. It's a role , not a rank . It's an evolutionary process where you'll gradually gain the experience and confidence that you need to undertake the role.
در طراحی مدل دامین، بیشتر مواقع از نوعهای اولیه مانند int , string,… استفاده میکنیم و به عبارتی میتوانیم بگوییم در استفاده از این نوع داده وسواس داریم. قطعه کد زیر را در نظر بگیرید:
public class UserFactory { public User CreateUser(string email) { return new User(email); } }
اگر به خاطر داشته باشید، در قسمتهای قبلی در مورد مفهومی به نام Honesty صحبت کردیم. به طور ساده باید بتوانیم از روی امضای تابع، کاری را که تابع انجام میدهد و خروجی آن را ببینیم. این تابع Honest نیست؛ شرایطی که string میتواند درست نباشد، خالی باشد، طول غیر مجاز داشته باشد و ... را نمیتوانیم از امضای تابع حدس بزنیم.
جواب خیلی سادهاست؛ شما نیاز دارید تا یک Type اختصاصی را ایجاد کنید. برای مثال بجای استفاده از نوع string برای یک ایمیل، میتوانید یک کلاس را به عنوان Email ایجاد کنید که مشخصهای به نام Value دارد. این کار به روشهای مختلفی قابل انجام است؛ اما پیشنهاد من استفاده از این روش هست:
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace ValueOf { public class ValueOf<TValue, TThis> where TThis : ValueOf<TValue, TThis>, new() { private static readonly Func<TThis> Factory; /// <summary> /// WARNING - THIS FEATURE IS EXPERIMENTAL. I may change it to do /// validation in a different way. /// Right now, override this method, and throw any exceptions you need to. /// Access this.Value to check the value /// </summary> protected virtual void Validate() { } static ValueOf() { ConstructorInfo ctor = typeof(TThis) .GetTypeInfo() .DeclaredConstructors .First(); var argsExp = new Expression[0]; NewExpression newExp = Expression.New(ctor, argsExp); LambdaExpression lambda = Expression.Lambda(typeof(Func<TThis>), newExp); Factory = (Func<TThis>)lambda.Compile(); } public TValue Value { get; protected set; } public static TThis From(TValue item) { TThis x = Factory(); x.Value = item; x.Validate(); return x; } protected virtual bool Equals(ValueOf<TValue, TThis> other) { return EqualityComparer<TValue>.Default.Equals(Value, other.Value); } public override bool Equals(object obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; return obj.GetType() == GetType() && Equals((ValueOf<TValue, TThis>)obj); } public override int GetHashCode() { return EqualityComparer<TValue>.Default.GetHashCode(Value); } public static bool operator ==(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b) { if (a is null && b is null) return true; if (a is null || b is null) return false; return a.Equals(b); } public static bool operator !=(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b) { return !(a == b); } public override string ToString() { return Value.ToString(); } } }
public class EmailAddress : ValueOf<string, EmailAddress> { }
EmailAddress emailAddress = EmailAddress.From("foo@bar.com");
public class Address : ValueOf<(string firstLine, string secondLine, Postcode postcode), Address> { }
روش معرفی شدهی در این مقاله، صرفا جهت آشنایی بیشتر شما و داشتن کدی تمیزتر از طریق مفاهیم برنامه نویسی تابعی خواهد بود. در دنیای واقعی، احتمالا مسائلی را برای ذخیره سازی این آبجکتها و یا کار با کتابخانههایی مانند Entity Framework خواهید داشت که به سادگی قابل حل است.
در صورتیکه مشکلی در پیاده سازی داشتید، میتوانید مشکل خود را زیر همین مطلب و یا بر روی gist آن کامنت کنید.