برای ایجاد «خواص الحاقی» قبلا در سایت مطلب ایجاد «خواص الحاقی» تهیه شدهاست. در این مطلب قصد داریم راه حل ارائه شدهی در مطلب مذکور را با یک TypeDescriptionProvider سفارشی ترکیب کرده تا به صورت یکدست، از طریق TypeDescriptor بتوان به آن خواص نیز دسترسی داشته باشیم.
فرض کنید در یک سیستم Modular Monolith، نیاز جدیدی به دست شما رسیده است که به شرح زیر میباشد:
نیاز داریم در گریدی از صفحهی X مربوط به «مؤلفه 1»، ستونی جدید را اضافه کنید و دیتای مربوط به این ستون، توسط «مؤلفه 2» مهیا خواهد شد.
- قبلا «مؤلفه 2» ارجاعی را به «مؤلفه 1» داده است؛ لذا امکان ارجاع معکوس را در این حالت، نداریم.
- «مؤلفه 1» باید بتواند مستقل از «مؤلفه 2» نیز توزیع شده و کار کند؛ لذا این نیاز برای زمانی است که «مؤلفه 2» برای توزیع در Component Model ما وجود داشته باشد.
- نمیخواهیم در آینده برای نیازهای مشابه در همان صفحهی X، تغییر جدیدی را در «مؤلفه 1» داشته باشیم (اضافه کردن خصوصیت مورد نظر به مدل نمایشی یا اصطلاحا ویو-مدل متناظر با گرید در در زمان طراحی، جواب مساله نمیباشد)
- میخواهیم به یک طراحی با Loose Coupling (اتصال سست و ضعیف، وابستگی ضعیف) دست پیدا کنیم.
در این حالت «مؤلفه 1» بدون آگاهی از سایر مؤلفهها، همهی پیاده سازیهای IExtraColumnConenvtion را در زمان اجرا یافته و از آنها برای ایجاد ستونهای جدید، استفاده خواهد کرد.
واسط مذکور به شکل زیر میباشد:
public interface IConvention { } public interface IExtraColumnConvention<T> : IConvention { string Name { get; } string Title { get; } void Populate(IEnumerable<T> list); }
البته این واسط میتواند جزئیات بیشتری را هم شامل شود.
گام اول: طراحی TypeDescriptionProvider
در .NET به دو طریق میتوان به متادیتای یک Type دسترسی داشت:
- استفاده از API Reflection موجود در فضای نام System.Reflection
- کلاس TypeDescriptor
به طور کلی هدف از این کلاس در دات نت، ارائه اطلاعاتی در خصوص یک وهله از جمله: Attributeها، Propertyها، Eventهای آن و غیره، میباشد. هنگام استفاده از Reflection، اطلاعات بدست آمده از Type، به دلیل اینکه بعد از کامپایل نمیتوانند تغییر کنند، لذا قابلیت توسعه پذیری را هم ندارند. در مقابل، با استفاده از کلاس TypeDescriptor این توسعه پذیری را برای وهلههای مختلف میتوانید داشته باشید.
برای مهیا کردن متادیتای سفارشی (در اینجا اطلاعات مرتبط با خصوصیات الحاقی) برای TypeDescriptor، نیاز است یک TypeDescriptionProvider سفارشی را طراحی کنیم.
/// <summary> /// Use this provider when you need access ExtraProperties with TypeDescriptor.GetProperties(instance) /// </summary> public class ExtraPropertyTypeDescriptionProvider<T> : TypeDescriptionProvider where T : class { private static readonly TypeDescriptionProvider Default = TypeDescriptor.GetProvider(typeof(T)); public ExtraPropertyTypeDescriptionProvider() : base(Default) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type instanceType, object instance) { var descriptor = base.GetTypeDescriptor(instanceType, instance); return instance == null ? descriptor : new ExtraPropertyCustomTypeDescriptor(descriptor, instance); } private sealed class ExtraPropertyCustomTypeDescriptor : CustomTypeDescriptor { //... } }
در تکه کد بالا، ابتدا تامین کنندهی پیشفرض مرتبط با نوع جنریک مورد نظر را یافته و به عنوان تامین کنندهی پایه معرفی کردهایم. سپس برای معرفی CustomTypeDescritpr باید متد GetTypeDescriptor را بازنویسی کنیم. در اینجا لازم است برای معرفی متادیتا مرتبط با یک نوع، یک پیاده سازی از واسط ICustomTypeDescriptor را ارائه کنیم:
private sealed class ExtraPropertyCustomTypeDescriptor : CustomTypeDescriptor { private readonly IEnumerable<ExtraPropertyDescriptor<T>> _instanceExtraProperties; public ExtraPropertyCustomTypeDescriptor(ICustomTypeDescriptor defaultDescriptor, object instance) : base(defaultDescriptor) { _instanceExtraProperties = instance.ExtraPropertyList<T>(); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { var properties = new PropertyDescriptorCollection(null); foreach (PropertyDescriptor property in base.GetProperties(attributes)) { properties.Add(property); } foreach (var property in _instanceExtraProperties) { properties.Add(property); } return properties; } public override PropertyDescriptorCollection GetProperties() { return GetProperties(null); } }
public static class ExtraProperties { //... public static IEnumerable<ExtraPropertyDescriptor<T>> ExtraPropertyList<T>(this object instance) where T : class { if (!PropertyCache.TryGetValue(instance, out var properties)) throw new KeyNotFoundException($"key: {instance.GetType().Name} was not found in dictionary"); return properties.Select(p => new ExtraPropertyDescriptor<T>(p.PropertyName, p.PropertyValueFunc, p.SetPropertyValueFunc, p.PropertyType, p.Attributes)); } }
public sealed class ExtraPropertyDescriptor<T> : PropertyDescriptor where T : class { private readonly Func<object, object> _propertyValueFunc; private readonly Action<object, object> _setPropertyValueFunc; private readonly Type _propertyType; public ExtraPropertyDescriptor( string propertyName, Func<object, object> propertyValueFunc, Action<object, object> setPropertyValueFunc, Type propertyType, Attribute[] attributes) : base(propertyName, attributes) { _propertyValueFunc = propertyValueFunc; _setPropertyValueFunc = setPropertyValueFunc; _propertyType = propertyType; } public override void ResetValue(object component) { } public override bool CanResetValue(object component) => true; public override object GetValue(object component) => _propertyValueFunc(component); public override void SetValue(object component, object value) => _setPropertyValueFunc(component, value); public override bool ShouldSerializeValue(object component) => true; public override Type ComponentType => typeof(T); public override bool IsReadOnly => _setPropertyValueFunc == null; public override Type PropertyType => _propertyType; }
[TypeDescriptionProvider(typeof(ExtraPropertyTypeDescriptionProvider<Person>))] private class Person { public string Name { get; set; } public string Family { get; set; } }
[Test] public void Should_TypeDescriptor_GetProperties_Returns_ExtraProperties_And_PredefinedProperties() { //Arrange var rabbal = new Person {Name = "GholamReza", Family = "Rabbal"}; const string propertyName = "Title"; const string propertyValue = "Software Engineer"; //Act rabbal.ExtraProperty(propertyName, propertyValue); var title = TypeDescriptor.GetProperties(rabbal).Find(propertyName, true); //Assert rabbal.ExtraProperty<string>(propertyName).ShouldBe(propertyValue); title.ShouldNotBeNull(); title.GetValue(rabbal).ShouldBe(propertyValue); }
گام دوم: استفاده از IExtraColumnConvention برای نمایش ستونهای الحاقی
public class Column4Convention : IExtraColumnConvention<Product> { public string Name => "Column4"; public string Title => "Column 4" public void Populate(IEnumerable<Product> list) { //TODO: forEach on list and set ExtraProperty // item.ExtraProperty(Name,value) // item.ExtraProperty(Name,(obj)=> value) // item.ExtraProperty(Name,(obj)=> value, (obj,value)=>) } } public class Column2Convention : IExtraColumnConvention<Product> { public string Name => "Column2"; public string Title => "Column 2" public void Populate(IEnumerable<Product> list) { //TODO: forEach on list and set ExtraProperty } } public class Column3Convention : IExtraColumnConvention<Product> { public string Name => "Column3"; public string Title => "Column 3" public void Populate(IEnumerable<Product> list) { //TODO: forEach on list and set ExtraProperty } }
سپس این پیادهسازیها از طریق مکانیزمی مانند معرفی آنها به یک IoC Container، توسط میزبان (مؤلفه 1) قابل دسترسی خواهد بود. در نهایت میزبان، قبل از نمایش محصولات، به شکل زیر عمل خواهد کرد:
var products = _productService.PagedList(page:1, pageSize:10); var columns = _provider.GetServices<IExtraColumnConvention<Product>>(); foreach(var column in columns) { column.Populate(products); }
من همیشه در مورد EF یک نظری داشتم و آن اینست که با اینکه یک ORM، یک هدف مهم را در نظر دارد و آن اینست که تا حد ممکن استانداردهایی را که بین تمامی دیتابیسها مشترک است، رعایت کند، ولی باز قابل قبول است اگر بگوییم که کاربران EF انتظار داشته باشند تا اطلاعات بیشتری در مورد sql server در آن نهفته باشد. از یک سو هر دو محصول مایکروسافت هستند و از سوی دیگر مطمئنا توسعه گران محصولات دات نت بیش از هر چیزی به sql server نگاه ویژهتری دارند. پس مایکروسافت در کنار حفظ آن ویژگیهای مشترک، باید به حفظ استانداردهای جدایی برای sql server هم باشد.
تعدادی از برنامه نویسان در هنگام ایجاد Domain Model کم لطفیهای زیادی را میکنند که یکی از آنها عدم کنترل نوع دادههای خود است. مثلا برای رشتهها هیچ محدودیتی را در نظر نمیگیرند. شاید در سمت کلاینت اینکار را انجام میدهند؛ ولی نکتهی مهم در طرف دیتابیس است که چگونه تعریف میشود. در این حالت (nvarchar(MAX در نظر گرفته میشود که به معنی اشاره به منطقه دوگیگابایتی از اطلاعات است. در نکات بعدی، قصد داریم این مرحله را یک گام به جلوتر پیش ببریم و آن هم ایجاد نوع دادههای بهینهتر در Sql Server است.
نکته مهم: بدیهی است که تغییرات زیر، ORM شما را تنها به sql server مقید میکند که بعدها در صورت تغییر دیتابیس نیاز به حذف موارد زیر را خواهید داشت؛ در غیر اینصورت به مشکل عدم ایجاد دیتابیس برخواهید خورد.
اولین مورد مهم بحث تاریخ و زمان است؛ وقتی ما یک نوع داده را تنها DateTime در نظر بگیریم، در Sql Server هم همین نوع داده وجود دارد و انتخاب میشود. ولی اگر شما واقعا نیازی به این نوع داده نداشته باشید چطور؟ در حال حاضر من بر روی یک برنامهی کارخانه کار میکنم که بخش کارمندان و گارگران آن سه داده زمانی زیر را شامل میشود:
public DateTime BirthDate { get; set; } public DateTime HireDate { get; set; } public DateTime? LeaveDate { get; set; }
حال به جدول زیر نگاه کنید که هر نوع داده چه مقدار فضا را به خود اختصاص میدهد:
SmallDateTime | 4 بایت |
DateTime | 8 بایت |
DateTime2 | 6 تا 8 بایت |
DateTimeOffset | 8 تا 10 بایت |
Date | 3 بایت |
Time | 3 تا 5 بایت |
از این جدول چه میفهمید؟ با یک نگاه میتوان فهمید که ساختار بالای من باید 24 بایت گرفته باشد؛ برای ساختاری که هم تاریخ و هم زمان (ساعت) را پشتیبانی میکند. ولی با نگاه دقیقتر به نام پراپرتیها این نکته روشن میشود که ما یک گپ Gap (فضای بیهوده) داریم چون زمان تولد، استخدام و ترک سازمان اصلا نیازی به ساعت ندارند و همان تاریخ کافی است. یعنی نوع Date با حجم کلی 9 بایت؛ که در نتیجه 15 بایت صرفه جویی در یک رکورد صورت خواهد گرفت.
پس کد بالا را به شکل زیر تغییر میدهم:
[Column(TypeName = "date")] public DateTime BirthDate { get; set; } [Column(TypeName = "date")] public DateTime HireDate { get; set; } [Column(TypeName = "date")] public DateTime? LeaveDate { get; set; }
خصوصیت Column از نسخه 4.5دات نت فریم ورک اضافه شده و در فضای نام System.ComponentModel.DataAnnotations.Schema قرار گرفته است.نوعهایی که در بالا با سایز متغیر هستند، به نسبت دقتی که برای آن تعیین میکنید، سایز میگیرند. مثل (time(0 که 3 بایت از حافظه را میگیرد. در صورتی که time معرفی کنید، به جای اینکه از شیء DateTime استفاده کنید، از شی Timespan استفاده کنید، تا در پشت صحنه از نوع داده time استفاده کند. در این حالت حداکثر حافظه یعنی 5 بایت را برخواهد داشت و بهترین حالت ممکن این هست که نیاز خود را بسنجید و خودتان دقت آن را مشخص کنید. دو شکل زیر نحوهی تعریف نوع زمان را مشخص میکنند. یکی حالت پیش فرض و دیگری انتخاب دقت:
public class Testtypes { public TimeSpan CloseTime { get; set; } public TimeSpan CloseTime2 { get; set; } } public class TestConfig : EntityTypeConfiguration<Testtypes> { public TestConfig() { this.Property(x => x.CloseTime2).HasPrecision(3); } }
مورد دوم در مورد دادههای اعشاری است:
float flExample=23.2f;
//real public float FloatData { get; set; } //real public Single SingleData { get; set; } //float public double DoubleData { get; set; }
4235.254
decimal d1=4545.112m;
modelBuilder.Entity<Class>().Property(object => object.property).HasPrecision(7, 3);
public class Testtypes { public Decimal Decimal1 { get; set; } public Decimal Decimal2 { get; set; } } public class TestConfig : EntityTypeConfiguration<Testtypes> { public TestConfig() { this.Property(x => x.Decimal2).HasPrecision(7, 3); } }
مورد سوم مبحث رشته هاست:
[StringLength(25)] public string FirstName { get; set; } [StringLength(30)] [Column(TypeName = "varchar")] public string EnProductTitle { get; set; } public string ArticleContent { get; set; } [Column(TypeName = "varchar(max)")] public string ArticleContentEn { get; set; }
this.Property(e => e.EnProductTitle).HasColumnType("VARCHAR").HasMaxLength(30);
modelBuilder.Properties<string>().Configure(c => c.HasColumnType("varchar")); //=========== یا modelBuilder.Properties<string>().Configure(c => c.IsUnicode(false));
//tinyInt public byte Age { get; set; } //smallInt public Int16 OldInt { get; set; } //int public int Int32 { get; set; } //Bigint public Int64 HighNumbers { get; set; }
C# Regex class provides pattern matching funcions in form of regular expressions. The source code examples in this article show how to use Regular Expressions to validate different inputs. The code provides methods to validate Alphabet, AlphaNumeric, Integer, Postive Integers, Floating point numbers and so on
- در کدهای فوق، فقط این چند سطر باید تغییر کنند:
//var dataS = childNode.Attributes.First(x => x.Name == "data-s"); var dataS = childNode.Attributes.First(x => x.Name == "ng-click"); var startTime = new Regex("(?s)start=(.+?)'").Matches(dataS.Value) .OfType<Match>() .First() .Groups[1] .Value; itemsList.Add(new TranscriptItem { StartTime = double.Parse(startTime), Text = HttpUtility.HtmlDecode(childNode.InnerText.Trim()) });
مروری بر روش ها و رویکردهای مختلف در یادگیری مدل
همان گونه که اشاره شد در روشهای با ناظر (برای مثال الگوریتمهای دسته بندی) کل مجموعه دادهها به دو بخش مجموعه دادههای آموزشی و مجموعه دادههای آزمایشی تقسیم میشود. در مرحله یادگیری (آموزش) مدل، الگوریتم براساس مجموعه دادههای آموزشی یک مدل میسازد که شکل مدل ساخته شده به الگوریتم یادگیرنده مورد استفاده بستگی دارد. در مرحله ارزیابی براساس مجموعه دادههای آزمایشی دقت و کارائی مدل ساخته شده بررسی میشود. توجه داشته باشید که مجموعه دادههای آزمایشی برای مدل ساخته شده پیش از این ناشناخته هستند.
در مرحله یادگیری مدل؛ برای مقابله با مشکل به خاطرسپاری (Memorization) مجموعه دادههای آموزشی، در برخی موارد بخشی از مجموعه دادههای آموزشی را از آن مجموعه جدا میکنند که با عنوان مجموعه داده ارزیابی (Valid Dataset) شناسائی میشود. استفاده از مجموعه داده ارزیابی باعث میشود که مدل ساخته شده، مجموعه دادههای آموزشی را حقیقتاً یاد بگیرد و در پی به خاطرسپاری و حفظ آن نباشد. به بیان دیگر در مرحله یادگیری مدل؛ تا قبل از رسیدن به لحظه ای، مدل در حال یادگیری و کلی سازی (Generalization) است و از آن لحظه به بعد در حال به خاطرسپاری (Over Fitting) مجموعه دادههای آموزشی است. بدیهی است به خاطرسپاری باعث افزایش دقت مدل برای مجموعه دادههای آموزشی و بطور مشابه باعث کاهش دقت مدل برای مجموعه دادههای آزمایشی میشود. بدین منظور جهت جلوگیری از مشکل به خاطرسپاری از مجموعه داده ارزیابی استفاده میشود که به شکل غیر مستقیم در فرآیند یادگیری مدل، وارد عمل میشوند. بدین ترتیب مدلی که مفهومی را از دادههای آموزشی فرا گرفته، نسبت به مدلی که صرفاً دادههای آموزشی را به خوبی حفظ کرده است، برای مجموعه داده آزمایشی دقت به مراتب بالاتری دارد. این حقیقت در بیشتر فرآیندهای آموزشی که از مجموعه داده ارزیابی بهره میگیرند قابل مشاهده است.
در روشهای بدون ناظر یا روشهای توصیفی (برای مثال خوشه بندی) الگوریتمها فاقد مراحل آموزشی و آزمایشی هستند و در پایان عملیات یادگیری مدل، مدل ساخته شده به همراه کارائی آن به عنوان خروجی ارائه میشود، برای مثال در الگوریتمهای خوشه بندی خروجی همان خوشههای ایجاد شده هستند و یا خروجی در روش کشف قوانین انجمنی عبارت است از مجموعه ای از قوانین «اگر- آنگاه» که بیانگر ارتباط میان رخداد توامان مجموعه ای از اشیاء با یکدیگر میباشد.
در این قسمت عملیات ساخت مدل در فرآیند داده کاوی برای سه روش دسته بندی، خوشه بندی و کشف قوانین انجمنی ارائه میشود. بدیهی است برای هر کدام از این روشها علاوه بر الگوریتمهای معرفی شده، الگوریتمهای متنوعی دیگری نیز وجود دارد. در ادامه سعی میشود به صورت کلان به فلسفه یادگیری مدل پرداخته شود. فهرست مطالب به شرح زیر است:
1- دسته بندی:
1-1- دسته بندی مبتنی بر درخت تصمیم (Decision Tree based methods) :
1-2- دسته بندهای مبتنی بر قانون (Rule based methods) :
1-3- دسته بندهای مبتنی بر نظریه بیز (Naïve Bayes and Bayesian belief networks) :
2- خوشه بندی:
2-1- خوشه بندی افرازی (Centroid Based Clustering) :
2-1-1- الگوریتم خوشه بندی K-Means :
2-1-2- الگوریتم خوشه بندی K-Medoids :
2-1-3- الگوریتم خوشه بندی Bisecting K-Means :
2-1-4- الگوریتم خوشه بندی Fuzzy C-Means :
2-2- خوشه بندی سلسله مراتبی (Connectivity Based Clustering (Hierarchical Clustering :
2-2-1- روشهای خوشه بندی تجمیعی (Agglomerative Clustering) :
2-2-2- روشهای خوشه بندی تقسیمی (Divisive Clustering) :
2-3- خوشه بندی مبتنی بر چگالی (Density Based Clustering) :
3- کشف قوانین انجمنی :
3-1- الگوریتم های Apriori ، Brute-Force و FP-Growth:
1- دسته بندی:
در الگوریتمهای دسته بندی، برای هر یک از رکوردهای مجموعه داده مورد کاوش، یک برچسب که بیانگر حقیقتی از مساله است تعریف میشود و هدف الگوریتم یادگیری؛ یافتن نظم حاکم بر این برچسب هاست. به بیان دیگر در مرحله آموزش؛ مجموعه دادههای آموزشی به یکی از الگوریتمهای دسته بندی داده میشود تا بر اساس سایر ویژگیها برای مقادیر ویژگی دسته، مدل ساخته شود. سپس در مرحله ارزیابی؛ دقت مدل ساخته شده به کمک مجموعه دادههای آزمایشی ارزیابی خواهد شد. انواع گوناگون الگوریتمهای دسته بندی را میتوان بصورت ذیل برشمرد:
1-1- دسته بندی مبتنی بر درخت تصمیم (Decision Tree based methods):
از مشهورترین روشهای ساخت مدل دسته بندی میباشد که دانش خروجی را به صورت یک درخت از حالات مختلف مقادیر ویژگیها ارائه میکند. بدین ترتیب دسته بندیهای مبتنی بر درخت تصمیم کاملاً قابل تفسیر میباشند. در حالت کلی درخت تصمیم بدست آمده برای یک مجموعه داده آموزشی؛ واحد و یکتا نیست. به بیان دیگر براساس یک مجموعه داده، درختهای تصمیم مختلفی میتوان بدست آورد. عموماً به منظور فراهم نمودن اطلاعات بیشتری از داده ها، از میان ویژگیهای موجود یک Case ابتدا آنهایی که دارای خاصیت جداکنندگی بیشتری هستند انتخاب میشوند. در واقع براساس مجموعه دادههای آموزشی از میان ویژگی ها، یک ویژگی انتخاب میشود و در ادامه مجموعه رکوردها براساس مقدار این ویژگی شکسته میشود و این فرآیند ادامه مییابد تا درخت کلی ساخته شود. پس از ساخته شدن مدل، میتوان آن را بر روی مجموعه دادههای آزمایشی اعمال (Apply) نمود. منظور از اعمال کردن مدل، پیش بینی مقدار ویژگی یک دسته برای یک رکورد آزمایشی براساس مدل ساخته شده است. توجه شود هدف پیش بینی ویژگی دسته این رکورد، براساس درخت تصمیم موجود است.
بطور کلی الگوریتمهای تولید درخت تصمیم مختلفی از جمله SPRINT، SLIQ، C4.5، ID3، CART و HUNT وجود دارد. این الگوریتمها به لحاظ استفاده از روشهای مختلف جهت انتخاب ویژگی و شرط توقف در ساخت درخت با یکدیگر تفاوت دارند. عموماً الگوریتمهای درخت تصمیم برای شناسائی بهترین شکست، از یک مکانیزم حریصانه (Greedy) استفاده میکنند که براساس آن شکستی که توزیع دستهها در گرههای حاصل از آن همگن باشد، نسبت به سایر شکستها بهتر خواهد بود. منظور از همگن بودن گره این است که همه رکوردهای موجود در آن متعلق به یک دسته خاص باشند، بدین ترتیب آن گره به برگ تبدیل خواهد شد. بنابراین گره همگن گره ای است که کمترین میزان ناخالصی (Impurity) را دارد. به بیان دیگر هر چه توزیع دستهها در یک گره همگنتر باشد، آن گره ناخالصی کمتری خواهد داشت. سه روش مهم برای محاسبه ناخالصی گره وجود دارد که عبارتند از: ضریب GINI، روش Entropy و Classification Error.
از مزایای درخت تصمیم میتوان به توانایی کار با دادههای گسسته و پیوسته، سهولت در توصیف شرایط (با استفاده از منطق بولی) در درخت تصمیم، عدم نیاز به تابع تخمین توزیع، کشف روابط غیرمنتظره یا نامعلوم و ... اشاره نمود.
همچنین از معایب درخت تصمیم نسبت به دیگر روشهای داده کاوی میتوان این موارد را برشمرد: تولید درخت تصمیم گیری هزینه بالائی دارد، در صورت همپوشانی گرهها تعداد گرههای پایانی زیاد میشود، طراحی درخت تصمیم گیری بهینه دشوار است، احتمال تولید روابط نادرست وجود دارد و ... .
میتوان موارد استفاده از دسته بند درخت تصمیم نسبت به سایر دسته بندی کنندههای تک مرحله ای رایج را؛ حذف محاسبات غیر ضروری و انعطاف پذیری در انتخاب زیر مجموعههای مختلفی از صفات برشمرد. در نهایت از جمله مسائل مناسب برای یادگیری درخت تصمیم، میتوان به مسائلی که در آنها نمونهها به شکل جفتهای «صفت-مقدار» بازنمائی میشود و همچنین مسائلی که تابع هدف، مقادیر خروجی گسسته دارد اشاره نمود.
1-2- دسته بندهای مبتنی بر قانون (Rule based methods):
این دسته بندها دانش خروجی خود را به صورت یک مجموعه از قوانین «اگر-آنگاه» نشان میدهند. هر قانون یک بخش شرایط (LHS: Left Hand Side) و یک بخش نتیجه (RHS: Right Hand Side) دارد. بدیهی است اگر تمام شرایط مربوط به بخش مقدم یک قانون درباره یک رکورد خاص درست تعبیر شود، آن قانون آن رکورد را پوشش میدهد. دو معیار Accuracy و Coverage برای هر قانون قابل محاسبه است که هر چه میزان این دو معیار برای یک قانون بیشتر باشد، آن قانون؛ قانونی با ارزشتر محسوب میشود.
Coverage یک قانون، برابر با درصد رکوردهایی است که بخش شرایط قانون مورد نظر در مورد آنها صدق میکند و درست تعبیر میشود. بنابراین هر چه این مقدار بیشتر باشد آن قانون، قانونی کلیتر و عمومیتر میباشد.
Accuracy یک قانون بیان میکند که در میان رکوردهایی که بخش شرایط قانون در مورد آنها صدق میکند، چند درصد هر دو قسمت قانون مورد نظر در مورد آنها صحیح است.
چنانچه مجموعه همه رکوردها را در نظر بگیریم؛ مطلوبترین حالت این است که همواره یک رکورد توسط یک و تنها یک قانون پوشش داده شود، به بیان دیگر مجموعه قوانین نهایی به صورت جامع (Exhaustive Rules) و دو به دو ناسازگار (Mutually Exclusive Rules) باشند. جامع بودن به معنای این است که هر رکورد حداقل توسط یک قانون پوشش داده شود و معنای قوانین مستقل یا دو به دو ناسازگار بودن بدین معناست که هر رکورد حداکثر توسط یک قانون پوشش داده شود.
مجموعه قوانین و درخت تصمیم عیناً یک مجموعه دانش را نشان میدهند و تنها در شکل نمایش متفاوت از هم هستند. البته روشهای مبتنی بر قانون انعطاف پذیری و تفسیرپذیری بالاتری نسبت به روشهای مبتنی بر درخت دارند. همچنین اجباری در تعیین وضعیت هایی که در یک درخت تصمیم برای ترکیب مقادیر مختلف ویژگیها رخ میدهد ندارند و از این رو دانش خلاصهتری ارائه میدهند.
1-3- دسته بندهای مبتنی بر نظریه بیز (Naïve Bayes and Bayesian belief networks):
دسته بند مبتنی بر رابطه نظریه بیز (Naïve Bayes) از یک چهارچوب احتمالی برای حل مسائل دسته بندی استفاده میکند. براساس نظریه بیز رابطه I برقرار است:
هدف محاسبه دسته یک رکورد مفروض با مجموعه ویژگیهای (A1,A2,A3,…,An) میباشد. در واقع از بین دستههای موجود به دنبال پیدا کردن دسته ای هستیم که مقدار II را بیشینه کند. برای این منظور این احتمال را برای تمامی دستههای مذکور محاسبه نموده و دسته ای که مقدار این احتمال به ازای آن بیشینه شود را به عنوان دسته رکورد جدید در نظر میگیریم. ذکر این نکته ضروری است که بدانیم نحوه محاسبه برای ویژگیهای گسسته و پیوسته متفاوت میباشد.
2- خوشه بندی:
خوشه را مجموعه ای از دادهها که به هم شباهت دارند تعریف میکنند و هدف از انجام عملیات خوشه بندی فهم (Understanding) گروه رکوردهای مشابه در مجموعه دادهها و همچنین خلاصه سازی (Summarization) یا کاهش اندازهی مجموعه دادههای بزرگ میباشد. خوشه بندی از جمله روش هایی است که در آن هیچ گونه برچسبی برای رکوردها در نظر گرفته نمیشود و رکوردها تنها براساس معیار شباهتی که معرفی شده است، به مجموعه ای از خوشهها گروه بندی میشوند. عدم استفاده از برچسب موجب میشود الگوریتمهای خوشه بندی جزء روشهای بدون ناظر محسوب شوند و همانگونه که پیشتر ذکر آن رفت در خوشه بندی تلاش میشود تا دادهها به خوشه هایی تقسیم شوند که شباهت بین داده ای درون هر خوشه بیشینه و بطور مشابه شباهت بین دادهها در خوشههای متفاوت کمینه شود.
چنانچه بخواهیم خوشه بندی و دسته بندی را مقایسه کنیم، میتوان بیان نمود که در دسته بندی هر داده به یک دسته (طبقه) از پیش مشخص شده تخصیص مییابد ولی در خوشه بندی هیچ اطلاعی از خوشهها وجود ندارد و به عبارتی خود خوشهها نیز از دادهها استخراج میشوند. به بیان دیگر در دسته بندی مفهوم دسته در یک حقیقت خارجی نهفته است حال آنکه مفهوم خوشه در نهان فواصل میان رکورد هاست. مشهورترین تقسیم بندی الگوریتمهای خوشه بندی به شرح زیر است:
2-1- خوشه بندی افرازی (Centroid Based Clustering) :
تقسیم مجموعه دادهها به زیرمجموعههای بدون همپوشانی، به طریقی که هر داده دقیقاً در یک زیر مجموعه قرار داشته باشد. این الگوریتمها بهترین عملکرد را برای مسائل با خوشههای به خوبی جدا شده از خود نشان میدهند. از الگوریتمهای افرازی میتوان به موارد زیر اشاره نمود:
2-1-1- الگوریتم خوشه بندی K-Means :
در این الگوریتم عملاً مجموعه دادهها به تعداد خوشههای از پیش تعیین شده تقسیم میشوند. در واقع فرض میشود که تعداد خوشهها از ابتدا مشخص میباشند. ایده اصلی در این الگوریتم تعریف K مرکز برای هر یک از خوشهها است. بهترین انتخاب برای مراکز خوشهها قرار دادن آنها (مراکز) در فاصله هر چه بیشتر از یکدیگر میباشد. پس از آن هر رکورد در مجموعه داده به نزدیکترین مرکز خوشه تخصیص مییابد. معیار محاسبه فاصله در این مرحله هر معیاری میتواند باشد. این معیار با ماهیت مجموعه داده ارتباط تنگاتنگی دارد. مشهورترین معیارهای محاسبه فاصله رکوردها در روش خوشه بندی معیار فاصله اقلیدسی و فاصله همینگ میباشد. لازم به ذکر است در وضعیتی که انتخاب مراکز اولیه خوشهها به درستی انجام نشود، خوشههای حاصل در پایان اجرای الگوریتم کیفیت مناسبی نخواهند داشت. بدین ترتیب در این الگوریتم جواب نهائی به انتخاب مراکز اولیه خوشهها وابستگی زیادی دارد که این الگوریتم فاقد روالی مشخص برای محاسبه این مراکز میباشد. امکان تولید خوشههای خالی توسط این الگوریتم از دیگر معایب آن میباشد.
2-1-2- الگوریتم خوشه بندی K-Medoids :
این الگوریتم برای حل برخی مشکلات الگوریتم K-Means پیشنهاد شده است، که در آن بجای کمینه نمودن مجموع مجذور اقلیدسی فاصله بین نقاط (که معمولاً به عنوان تابع هدف در الگوریتم K-Means مورد استفاده قرار میگیرد)، مجموع تفاوتهای فواصل جفت نقاط را کمینه میکنند. همچنین بجای میانگین گیری برای یافتن مراکز جدید در هر تکرار حلقه یادگیری مدل، از میانه مجموعه اعضای هر خوشه استفاده میکنند.
2-1-3- الگوریتم خوشه بندی Bisecting K-Means :
ایده اصلی در این الگوریتم بدین شرح است که برای بدست آوردن K خوشه، ابتدا کل نقاط را به شکل یک خوشه در نظر میگیریم و در ادامه مجموعه نقاط تنها خوشه موجود را به دو خوشه تقسیم میکنیم. پس از آن یکی از خوشههای بدست آمده را برای شکسته شدن انتخاب میکنیم و تا زمانی که K خوشه را بدست آوریم این روال را ادامه میدهیم. بدین ترتیب مشکل انتخاب نقاط ابتدایی را که در الگوریتم K-Means با آن مواجه بودیم نداشته و بسیار کاراتر از آن میباشد.
2-1-4- الگوریتم خوشه بندی Fuzzy C-Means:
کارائی این الگوریتم نسبت به الگوریتم K-Means کاملاً بالاتر میباشد و دلیل آن به نوع نگاهی است که این الگوریتم به مفهوم خوشه و اعضای آن دارد. در واقع نقطه قوت الگوریتم Fuzzy C-Means این است که الگوریتمی همواره همگراست. در این الگوریتم تعداد خوشهها برابر با C بوده (مشابه الگوریتم K-Means) ولی برخلاف الگوریتم K-Means که در آن هر رکورد تنها به یکی از خوشههای موجود تعلق دارد، در این الگوریتم هر کدام از رکوردهای مجموعه داده به تمامی خوشهها متعلق است. البته این میزان تعلق با توجه به عددی که درجه عضویت تعلق هر رکورد را نشان میدهد، مشخص میشود. بدین ترتیب عملاً تعلق فازی هر رکورد به تمامی خوشهها سبب خواهد شد که امکان حرکت ملایم عضویت هر رکورد به خوشههای مختلف امکان پذیر شود. بنابراین در این الگوریتم امکان تصحیح خطای تخصیص ناصحیح رکوردها به خوشهها سادهتر میباشد و مهمترین نقطه ضعف این الگوریتم در قیاس با K-Means زمان محاسبات بیشتر آن میباشد. میتوان پذیرفت که از سرعت در عملیات خوشه بندی در برابر رسیدن به دقت بالاتر میتوان صرفه نظر نمود.
2-2- خوشه بندی سلسله مراتبی (Connectivity Based Clustering (Hierarchical Clustering:
در پایان این عملیات یک مجموعه از خوشههای تودرتو به شکل سلسله مراتبی و در قالب ساختار درختی خوشه بندی بدست میآید که با استفاده از نمودار Dendrogram چگونگی شکل گیری خوشههای تودرتو را میتوان نمایش داد. این نمودار درخت مانند، ترتیبی از ادغام و تجزیه را برای خوشههای تشکیل شده ثبت میکند، یکی از نقاط قوت این روش عدم اجبار برای تعیین تعداد خوشهها میباشد (بر خلاف خوشه بندی افرازی). الگوریتمهای مبتنی بر خوشه بندی سلسله مراتبی به دو دسته مهم تقسیم بندی میشوند:
2-2-1- روشهای خوشه بندی تجمیعی (Agglomerative Clustering) :
با نقاطی به عنوان خوشههای منحصر به فرد کار را آغاز نموده و در هر مرحله، به ادغام خوشههای نزدیک به یکدیگر میپردازیم، تا زمانی که تنها یک خوشه باقی بماند.
عملیات کلیدی در این روش، چگونگی محاسبه میزان مجاورت دو خوشه است و روشهای متفاوت تعریف فاصله بین خوشهها باعث تمایز الگوریتمهای مختلف مبتنی بر ایده خوشه بندی تجمیعی است. برخی از این الگوریتمها عبارتند از: خوشه بندی تجمیعی – کمینه ای، خوشه بندی تجمیعی – بیشینه ای، خوشه بندی تجمیعی – میانگینی، خوشه بندی تجمیعی – مرکزی.
2-2-2- روش های خوشه بندی تقسیمی (Divisive Clustering) :
با یک خوشهی دربرگیرندهی همه نقاط کار را آغاز نموده و در هر مرحله، خوشه را میشکنیم تا زمانی که K خوشه بدست آید و یا در هر خوشه یک نقطه باقی بماند.
2-3- خوشه بندی مبتنی بر چگالی (Density Based Clustering):
تقسیم مجموعه داده به زیرمجموعه هایی که چگالی و چگونگی توزیع رکوردها در آنها لحاظ میشود. در این الگوریتم مهمترین فاکتور که جهت تشکیل خوشهها در نظر گرفته میشود، تراکم و یا چگالی نقاط میباشد. بنابراین برخلاف دیگر روشهای خوشه بندی که در آنها تراکم نقاط اهمیت نداشت، در این الگوریتم سعی میشود تنوع فاصله هایی که نقاط با یکدیگر دارند، در عملیات خوشه بندی مورد توجه قرار گیرد. الگوریتم DBSCAN مشهورترین الگوریتم خوشه بندی مبتنی بر چگالی است.
به طور کلی عملکرد یک الگوریتم خوشه بندی نسبت به الگوریتمهای دیگر، بستگی کاملی به ماهیت مجموعه داده و معنای آن دارد.
3- کشف قوانین انجمنی :
الگوریتمهای کاشف قوانین انجمنی نیز همانند الگوریتمهای خوشه بندی به صورت روشهای توصیفی یا بدون ناظر طبقه بندی میشوند. در این الگوریتمها بدنبال پیدا کردن یک مجموعه از قوانین وابستگی یا انجمنی در میان تراکنشها (برای مثال تراکنشهای خرید در فروشگاه، تراکنشهای خرید و فروش سهام در بورس و ...) هستیم تا براساس قوانین کشف شده بتوان میزان اثرگذاری اشیایی را بر وجود مجموعه اشیاء دیگری بدست آورد. خروجی در این روش کاوش، به صورت مجموعه ای از قوانین «اگر-آنگاه» است، که بیانگر ارتباطات میان رخداد توامان مجموعه ای از اشیاء با یکدیگر میباشد. به بیان دیگر این قوانین میتواند به پیش بینی وقوع یک مجموعه اشیاء مشخص در یک تراکنش، براساس وقوع اشیاء دیگر موجود در آن تراکنش بپردازد. ذکر این نکته ضروری است که بدانیم قوانین استخراج شده تنها استلزام یک ارتباط میان وقوع توامان مجموعه ای از اشیاء را نشان میدهد و در مورد چرایی یا همان علیت این ارتباط سخنی به میان نمیآورد. در ادامه به معرفی مجموعه ای از تعاریف اولیه در این مبحث میپردازیم (در تمامی تعاریف تراکنشهای سبد خرید مشتریان در یک فروشگاه را به عنوان مجموعه داده مورد کاوش در نظر بگیرید):
• مجموعه اشیاء: مجموعه ای از یک یا چند شیء. منظور از مجموعه اشیاء K عضوی، مجموعه ای است که شامل K شیء باشد.
برای مثال:{مسواک، نان، شیر}
• تعداد پشتیبانی (Support Count) : فراوانی وقوع مجموعهی اشیاء در تراکنشهای موجود که آنرا با حرف σ نشان میدهیم.
برای مثال: 2=({مسواک، نان، شیر})σ
• مجموعه اشیاء مکرر (Frequent Item Set) : مجموعه ای از اشیاء که تعداد پشتیبانی آنها بزرگتر یا مساوی یک مقدار آستانه (Min Support Threshold) باشد، مجموعه اشیاء مکرر نامیده میشود.
• قوانین انجمنی: بیان کننده ارتباط میان اشیاء در یک مجموعه از اشیاء مکرر. این قوانین معمولاً به شکل X=>Y هستند.
برای مثال:{نوشابه}<={مسواک، شیر}
مهمترین معیارهای ارزیابی قوانین انجمنی عبارتند از:
• Support: کسری از تراکنشها که حاوی همه اشیاء یک مجموعه اشیاء خاص هستند و آنرا با حرف S نشان میدهند.
برای مثال: 2.2=({نان، شیر})S
• Confidence: کسری از تراکنشهای حاوی همه اشیاء بخش شرطی قانون انجمنی که صحت آن قانون را نشان میدهد که با آنرا حرف C نشان میدهند. برخلاف Support نمیتوانیم مثالی برای اندازه گیری Confidence یک مجموعه اشیاء بیاوریم زیرا این معیار تنها برای قوانین انجمنی قابل محاسبه است.
با در نظر گرفتن قانون X=>Y میتوان Support را کسری از تراکنش هایی دانست که شامل هر دو مورد X و Y هستند و Confidence برابر با اینکه چه کسری از تراکنش هایی که Y را شامل میشوند در تراکنش هایی که شامل X نیز هستند، ظاهر میشوند. هدف از کاوش قوانین انجمنی پیدا کردن تمام قوانین Rx است که از این دستورات تبعیت میکند:
در این دستورات منظور از SuppMIN و ConfMIN به ترتیب عبارت است از کمترین مقدار برای Support و Confidence که بایست جهت قبول هر پاسخ نهائی به عنوان یک قانون با ارزش مورد توجه قرار گیرد. کلیه قوانینی که از مجموعه اشیاء مکرر یکسان ایجاد میشوند دارای مقدار Support مشابه هستند که دقیقاً برابر با تعداد پشتیبانی یا همان σ شیء مکرری است که قوانین انجمنی با توجه به آن تولید شده اند. به همین دلیل فرآیند کشف قوانین انجمنی را میتوان به دو مرحله مستقل «تولید مجموعه اشیاء مکرر» و «تولید قوانین انجمنی مطمئن» تقسیم نمائیم.
در مرحله نخست، تمام مجموعه اشیاء که دارای مقدار Support ≥ SuppMIN میباشند را تولید میکنیم. رابطه I
در مرحله دوم با توجه به مجموعه اشیاء مکرر تولید شده، قوانین انجمنی با اطمینان بالا بدست میآیند که همگی دارای شرط Confidence ≥ ConfMIN هستند. رابطه II
3-1- الگوریتم های Apriori ، Brute-Force و FP-Growth:
یک روش تولید اشیاء مکرر روش Brute-Force است که در آن ابتدا تمام قوانین انجمنی ممکن لیست شده، سپس مقادیر Support و Confidence برای هر قانون محاسبه میشود. در نهایت قوانینی که از مقادیر آستانهی SuppMIN و ConfMIN تبعیت نکنند، حذف میشوند. تولید مجموعه اشیاء مکرر بدین طریق کاری بسیار پرهزینه و پیچیده ای میباشد، در واقع روشهای هوشمندانه دیگری وجود دارد که پیچیدگی بالای روش Brute-Force را ندارند زیرا کل شبکه مجموعه اشیاء را به عنوان کاندید در نظر نمیگیرند. همانند تولید مجموعه اشیاء مکرر، تولید مجموعه قوانین انجمنی نیز بسیار پرهزینه و گران است.
چنانچه یک مجموعه اشیاء مکرر مشخص با d شیء را در نظر بگیریم، تعداد کل قوانین انجمنی قابل استخراج از رابطه III محاسبه میشود. (برای مثال تعداد قوانین انجمنی قابل استخراج از یک مجموعه شیء 6 عضوی برابر با 602 قانون میباشد، که با توجه به رشد d؛ سرعت رشد تعداد قوانین انجمنی بسیار بالا میباشد.)
الگوریتمهای متعددی برای تولید مجموعه اشیاء مکرر وجود دارد برای نمونه الگوریتمهای Apriori و FP-Growth که در هر دوی این الگوریتم ها، ورودی الگوریتم لیست تراکنشها و پارامتر SuppMIN میباشد. الگوریتم Apriori روشی هوشمندانه برای یافتن مجموعه اشیاء تکرار شونده با استفاده از روش تولید کاندید است که از یک روش بازگشتی برای یافتن مجموعه اشیاء مکرر استفاده میکند. مهمترین هدف این الگوریتم تعیین مجموعه اشیاء مکرری است که تعداد تکرار آنها حداقل برابر با SuppMIN باشد. ایده اصلی در الگوریتم Apriori این است که اگر مجموعه اشیایی مکرر باشد، آنگاه تمام زیر مجموعههای آن مجموعه اشیاء نیز باید مکرر باشند. در واقع این اصل همواره برقرار است زیرا Support یک مجموعه شیء هرگز بیشتر از Support زیرمجموعههای آن مجموعه شیء نخواهد بود. مطابق با این ایده تمام ابرمجموعههای مربوط به مجموعه شیء نامکرر از شبکه مجموعه اشیاء حذف خواهند شد (هرس میشوند). هرس کردن مبتنی بر این ایده را هرس کردن بر پایه Support نیز عنوان میکنند که باعث کاهش قابل ملاحظه ای از تعداد مجموعههای کاندید جهت بررسی (تعیین مکرر بودن یا نبودن مجموعه اشیاء) میشود.
الگوریتم FP-Growth در مقایسه با Apriori روش کارآمدتری برای تولید مجموعه اشیاء مکرر ارائه میدهد. این الگوریتم با ساخت یک درخت با نام FP-Tree سرعت فرآیند تولید اشیاء مکرر را به طور چشمگیری افزایش میدهد، در واقع با یکبار مراجعه به مجموعه تراکنشهای مساله این درخت ساخته میشود. پس از ساخته شدن درخت با توجه به ترتیب نزولی Support مجموعه اشیاء تک عضوی (یعنی مجموعه اشیاء) مساله تولید مجموعه اشیاء مکرر به چندین زیر مسئله تجزیه میشود، که هدف در هر کدام از این زیر مساله ها، یافتن مجموعه اشیاء مکرری است که به یکی از آن اشیاء ختم خواهند شد.
الگوریتم Aprior علاوه بر تولید مجموعه اشیاء مکرر، اقدام به تولید مجموعه قوانین انجمنی نیز مینماید. در واقع این الگوریتم با استفاده از مجموعه اشیاء مکرر بدست آمده از مرحله قبل و نیز پارامتر ConfMIN قوانین انجمنی مرتبط را که دارای درجه اطمینان بالائی هستند نیز تولید میکند. به طور کلی Confidence دارای خصوصیت هماهنگی (Monotone) نیست ولیکن Confidence قوانینی که از مجموعه اشیاء یکسانی بوجود میآیند دارای خصوصیت ناهماهنگی هستند. بنابراین با هرس نمودن کلیه ابرقوانین انجمنی یک قانون انجمنی یا Confidence (Rx) ≥ ConfMIN در شبکه قوانین انجمنی (مشابه با شبکه مجموعه اشیاء) اقدام به تولید قوانین انجمنی مینمائیم. پس از آنکه الگوریتم با استفاده از روش ذکر شده، کلیه قوانین انجمنی با اطمینان بالا را در شبکه قوانین انجمنی یافت، اقدام به الحاق نمودن آن دسته از قوانین انجمنی مینماید که پیشوند یکسانی را در توالی قانون به اشتراک میگذارند و بدین ترتیب قوانین کاندید تولید میشوند.
جهت آشنائی بیشتر به List of machine learning concepts مراجعه نمائید.
ایجاد برنامهی backend ارائه دهندهی REST API
در اینجا یک برنامهی سادهی ASP.NET Core Web API را جهت تدارک سرویسهای backend، مورد استفاده قرار میدهیم. هرچند این مورد الزامی نبوده و اگر علاقمند بودید که مستقل از آن کار کنید، حتی میتوانید از سرویس آنلاین JSONPlaceholder نیز برای این منظور استفاده کنید که یک Fake Online REST API است. کار آن ارائهی یک سری endpoint است که به صورت عمومی از طریق وب قابل دسترسی هستند. میتوان به این endpintها درخواستهای HTTP خود را مانند GET/POST/DELETE/UPDATE ارسال کرد و از آن اطلاعاتی را دریافت نمود و یا تغییر داد. به هر کدام از این endpointها یک API گفته میشود که جهت آزمایش برنامهها بسیار مناسب هستند. برای نمونه در قسمت resources آن اگر به آدرس https://jsonplaceholder.typicode.com/posts مراجعه کنید، میتوان لیستی از مطالب را با فرمت JSON مشاهده کرد. کار آن ارائهی آرایهای از اشیاء جاوا اسکریپتی قابل استفادهی در برنامههای frontend است. بنابراین زمانیکه یک HTTP GET را به این endpoint ارسال میکنیم، آرایهای از اشیاء مطالب را دریافت خواهیم کرد. همین endpoint، امکان تغییر این اطلاعات را توسط برای مثال HTTP Delete نیز میسر کردهاست.
اگر علاقمندید بودید میتوانید از JSONPlaceholder استفاده کنید و یا در ادامه دقیقا ساختار همین endpoint ارائهی مطالب آنرا با ASP.NET Core Web API نیز پیاده سازی میکنیم (برای مطالعهی قسمت «ارتباط با سرور» اختیاری است و از هر REST API مشابهی که توسط nodejs یا PHP و غیره تولید شده باشد نیز میتوان استفاده کرد):
مدل مطالب
namespace sample_22_backend.Models { public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } public int UserId { set; get; } } }
منبع دادهی فرضی مطالب
برای ارائهی سادهتر برنامه، یک منبع دادهی درون حافظهای را به همراه یک سرویس، در اختیار کنترلر مطالب، قرار میدهیم:
using System; using System.Collections.Generic; using System.Linq; using sample_22_backend.Models; namespace sample_22_backend.Services { public interface IPostsDataSource { List<Post> GetAllPosts(); bool DeletePost(int id); Post AddPost(Post post); bool UpdatePost(int id, Post post); Post GetPost(int id); } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> public class PostsDataSource : IPostsDataSource { private readonly List<Post> _allPosts; public PostsDataSource() { _allPosts = createDataSource(); } public List<Post> GetAllPosts() { return _allPosts; } public Post GetPost(int id) { return _allPosts.Find(x => x.Id == id); } public bool DeletePost(int id) { var item = _allPosts.Find(x => x.Id == id); if (item == null) { return false; } _allPosts.Remove(item); return true; } public Post AddPost(Post post) { var id = 1; var lastItem = _allPosts.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } post.Id = id; _allPosts.Add(post); return post; } public bool UpdatePost(int id, Post post) { var item = _allPosts .Select((pst, index) => new { Item = pst, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null || id != post.Id) { return false; } _allPosts[item.Index] = post; return true; } private static List<Post> createDataSource() { var list = new List<Post>(); var rnd = new Random(); for (var i = 1; i < 10; i++) { list.Add(new Post { Id = i, UserId = rnd.Next(1, 1000), Title = $"Title {i} ...", Body = $"Body {i} ..." }); } return list; } } }
کنترلر Web API برنامهی backend
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using sample_22_backend.Models; using sample_22_backend.Services; namespace sample_22_backend.Controllers { [ApiController] [Route("api/[controller]")] public class PostsController : ControllerBase { private readonly IPostsDataSource _postsDataSource; public PostsController(IPostsDataSource postsDataSource) { _postsDataSource = postsDataSource; } [HttpGet] public ActionResult<List<Post>> GetPosts() { return _postsDataSource.GetAllPosts(); } [HttpGet("{id}")] public ActionResult<Post> GetPost(int id) { var post = _postsDataSource.GetPost(id); if (post == null) { return NotFound(); } return Ok(post); } [HttpDelete("{id}")] public ActionResult DeletePost(int id) { var deleted = _postsDataSource.DeletePost(id); if (deleted) { return Ok(); } return NotFound(); } [HttpPost] public ActionResult<Post> CreatePost([FromBody]Post post) { post = _postsDataSource.AddPost(post); return CreatedAtRoute(nameof(GetPost), new { post.Id }, post); } [HttpPut("{id}")] public ActionResult<Post> UpdatePost(int id, [FromBody]Post post) { var updated = _postsDataSource.UpdatePost(id, post); if (updated) { return Ok(post); } return NotFound(); } } }
البته نمایش فرمت شدهی JSON در مرورگر کروم، نیاز به نصب این افزونه را دارد.
ایجاد ساختار ابتدایی برنامهی ارتباط با سرور
در اینجا برای بررسی کار با سرور، یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app sample-22-frontend > cd sample-22-frontend > npm start
> npm install --save bootstrap
import "bootstrap/dist/css/bootstrap.css";
سپس فایل app.js را به شکل زیر تکمیل میکنیم:
import "./App.css"; import React, { Component } from "react"; class App extends Component { state = { posts: [] }; handleAdd = () => { console.log("Add"); }; handleUpdate = post => { console.log("Update", post); }; handleDelete = post => { console.log("Delete", post); }; render() { return ( <React.Fragment> <button className="btn btn-primary mt-1 mb-1" onClick={this.handleAdd}> Add </button> <table className="table"> <thead> <tr> <th>Title</th> <th>Update</th> <th>Delete</th> </tr> </thead> <tbody> {this.state.posts.map(post => ( <tr key={post.id}> <td>{post.title}</td> <td> <button className="btn btn-info btn-sm" onClick={() => this.handleUpdate(post)} > Update </button> </td> <td> <button className="btn btn-danger btn-sm" onClick={() => this.handleDelete(post)} > Delete </button> </td> </tr> ))} </tbody> </table> </React.Fragment> ); } } export default App;
نگاهی به انواع و اقسام HTTP Clientهای مهیا
در ادامه نیاز خواهیم داشت تا از طریق برنامههای React خود، درخواستهای HTTP را به سمت سرور (یا همان برنامهی backend) ارسال کنیم، تا بتوان اطلاعاتی را از آن دریافت کرد و یا تغییری را در اطلاعات موجود، ایجاد نمود. همانطور که پیشتر نیز در این سری عنوان شد، React برای این مورد نیز راهحل توکاری را به همراه ندارد و تنها کار آن، رندر کردن View و مدیریت DOM است. البته شاید این مورد یکی از مزایای کار با React نیز باشد! چون در این حالت میتوانید از کتابخانههایی که خودتان ترجیح میدهید، نسبت به کتابخانههایی که به شما ارائه/تحمیل (!) میشوند (مانند برنامههای Angular) آزادی انتخاب کاملی را داشته باشید. برای مثال هرچند Angular به همراه یک HTTP Module توکار است، اما تاکنون چندین بار بازنویسی از ابتدا شدهاست! ابتدا با یک کتابخانهی HTTP مقدماتی شروع کردند. بعدی آنرا منسوخ شده اعلام و با یک ماژول جدید جایگزین کردند. بعد در نگارشی دیگر، چون این کتابخانه وابستهاست به RxJS و خود RxJS نیز بازنویسی کامل شد، روش کار کردن با این HTTP Module نیز مجددا تغییر پیدا کرد! بنابراین اگر با Angular کار میکنید، باید کارها را آنگونه که Angular میپسندد، انجام دهید؛ اما در اینجا خیر و آزادی انتخاب کاملی برقرار است.
بنابراین اکنون این سؤال مطرح میشود که در React، برای برقراری ارتباط با سرور، چه باید کرد؟ در اینجا آزاد هستید برای مثال از Fetch API جدید مرورگرها و یا روش Ajax ای مبتنی بر XML قدیمیتر آنها، استفاده کنید (اطلاعات بیشتر) و یا حتی اگر علاقمند باشید میتوانید از محصور کنندههای آن مانند jQuery Ajax استفاده کنید. بنابراین اگر با jQuery Ajax راحت هستید، به سادگی میتوانید از آن در برنامههای React نیز استفاده کنید. اما ... ما در اینجا از یک کتابخانهی بسیار محبوب و قدرتمند HTTP Client، به نام Axios (اکسیوس/ یک واژهی یونانی به معنای «سودمند») استفاده خواهیم کرد که فقط تعداد بار دانلود هفتگی آن، 6 میلیون بار است!
نصب Axios در برنامهی React این قسمت
برای نصب کتابخانهی Axios، در ریشهی پروژهی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-22-frontend-part-01.zip و sample-22-backend-part-01.zip
Serialization #1
- انتقال اشیاء از طریق یک شبکه یا مرز یک نرم افزار .
- ذخیره اشیاء در یک فایل یا بانک اطلاعاتی.
- سریالیکننده قرارداد داده (Data Contract Serializer)
- سریالیکننده باینری
- سریالیکننده XML مبتنی بر صفت
- سریالیکننده اینترفیس IXmlSerializer
- DataContractSerializer، که اصطلاحاً loosely Coupled شده است به نوع سریالیکننده
Data Contract.
- NetDataContractSerializer که اصطلاحاً tightly Coupled شده است به نوع سریالیکننده
Data Contract.
public class News { public int Id; public string Body; public DateTime NewsDate; }
- فضای نام System.Runtime.Serialization را به کد برنامه اضافه کنیم.
- صفت [DataContract] را به نوعی که تعریف کردهایم، اضافه کنیم.
- صفت [DataMember] را به اعضایی که میخواهیم در سریالی شدن شرکتکنند، اضافه کنیم.
using System.Runtime.Serialization; using System.Xml; using System; using System.IO; namespace ConsoleApplication1 { [DataContract] public class News { [DataMember] public int Id; [DataMember] public string Body; [DataMember] public DateTime NewsDate; } }
var news = new News { Id = 1, Body = "NewsBody", NewsDate = new DateTime(2012, 10, 4) }; var ds = new DataContractSerializer(typeof (News)); using (Stream s=File.Create("News.Xml")) { ds.WriteObject(s, news);//سریالیکردن }
<News xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication7" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Body>NewsBody</Body><Id>1</Id><NewsDate>2012-10-04T00:00:00</NewsDate></News>
News deserializednews; using (Stream s = File.OpenRead("News.Xml")) { deserializednews = (News)ds.ReadObject(s);//Deserializing } Console.WriteLine(deserializednews.Body);
XmlWriterSettings settings = new XmlWriterSettings {Indent = true}; using (XmlWriter writer = XmlWriter.Create("News.Xml", settings)) { ds.WriteObject(writer, news); } System.Diagnostics.Process.Start("News.Xml");
<?xml version="1.0" encoding="utf-8"?> <News xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication7"> <Body>NewsBody</Body> <Id>1</Id> <NewsDate>2012-10-04T00:00:00</NewsDate> </News>
CoffeeScript #6
Classes
Inheritance & Super
شما میتوانید به راحتی از کلاسهای دیگری که نوشتهاید، با استفاده از کلمهی کلیدی ،extends ارث بری کنید:
class Animal constructor: (@name) -> alive: -> true class Parrot extends Animal constructor: -> super("Parrot") dead: -> not @alive()
همانطوری که در مثال بالا مشاهده میکنید، در کلاس Parrot در تابع constructor، تابع super فراخوانی شده است. با استفاده از کلمهی کلیدی super میتوان تابع سازندهی کلاس پدر را فراخوانی کرد. نتیجهی کامپایل super در مثال بالا به این صورت میشود:
Parrot.__super__.constructor.call(this, "Parrot");
در صورتیکه تابع constructor را در کلاس فرزند ننوشته باشید، به طور پیش فرض CoffeeScript سازنده کلاس پدر را فراخوانی میکند.
CoffeeScript با استفاده از prototypal inheritance، به صورت خودکار تمامی خصوصیات کلاس پدر، به فرزندان انتقال پیدا میکند. این ویژگی سبب داشتن کلاسهای پویا میشود. برای درک بهتر این موضوع، فرض کنید که خصوصیتی را به کلاس پدر بعد از ارث بری کلاس فرزند اضافه میکنید. خصوصیت اضافه شده به تمامی فرزندان کلاس پدر به صورت خودکار اضافه میشود.
class Animal constructor: (@name) -> class Parrot extends Animal Animal::rip = true parrot = new Parrot("Macaw") alert("This parrot is no more") if parrot.rip
Mixins
Mixins توسط CoffeeScript پشتیبانی نمیشود و برای همین نیاز است که این قابلیت را برای خودمان پیاده سازی کنیم، به مثال زیر توجه کنید.extend = (obj, mixin) -> obj[name] = method for name, method of mixin obj include = (klass, mixin) -> extend klass.prototype, mixin # Usage include Parrot, isDeceased: true alert (new Parrot).isDeceased
var extend, include; extend = function(obj, mixin) { var method, name; for (name in mixin) { method = mixin[name]; obj[name] = method; } return obj; }; include = function(klass, mixin) { return extend(klass.prototype, mixin); }; include(Parrot, { isDeceased: true }); alert((new Parrot).isDeceased);
Extending classes
Mixins خیلی مرتب و خوب است اما خیلی شیء گرا نیست؛ در عوض امکان ادغام را در کلاسهای CoffeeScript ایجاد میکند. برای اینکه اصول شیء گرایی را بخواهیم رعایت کنیم و ویژگی ادغام را نیز داشته باشیم، کلاسی با نام Module را پیاده سازی میکنیم و تمامی کلاسهایی را که میخواهیم ویژگی ادغام را داشته باشند، از آن ارث بری میکنیم.
moduleKeywords = ['extended', 'included'] class Module @extend: (obj) -> for key, value of obj when key not in moduleKeywords @[key] = value obj.extended?.apply(@) this @include: (obj) -> for key, value of obj when key not in moduleKeywords # Assign properties to the prototype @::[key] = value obj.included?.apply(@) this
classProperties = find: (id) -> create: (attrs) -> instanceProperties = save: -> class User extends Module @extend classProperties @include instanceProperties # Usage: user = User.find(1) user = new User user.save()
همچنین برای خلاصه نویسی بیشتر میتوان از این الگو استفاده کرد (ساده و زیبا).
ORM = find: (id) -> create: (attrs) -> extended: -> @include save: -> class User extends Module @extend ORM