اشتراکها
نظرات مطالب
آشنایی با Refactoring - قسمت 4
Attached Properties در WPF در حقیقت یک نوع تزریق شیء به شیء مورد است. شما خواص شیءایی را به شیء دیگر تزریق میکنید. مثلا یک دکمه دارید، سپس Canvas.Top یا Grid.Row به آن متصل میکنید، علت هم این است که اگر قرار بود از روز اول برای یک دکمه تمام این خواص را تعریف کنند، باید بینهایت خاصیت تعریف میکردند؛ چون WPF قابل توسعه است و میشود layout panel سفارشی هم طراحی کرد. البته این تازه یک مورد از کاربردهای این مبحث است.
به صورت خلاصه، Attached Properties ، کپسوله سازی شیء جاری را زیر سؤال نمیبرد. (موضوع اصلی بحث جاری)
هر چند با استفاده از Attached Properties میتوان به تمام خواص و کلیه رویدادهای شیء تزریق شده به آن هم دسترسی پیدا کرد. اینجا هم باز هم برخلاف بحث جاری ما نیست؛ چون اساسا این شیء الحاقی یا ضمیمه شده، نهایتا با شیء جاری از دید سیستم یکپارچه به نظر میرسد. این تزریق هم به همین دلیل صورت گرفته. بنابراین در اینجا هم دسترسی یا تغییر خواص شیء ضمیمه شده، خلاف مقررات شیءگرایی و کپسوله سازی نیست. چون ما در اساس داریم راجع به مثلا یک دکمه صحبت میکنیم. اگر خاصیتی هم به آن تزریق شده باز هم نهایتا جزو خواص همان دکمه در نظر گرفته میشود.
به صورت خلاصه، Attached Properties ، کپسوله سازی شیء جاری را زیر سؤال نمیبرد. (موضوع اصلی بحث جاری)
هر چند با استفاده از Attached Properties میتوان به تمام خواص و کلیه رویدادهای شیء تزریق شده به آن هم دسترسی پیدا کرد. اینجا هم باز هم برخلاف بحث جاری ما نیست؛ چون اساسا این شیء الحاقی یا ضمیمه شده، نهایتا با شیء جاری از دید سیستم یکپارچه به نظر میرسد. این تزریق هم به همین دلیل صورت گرفته. بنابراین در اینجا هم دسترسی یا تغییر خواص شیء ضمیمه شده، خلاف مقررات شیءگرایی و کپسوله سازی نیست. چون ما در اساس داریم راجع به مثلا یک دکمه صحبت میکنیم. اگر خاصیتی هم به آن تزریق شده باز هم نهایتا جزو خواص همان دکمه در نظر گرفته میشود.
مطالب
ایجاد «خواص الحاقی»
حتما با متدهای الحاقی یا Extension methods آشنایی دارید؛ میتوان به یک شیء، که حتی منبع آن در دسترس ما نیست، متدی را اضافه کرد. سؤال: در مورد خواص چطور؟ آیا میشود به وهلهای از یک شیء موجود از پیش طراحی شده، یک خاصیت جدید را اضافه کرد؟
احتمالا شاید عنوان کنید که با اشیاء dynamic میتوان چنین کاری را انجام داد. اما سؤال در مورد اشیاء غیر dynamic است.
یا نمونهی دیگر آن Attached Properties در برنامههای مبتنی بر Xaml هستند. میتوان به یک شیء از پیش موجود Xaml، خاصیتی را افزود که البته پیاده سازی آن منحصر است به همان نوع برنامهها.
راه حل پیشنهادی
یک Dictionary را ایجاد کنیم تا ارجاعی از اشیاء، به عنوان کلید، در آن ذخیره شده و سپس key/valueهایی به عنوان value هر شیء، در آن ذخیره شوند. این key/valueها همان خواص و مقادیر آنها خواهند بود. هر چند این راه حل به خوبی کار میکند اما ... مشکل نشتی حافظه دارد.
شیء Dictionary یک ارجاع قوی را از اشیاء، درون خودش نگه داری میکند و تا زمانیکه در حافظه باقی است، سیستم GC مجوز رهاسازی منابع آنها را نخواهد یافت؛ چون عموما این نوع Dictionaryها باید استاتیک تعریف شوند تا طول عمر آنها با طول عمر برنامه یکی گردد. بنابراین اساسا اشیایی که به این نحو قرار است پردازش شوند، هیچگاه dispose نخواهند شد. راه حلی برای این مساله در دات نت 4 به صورت توکار به دات نت فریم ورک اضافه شدهاست؛ به نام ساختار دادهای ConditionalWeakTable.
معرفی ConditionalWeakTable
ConditionalWeakTable جزو ساختارهای دادهای کمتر شناخته شدهی دات نت است. این ساختار داده، اشارهگرهایی را به ارجاعات اشیاء، درون خود ذخیره میکند. بنابراین چون ارجاعاتی قوی را به اشیاء ایجاد نمیکند، مانع عملکرد GC نیز نشده و برنامه در دراز مدت دچار مشکل نشتی حافظه نخواهد شد. هدف اصلی آن ایجاد ارتباطی بین CLR و DLR است. توسط آن میتوان به اشیاء دلخواه، خواصی را افزود. به علاوه طراحی آن به نحوی است که thread safe است و مباحث قفل گذاری بر روی اطلاعات، به صورت توکار در آن پیاده سازی شدهاست. کار DLR فراهم آوردن امکان پیاده سازی زبانهای پویایی مانند Ruby و Python برفراز CLR است. در این نوع زبانها میتوان به وهلههایی از اشیاء موجود، خاصیتهای جدیدی را متصل کرد.
به صورت خلاصه کار ConditionalWeakTable ایجاد نگاشتی است بین وهلههایی از اشیاء CLR (اشیایی غیرپویا) و خواصی که به آنها میتوان به صورت پویا انتساب داد. در کار GC اخلال ایجاد نمیکند و همچنین میتوان به صورت همزمان از طریق تردهای مختلف، بدون مشکل با آن کار کرد.
پیاده سازی خواص الحاقی به کمک ConditionalWeakTable
در اینجا نحوهی استفاده از ConditionalWeakTable را جهت اتصال خواصی جدید به وهلههای موجود اشیاء مشاهده میکنید:
ObjectCache تعریف شده از نوع استاتیک است؛ بنابراین در طول عمر برنامه زنده نگه داشته خواهد شد، اما اشیایی که به آن منتسب میشوند، خیر. هرچند به ظاهر در متد GetOrCreateValue، یک وهله از شیءایی موجود را دریافت میکند، اما در پشت صحنه صرفا IntPtr یا اشارهگری به این شیء را ذخیره سازی خواهد کرد. به این ترتیب در کار GC اخلالی صورت نخواهد گرفت و شیء مورد نظر، تا پایان کار برنامه به اجبار زنده نگه داشته نخواهد شد.
کاربرد اول
اگر با ASP.NET کار کرده باشید حتما با IPrincipal آشنایی دارید. خواصی مانند Identity یک کاربر در آن ذخیره میشوند.
سؤال: چگونه میتوان یک خاصیت جدید به نام مثلا Disclaimer را به وهلهای از این شیء افزود:
در اینجا مثالی را از کاربرد کلاس AttachedProperties فوق مشاهده میکنید. توسط متد SetDisclaimer یک خاصیت جدید به نام Disclaimer به وهلهای از شیءایی از نوع IPrincipal قابل اتصال است. سپس توسط متد Disclaimer قابل دستیابی خواهد بود.
اگر صرفا قرار است یک خاصیت به شیءایی متصل شود، روش ذیل نیز قابل استفاده میباشد (بجای استفاده از دیکشنری از یک کلاس جهت تعریف خاصیت اضافی جدید استفاده شدهاست):
کاربرد دوم
ایجاد Id منحصربفرد برای اشیاء برنامه.
فرض کنید در حال نوشتن یک Entity framework profiler هستید. طراحی فعلی سیستم Interception آن به نحو زیر است:
سؤال: اینجا رویداد بسته شدن یک اتصال را دریافت میکنیم؛ اما ... دقیقا کدام اتصال؟ رویداد Opened را هم داریم اما چگونه این اشیاء را به هم مرتبط کنیم؟ شیء DbConnection دارای Id نیست. متد GetHashCode هم الزامی ندارد که اصلا پیاده سازی شده باشد یا حتی یک Id منحصربفرد را تولید کند. این متد با تغییر مقادیر خواص یک شیء میتواند مقادیر متفاوتی را ارائه دهد. در اینجا میخواهیم به ازای ارجاعی از یک شیء، یک Id منحصربفرد داشته باشیم تا بتوانیم تشخیص دهیم که این اتصال بسته شده، دقیقا کدام اتصال باز شدهاست؟
راه حل: خوب ... یک خاصیت Id را به اشیاء موجود متصل کنید!
در اینجا مثالی دیگر از پیاده سازی و استفاده از ConditionalWeakTable را ملاحظه میکنید. اگر در کش آن ارجاعی به شیء مورد نظر وجود داشته باشد، مقدار Guid آن بازگشت داده میشود؛ اگر خیر، یک Guid به ارجاعی از شیء، انتساب داده شده و سپس بازگشت داده میشود. به عبارتی به صورت پویا یک خاصیت UniqueId به وهلههایی از اشیاء اضافه میشوند. به این ترتیب به سادگی میتوان آنها را ردیابی کرد و تشخیص داد که اگر این Guid پیشتر جایی به اتصال باز شدهای منتسب شدهاست، در چه زمانی و در کجا بسته شده است یا اصلا ... خیر. جایی بسته نشدهاست.
برای مطالعه بیشتر
The Conditional Weak Table: Enabling Dynamic Object Properties
How to create mixin using C# 4.0
Disclaimer Page using MVC
Extension Properties Revised
Easy Modeling
Providing unique ID on managed object using ConditionalWeakTable
احتمالا شاید عنوان کنید که با اشیاء dynamic میتوان چنین کاری را انجام داد. اما سؤال در مورد اشیاء غیر dynamic است.
یا نمونهی دیگر آن Attached Properties در برنامههای مبتنی بر Xaml هستند. میتوان به یک شیء از پیش موجود Xaml، خاصیتی را افزود که البته پیاده سازی آن منحصر است به همان نوع برنامهها.
راه حل پیشنهادی
یک Dictionary را ایجاد کنیم تا ارجاعی از اشیاء، به عنوان کلید، در آن ذخیره شده و سپس key/valueهایی به عنوان value هر شیء، در آن ذخیره شوند. این key/valueها همان خواص و مقادیر آنها خواهند بود. هر چند این راه حل به خوبی کار میکند اما ... مشکل نشتی حافظه دارد.
شیء Dictionary یک ارجاع قوی را از اشیاء، درون خودش نگه داری میکند و تا زمانیکه در حافظه باقی است، سیستم GC مجوز رهاسازی منابع آنها را نخواهد یافت؛ چون عموما این نوع Dictionaryها باید استاتیک تعریف شوند تا طول عمر آنها با طول عمر برنامه یکی گردد. بنابراین اساسا اشیایی که به این نحو قرار است پردازش شوند، هیچگاه dispose نخواهند شد. راه حلی برای این مساله در دات نت 4 به صورت توکار به دات نت فریم ورک اضافه شدهاست؛ به نام ساختار دادهای ConditionalWeakTable.
معرفی ConditionalWeakTable
ConditionalWeakTable جزو ساختارهای دادهای کمتر شناخته شدهی دات نت است. این ساختار داده، اشارهگرهایی را به ارجاعات اشیاء، درون خود ذخیره میکند. بنابراین چون ارجاعاتی قوی را به اشیاء ایجاد نمیکند، مانع عملکرد GC نیز نشده و برنامه در دراز مدت دچار مشکل نشتی حافظه نخواهد شد. هدف اصلی آن ایجاد ارتباطی بین CLR و DLR است. توسط آن میتوان به اشیاء دلخواه، خواصی را افزود. به علاوه طراحی آن به نحوی است که thread safe است و مباحث قفل گذاری بر روی اطلاعات، به صورت توکار در آن پیاده سازی شدهاست. کار DLR فراهم آوردن امکان پیاده سازی زبانهای پویایی مانند Ruby و Python برفراز CLR است. در این نوع زبانها میتوان به وهلههایی از اشیاء موجود، خاصیتهای جدیدی را متصل کرد.
به صورت خلاصه کار ConditionalWeakTable ایجاد نگاشتی است بین وهلههایی از اشیاء CLR (اشیایی غیرپویا) و خواصی که به آنها میتوان به صورت پویا انتساب داد. در کار GC اخلال ایجاد نمیکند و همچنین میتوان به صورت همزمان از طریق تردهای مختلف، بدون مشکل با آن کار کرد.
پیاده سازی خواص الحاقی به کمک ConditionalWeakTable
در اینجا نحوهی استفاده از ConditionalWeakTable را جهت اتصال خواصی جدید به وهلههای موجود اشیاء مشاهده میکنید:
using System.Collections.Generic; using System.Runtime.CompilerServices; namespace ConditionalWeakTableSamples { public static class AttachedProperties { public static ConditionalWeakTable<object, Dictionary<string, object>> ObjectCache = new ConditionalWeakTable<object, Dictionary<string, object>>(); public static void SetValue<T>(this T obj, string name, object value) where T : class { var properties = ObjectCache.GetOrCreateValue(obj); if (properties.ContainsKey(name)) properties[name] = value; else properties.Add(name, value); } public static T GetValue<T>(this object obj, string name) { Dictionary<string, object> properties; if (ObjectCache.TryGetValue(obj, out properties) && properties.ContainsKey(name)) return (T)properties[name]; return default(T); } public static object GetValue(this object obj, string name) { return obj.GetValue<object>(name); } } }
کاربرد اول
اگر با ASP.NET کار کرده باشید حتما با IPrincipal آشنایی دارید. خواصی مانند Identity یک کاربر در آن ذخیره میشوند.
سؤال: چگونه میتوان یک خاصیت جدید به نام مثلا Disclaimer را به وهلهای از این شیء افزود:
public static class ISecurityPrincipalExtension { public static bool Disclaimer(this IPrincipal principal) { return principal.GetValue<bool>("Disclaimer"); } public static void SetDisclaimer(this IPrincipal principal, bool value) { principal.SetValue("Disclaimer", value); } }
اگر صرفا قرار است یک خاصیت به شیءایی متصل شود، روش ذیل نیز قابل استفاده میباشد (بجای استفاده از دیکشنری از یک کلاس جهت تعریف خاصیت اضافی جدید استفاده شدهاست):
using System.Runtime.CompilerServices; namespace ConditionalWeakTableSamples { public static class PropertyExtensions { private class ExtraPropertyHolder { public bool IsDirty { get; set; } } private static readonly ConditionalWeakTable<object, ExtraPropertyHolder> _isDirtyTable = new ConditionalWeakTable<object, ExtraPropertyHolder>(); public static bool IsDirty(this object @this) { return _isDirtyTable.GetOrCreateValue(@this).IsDirty; } public static void SetIsDirty(this object @this, bool isDirty) { _isDirtyTable.GetOrCreateValue(@this).IsDirty = isDirty; } } }
کاربرد دوم
ایجاد Id منحصربفرد برای اشیاء برنامه.
فرض کنید در حال نوشتن یک Entity framework profiler هستید. طراحی فعلی سیستم Interception آن به نحو زیر است:
public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext) { }
راه حل: خوب ... یک خاصیت Id را به اشیاء موجود متصل کنید!
using System; using System.Runtime.CompilerServices; namespace ConditionalWeakTableSamples { public static class UniqueIdExtensions { static readonly ConditionalWeakTable<object, string> _idTable = new ConditionalWeakTable<object, string>(); public static string GetUniqueId(this object obj) { return _idTable.GetValue(obj, o => Guid.NewGuid().ToString()); } public static string GetUniqueId(this object obj, string key) { return _idTable.GetValue(obj, o => key); } } }
برای مطالعه بیشتر
The Conditional Weak Table: Enabling Dynamic Object Properties
How to create mixin using C# 4.0
Disclaimer Page using MVC
Extension Properties Revised
Easy Modeling
Providing unique ID on managed object using ConditionalWeakTable
return Ok نوشته شده محدودیتی ندارد؛ چون در پشت صحنه از کتابخانهی Json.NET استفاده میکند و قادر هست با انواع و اقسام اشیاء پیچیده کار کند و آنها را serialize کند. جهت سادگی کار در اینجا از یک anonymous object استفاده شدهاست. آنرا با هر شیء دیگری و با هر ساختاری که علاقمندید تعویض کنید. هر نوع شیءایی را در اینجا میتوان ذکر و یا اضافه کرد:
public IHttpActionResult Get() { // Anonymous and Weakly-Typed Objects return Ok(new { Id = 1, Title = "Hello from My Protected Controller!", Username = this.User.Identity.Name, Property2 = new Object1 { id = 1, name = "V" } }); // OR ... Strongly-Typed Objects return Ok(model); }
این ماژول یکی از معروفترین و پرسرعتترین کتابخانهها به منظور تغییر ابعاد تصاویر برای Node.js است.
این ماژول چیزی در حدود 4 الی 5 برابر، در سرعت تغییر ابعاد تصاویر تاثیر دارد و افزایش چشمگیر آن قابل لمس میباشد.
مطالب
Iterators در ES 6
یکی از اهداف ES 6، استاندارد سازی کار با Iterators و Iterables است. فرض کنید شیءایی را داریم که مجموعهای از عناصر را در بر دارد. این مجموعه میتواند آرایهای از عناصر باشد و یا set و map اضافه شده به ES 6 و یا حتی اشیایی که در زمان اجرا ایجاد میشوند. اگر این مجموعه Iterable باشد، حرکت بر روی آن یک Iterator را تولید میکند که امکان حرکت در این مجموعه را آیتم به آیتم میسر خواهد کرد:
هر Iterator شیءایی است که دارای متد next میباشد. هر بار که این متد فراخوانی میشود، عضو بعدی مجموعه، بازگشت داده خواهد شد. خروجی هر مرحله، درون یک شیء با دو خاصیت value و done قرار داده میشود. value، مقدار مرحلهی بعد است و done مشخص میکند که آیا به پایان مجموعه رسیدهایم یا خیر (بنابراین در اینجا تعداد اعضای Iterator مشخص نیست).
مثالی از پیمایش یک آرایه با چندین روش مختلف
در مثال زیر، آرایهای از اعداد را داریم که نیاز است جمع اعضای آن محاسبه شود:
اینکار را میتوان با استفاده از یک حلقهی for متداول انجام داد. از آنجائیکه آرایه دارای خاصیت length است، میتوان از آن جهت تعیین حد بالایی آرایه استفاده کرد:
روش دوم انجام اینکار، استفاده از حلقهی for in است. این حلقه هربار ایندکس بعدی قابل استفادهی آرایه را بازگشت میدهد که از آن میتوان جهت دسترسی به اعضای آرایه استفاده کرد:
روش دیگر انجام اینکار با استفاده از Iterators است:
برای کار با Iterators، ابتدا باید یک شیء Iterator را از مجموعهایی که در اختیار داریم، تولید کنیم. برای مثال آرایهها دارای متدی به نام values هستند که با فراخوانی ()number.values، سبب تولید یک Iterator میشوند. این Iterator امکان حرکت بین مقادیر آرایه را در اینجا میسر میکند.
مرحلهی بعدی، فراخوانی متد next این Iterator است. این عملیات باید در طی یک حلقه، تا پایان کار Iterator انجام شود. همانطور که در ابتدای بحث نیز عنوان شد، خروجی متد next یک شیء است که دارای خواص value و done میباشد. اگر done مساوی true شد، یعنی به پایان کار پیمایش رسیدهایم.
البته هدف از این مثال، صرفا نمایش سطح پایین کار با Iterators بود. در عمل از حلقهی جدیدی به نام for of برای انجام این پیمایش استفاده میشود.
معرفی حلقهی for of
جاوا اسکریپت سالها است که دارای حلقهی for in میباشد و نمونهای از کاربرد آنرا در مثال قبل مشاهده کردید. اگر این حلقه بر روی آرایهها فراخوانی شود، هربار ایندکس پیمایش شده را بازگشت میدهد و اگر بر روی یک شیء فراخوانی شود، خواص آن شیء را بازگشت میدهد:
در این مثال، i بار اول به first و بار دوم به خاصیت last اشاره میکند.
چون این حلقه صرفا ایندکسها و کلیدها را بازگشت میدهد، جهت کار با Iterators که نیاز است به مقادیر اعضاء دسترسی پیدا کنیم، مناسب نیست. به همین جهت در ES 6، حلقهی جدیدی به نام for of برای کار با Iterators معرفی شدهاست:
این حلقه برخلاف for in، بر روی values، کار پیمایش را انجام میدهد. همچنین به صورت خودکار در پشت صحنه متد next یک Iterator را فراخوانی میکند و خاصیت done آنرا بررسی کرده و زمانیکه این خاصیت مساوی true شد، حلقه را خاتمه میدهد. برای نمونه مثال سطح پایین while دار ابتدای بحث را به نحو سادهتری با حلقهی for of ذیل میتوان جایگزین کرد:
for of یکی از روشهای پیمایش Iterators است. پارامترهای rest و همچنین Array.from نیز چنین قابلیتی را فراهم میکنند.
امکان پیاده سازی Iterators سفارشی نیز وجود دارد که پیشنیاز آن، درک مبحث جدید Symbols است که به صورت جداگانهای بررسی خواهد شد.
هر Iterator شیءایی است که دارای متد next میباشد. هر بار که این متد فراخوانی میشود، عضو بعدی مجموعه، بازگشت داده خواهد شد. خروجی هر مرحله، درون یک شیء با دو خاصیت value و done قرار داده میشود. value، مقدار مرحلهی بعد است و done مشخص میکند که آیا به پایان مجموعه رسیدهایم یا خیر (بنابراین در اینجا تعداد اعضای Iterator مشخص نیست).
مثالی از پیمایش یک آرایه با چندین روش مختلف
در مثال زیر، آرایهای از اعداد را داریم که نیاز است جمع اعضای آن محاسبه شود:
let sum = 0; let numbers = [1,2,3,4];
// for loop sum = 0; for(let i =0; i < numbers.length; i++){ sum += numbers[i]; } //sum = 10
// for in sum = 0; for(let i in numbers) { sum += numbers[i]; } //sum = 10
// iterator sum = 0; let iterator = number.values(); let next = iterator.next(); while(!next.done){ sum += next.value; next = iterator.next(); } //sum = 10
مرحلهی بعدی، فراخوانی متد next این Iterator است. این عملیات باید در طی یک حلقه، تا پایان کار Iterator انجام شود. همانطور که در ابتدای بحث نیز عنوان شد، خروجی متد next یک شیء است که دارای خواص value و done میباشد. اگر done مساوی true شد، یعنی به پایان کار پیمایش رسیدهایم.
البته هدف از این مثال، صرفا نمایش سطح پایین کار با Iterators بود. در عمل از حلقهی جدیدی به نام for of برای انجام این پیمایش استفاده میشود.
معرفی حلقهی for of
جاوا اسکریپت سالها است که دارای حلقهی for in میباشد و نمونهای از کاربرد آنرا در مثال قبل مشاهده کردید. اگر این حلقه بر روی آرایهها فراخوانی شود، هربار ایندکس پیمایش شده را بازگشت میدهد و اگر بر روی یک شیء فراخوانی شود، خواص آن شیء را بازگشت میدهد:
var person = { first: "Vahid", last "N" }; for(let i in person) { console.log(person[i]); }
چون این حلقه صرفا ایندکسها و کلیدها را بازگشت میدهد، جهت کار با Iterators که نیاز است به مقادیر اعضاء دسترسی پیدا کنیم، مناسب نیست. به همین جهت در ES 6، حلقهی جدیدی به نام for of برای کار با Iterators معرفی شدهاست:
let numbers = [1,2,3,4]; for(let i of numbers) { console.log(i); }
let sum = 0; let numbers = [1,2,3,4]; for(let n of numbers){ sum += n; }
for of یکی از روشهای پیمایش Iterators است. پارامترهای rest و همچنین Array.from نیز چنین قابلیتی را فراهم میکنند.
امکان پیاده سازی Iterators سفارشی نیز وجود دارد که پیشنیاز آن، درک مبحث جدید Symbols است که به صورت جداگانهای بررسی خواهد شد.
اینترفیس، مانند قراردادی است که یک نوع را تعریف میکند. کامپایلر از اینترفیسها جهت بررسی نوعها و اجبار به رعایت قرارداد استفاده میکند. در این حالت اگر متدها یا خواص معرفی شدهی در نوع اینترفیس، توسط استفاده کننده بکار گرفته نشوند، خطایی توسط کامپایلر گزارش خواهد شد.
از آنجائیکه اینترفیسها به معنای نوعهای سفارشی هستند و جاوا اسکریپت از آنها پشتیبانی نمیکند، توسط کامپایلر TypeScript، به هیچ نوع کد معادلی در جاوا اسکریپت، ترجمه و تبدیل نخواهند شد. کامپایلر TypeScript تنها از آنها جهت بررسی نوعها استفاده میکند.
اینترفیسها به صورت مجموعهای از تعاریف خواص و متدها، بدون پیاده سازی آنها تعریف میشوند. پیاده سازی این اینترفیسها، توسط کلاسها و یا سایر اشیاء صورت خواهند گرفت. برای مثال یک قرارداد اجاره، مشخص میکند که آخر هر ماه چه مقداری را باید پرداخت کرد. اما این قرار داد مشخص نمیکند که چگونه باید این پرداخت صورت گیرد و از هر شخصی به شخص دیگری میتواند متفاوت باشد. به این حالت duck typing هم میگویند. به این معنا که قرار داد، شکل یک شیء را مشخص میکند و تا زمانیکه پیاده سازی کنندهی آن بتواند این قرارداد را تامین کند، میتواند بجای نوع اصلی نیز بکار گرفته شود.
Duck typing چیست؟
duck typing به این معنا است که اگر پرندهای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده میشود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته میشود. برای نمونه به مثال ذیل دقت کنید:
در این مثال اینترفیس Duck، متدهایی را تعریف کردهاست که یک Duck میتواند انجام دهد.
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شدهاست که همان متدهای اینترفیس Duck را پیاده سازی میکند و امضای آنها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شدهاست.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شدهاست و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کردهاست، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing میگویند.
نحوهی تعریف یک اینترفیس در TypeScript
تعریف یک اینترفیس با واژهی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آنها تعریف خواهند شد:
در این مثال خواص id، title و author اجباری هستند و پیاده سازی کننده موظف است آنها را به همراه داشته باشد.
در اینترفیسهای TypeScript میتوان خواص اختیاری و optional را نیز تعریف کرد. نمونهی آن خاصیت pages در این مثال است که با ? مشخص شدهاست و نمونهی آنرا در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی انجام میشود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.
استفاده از اینترفیسها برای تعریف نوع خروجی توابع
در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوعهای رشتهای و عددی است و خروجی آن نیز از نوع رشتهای تعریف شدهاست:
در ادامه تعریف متغیری را مشاهده میکنید که نوع آن، متدی است که با امضای متد CreateCustomerID یکسان است:
به این ترتیب امکان انتساب متد CreateCustomerID به متغیر IdGenerator وجود خواهد داشت:
جهت مدیریت بهتر یک چنین تعریفهایی و همچنین امکان استفادهی مجدد از آنها، میتوان از اینترفیسها کمک گرفت:
اینترفیس StringGenerator نام بهتر و با قابلیت استفادهی مجددی را به نوع متدی که قابل انتساب است به متغیر IdGenerator، تعریف میکند. در اینجا syntax تعریف نوع متد، در اینترفیس StringGenerator اندکی با حالتهای قبلی متفاوت است. در اینجا بجای استفاده از <= جهت مشخص کردن نوع خروجی متد، از کولن استفاده شدهاست.
اکنون میتوان نحوهی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
به عنوان نمونه میتوان یک چنین تغییری را در نحوهی تعریف اینترفیس Book ابتدای بحث و تغییر متد markDamaged آن نیز اعمال کرد.
بسط و توسعهی اینترفیسها
بسط و توسعهی اینترفیسها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، میتوان به نوعی مرکب رسید:
در این مثال، ابتدا دو اینترفیس منابع و کتابهای یک کتابخانه تعریف شدهاند. سپس اینترفیس جدیدی به نام Encyclopedia با بسط این دو اینترفیس توسط واژهی کلیدی extends ایجاد شدهاست.
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیسهای ذکر شدهی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
نوع کلاسها
مبحث کلاسها به صورت جداگانهای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشارهی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیسها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف میکنند؛ بدون ارائهی پیاده سازی خاصی از آنها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاسها روش بهتری برای انجام اینکار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه میکنید:
متد doWork دارای پارامتری نیست و خروجی نیز ندارد. سپس با استفاده از واژهی کلیدی class، یک کلاس جدید را ایجاد کردهایم که با استفادهی واژهی کلیدی implements، یک پیاده سازی مشخص از اینترفیس Librarian را ارائه میدهد:
اکنون داخل این کلاس، پیاده سازی خاصی از متد doWork مشخص شدهی در قرارداد و اینترفیس Librarian را مشاهده میکنید.
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل میکنیم:
در اینجا متغیر kidsLibrarian از نوع اینترفیس کتابدار تعریف شدهاست. به این معنا که شیءایی که به آن انتساب داده میشود باید این اینترفیس را پیاده سازی کند. این شیء نیز توسط واژهی کلیدی new، نمونه سازی/وهله سازی میشود. در ادامه میتوان به متدها و خواص شیء kidsLibrarian دسترسی یافت و آنها را فراخوانی کرد.
از آنجائیکه اینترفیسها به معنای نوعهای سفارشی هستند و جاوا اسکریپت از آنها پشتیبانی نمیکند، توسط کامپایلر TypeScript، به هیچ نوع کد معادلی در جاوا اسکریپت، ترجمه و تبدیل نخواهند شد. کامپایلر TypeScript تنها از آنها جهت بررسی نوعها استفاده میکند.
اینترفیسها به صورت مجموعهای از تعاریف خواص و متدها، بدون پیاده سازی آنها تعریف میشوند. پیاده سازی این اینترفیسها، توسط کلاسها و یا سایر اشیاء صورت خواهند گرفت. برای مثال یک قرارداد اجاره، مشخص میکند که آخر هر ماه چه مقداری را باید پرداخت کرد. اما این قرار داد مشخص نمیکند که چگونه باید این پرداخت صورت گیرد و از هر شخصی به شخص دیگری میتواند متفاوت باشد. به این حالت duck typing هم میگویند. به این معنا که قرار داد، شکل یک شیء را مشخص میکند و تا زمانیکه پیاده سازی کنندهی آن بتواند این قرارداد را تامین کند، میتواند بجای نوع اصلی نیز بکار گرفته شود.
Duck typing چیست؟
duck typing به این معنا است که اگر پرندهای بتواند مانند یک اردک راه برود، شنا کند و صدا در بیاورد، یک اردک نامیده میشود. بنابراین همینقدر که یک شیء بتواند قراردادی را پیاده سازی کند، نوع آن با نوع اینترفیس یکی درنظر گرفته میشود. برای نمونه به مثال ذیل دقت کنید:
interface Duck { walk: () => void; swim: () => void; quack: () => void; } let probablyADuck = { walk: () => console.log('walking like a duck'), swim: () => console.log('swimming like a duck'), quack: () => console.log('quacking like a duck') } function FlyOverWater(bird: Duck) { } FlyOverWater(probablyADuck); // works!
در ادامه متغیر و شیءایی بدون تعریف نوع آن ایجاد شدهاست که همان متدهای اینترفیس Duck را پیاده سازی میکند و امضای آنها با امضای متدهای اینترفیس Duck یکی هستند.
سپس متد FlyOverWater تعریف شده که در آن، نوع پارامتر ورودی آن به صورت صریحی به نوع اینترفیس Duck مقید شدهاست.
در سطر بعدی، این متد با دریافت شیء probablyADuck فراخوانی شدهاست و چون این شیء تمام اجزای قرارداد Duck را پیاده سازی کردهاست، مشکلی در اجرای آن نخواهد بود. به این حالت duck typing میگویند.
نحوهی تعریف یک اینترفیس در TypeScript
تعریف یک اینترفیس با واژهی کلیدی interface شروع شده و سپس خواص و متدهای مدنظر این قرارداد، به همراه نوع آنها تعریف خواهند شد:
interface Book { id: number; title: string; author: string; pages?: number; markDamaged: (reason: string) => void; }
در اینترفیسهای TypeScript میتوان خواص اختیاری و optional را نیز تعریف کرد. نمونهی آن خاصیت pages در این مثال است که با ? مشخص شدهاست و نمونهی آنرا در حین تعریف پارامترهای اختیاری متدها نیز پیشتر ملاحظه کرده بودید.
تعریف متدها در یک اینترفیس، با مشخص سازی نام آن متد و ذکر یک کولن و سپس مشخص سازی امضای پارامترهای دریافتی انجام میشود. نوع خروجی متد، در سمت راست علامت <= قرار خواهد گرفت.
استفاده از اینترفیسها برای تعریف نوع خروجی توابع
در مثال زیر، متد CreateCustomerID دارای دو پارامتر ورودی از نوعهای رشتهای و عددی است و خروجی آن نیز از نوع رشتهای تعریف شدهاست:
function CreateCustomerID(name: string, id: number): string { return name + id; }
let IdGenerator: (chars: string, nums: number) => string;
IdGenerator = CreateCustomerID;
interface StringGenerator { (chars: string, nums: number): string; }
اکنون میتوان نحوهی تعریف متغیر IdGenerator را به صورت زیر Refactor کرد و تغییر داد:
let IdGenerator: StringGenerator;
بسط و توسعهی اینترفیسها
بسط و توسعهی اینترفیسها شبیه به مباحث ارث بری هستند. به این ترتیب که با بسط یک اینترفیس از طریق اینترفیسی دیگر، میتوان به نوعی مرکب رسید:
interface LibraryResource { catalogNumber: number; } interface LibraryBook { title: string; } interface Encyclopedia extends LibraryResource, LibraryBook { volume: number; }
این نوع مرکب، علاوه بر دارا بودن خاصیت volume مختص به خودش، اکنون حاوی دو خاصیت موجود در سایر اینترفیسهای ذکر شدهی در قسمت extends نیز هست.
حال اگر متغیر جدیدی را از نوع Encyclopedia تعریف کنیم، جهت برآورده شده تمام اجزای قرارداد، لازم است هر سه خاصیت را مقدار دهی نمائیم:
let refBook: Encyclopedia = { catalogNumber: 1234, title: 'The Book of Everything', volume: 1 }
نوع کلاسها
مبحث کلاسها به صورت جداگانهای در این سری بررسی خواهند شد. اما جهت تکمیل بحث جاری نیاز است اشارهی کوتاهی به آنها شود.
همانطور که عنوان شد، اینترفیسها تنها شکل و قرارداد پیاده سازی یک شیء را تعریف میکنند؛ بدون ارائهی پیاده سازی خاصی از آنها. تا اینجا در بحث جاری، اشیاء را توسط object literals داخل {} تعریف کردیم (مانند متغیر refBook مثال قبل). اما کلاسها روش بهتری برای انجام اینکار و تعریف اشیاء هستند.
در ذیل تعریف اینترفیس کتابدار را با تک متد doWork آن ملاحظه میکنید:
interface Librarian { doWork: () => void; }
class ElementarySchoolLibrarian implements Librarian { doWork() { console.log('Reading to and teaching children...'); } }
در ادامه برای ایجاد شیءایی از روی این تعریف، به نحو ذیل عمل میکنیم:
let kidsLibrarian: Librarian = new ElementarySchoolLibrarian(); kidsLibrarian.doWork();
هنگام کدنویسی زبانهای سمت کاربر (Client Side) اگر به عنوان برنامه نویس و توسعه دهنده ، با کتابخانههای آن آشنا
باشید و آنها را حفظ باشید خیلی خوب است ولی اگر همین کتابخانهها و توابع به ابعاد شیرپوینت گسترش یابد چه؟ مسلما حفظ کردن تمام آنها کار دشواری است ؛ راه حلی که در اینجا ارائه میشود ،بار گذاری این توابع در Intellisense ویژوال استودیو است . برای این کار در صورتی که در حال نوشتن Visual WebPart هستید ، میتوانیدبه روش زیر عمل کنید :
ابتدا بالای صفحه مورد نظر کد زیر را وارد کنید :
<% #if SOME_UNDEFINED_CONSTANT %> <script type="text/javascript" src="/_layouts/MicrosoftAjax.js" ></script> <script type="text/javascript" src="/_layouts/SP.debug.js"></script> <% #endif %>
در این لحظه ویژوال استودیو آغاز به روز رسانی متادیتای Intellisense خود میکند که میتوانید این پیغام را در زیر نوار وضعیت این برنامه مشاهده کنید .
حال میتوانید به راحتی از این امکانات لذت ببرید و به توسعه خود سرعت دهید.
نظرات مطالب
ASP.NET MVC #5
این روزها هیچکدام از فناوریهای دسترسی به داده بدون امکان یکپارچگی آنها با سیستمها و روشهای متفاوت caching ، مطلوب شمرده نمیشوند. ایده اصلی caching هم به زبان ساده به این صورت است : فراهم آوردن روشهایی جهت میسر ساختن دسترسی سریعتر به دادههایی که به صورت متناوب در برنامه مورد استفاده قرار میگیرند، بجای مراجعه مستقیم به بانک اطلاعاتی و خواندن اطلاعات از دیسک سخت.
یکی از تفاوتهای مهم NHibernate با اکثر ORM های موجود داشتن دو سطح متفاوت cache است : first level cache & second level cache .
برای نمونه Entity framework (در زمان نگارش این مطلب) تنها first level caching را پشتیبانی میکند و پروایدر توکار و یکپارچهای را جهت second level caching ارائه نمیدهد.
در این قسمت قصد داریم First Level Cache را بررسی کنیم.
سطح اول caching در NHibernate چیست؟
سطح اول caching در تمام ORM هایی که آنرا پشتیبانی میکنند مانند NHibernate ، در طول عمر یک تراکنش تعریف میگردد. در این حالت در طی یک تراکنش و طول عمر یک سشن، دریافت اطلاعات هر رکورد از بانک اطلاعاتی، تنها یکبار انجام خواهد شد؛ صرفنظر از اینکه کوئری دریافت اطلاعات آن چندبار فراخوانی میگردد. یکی از دلایل این روش هم آن است که هیچ دو شیء متفاوتی که هم اکنون در حافظه قرار دارند نباید بیانگر یک رکورد واحد از بانک اطلاعاتی باشند.
در NHibernate به صورت پیش فرض هر زمانیکه از شیء استاندارد session استفاده میکنید، سطح اول caching نیز فعال است. درست در زمانیکه سشن خاتمه مییابد، این سطح از caching نیز به صورت خودکار تخلیه خواهد گردید.
به first level caching اصطلاحا thought-out cache system یا Cache Through pattern و یا identity map هم گفته میشود.
مثال:
روش متداول و استاندارد کار با NHibernate عموما به صورت زیر است:
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد.
د) سپس مجددا سعی در دریافت رکوردی با ID مساوی یک. اینبار اطلاعات این شیء مستقیما از cache خوانده میشود و رفت و برگشتی به بانک اطلاعاتی نخواهیم داشت. به همین جهت به این روش identity map هم گفته میشود، زیرا NHibernate بر اساس ID منحصربفرد این اشیاء ، identity map خود را تشکیل میدهد.
ه) خاتمهی سشن با فراخوانی متد Close آن
بلافاصله
الف) دریافت شیء Session از Session Factory
ب) شروع یک تراکنش با فراخوانی متد BeginTransaction شیء Session
ج) برای مثال دریافت اطلاعات رکوردی با ID مساوی یک به کمک متد Get مرتبط با شیء Session : این اطلاعات مستقیما از بانک اطلاعاتی دریافت خواهد شد (زیرا در یک سشن جدید قرار داریم و همچنین سشن قبلی بسته شده و کش آن تخلیه گشته است).
د) خاتمهی سشن با فراخوانی متد Close آن
سؤال: آیا استفاده از یک سشن سراسری در برنامه صحیح است؟
پاسخ: خیر!
توضیحات: زمانیکه از یک سشن سراسری استفاده میکنید، کش NHibernate را در اختیار تمام کاربران همزمان سیستم قرار دادهاید. در طی یک سشن، همانطور که عنوان شد، بر اساس IDهای اشیاء، یک identity map تشکیل میشود و در این حالت به ازای هر رکورد بانک اطلاعاتی فقط و فقط یک شیء در حافظه وجود خواهد داشت که این روش در محیطهای چندکاربره مانند برنامههای وب به زودی تبدیل به نشت اطلاعات و یا تخریب اطلاعات میگردد. به همین جهت در این نوع برنامهها روش session-per-request بهترین حالت کاری است.
سؤال: حین به روز رسانی اشیاء جدید، به خطا بر میخورم. مشکل در کجاست؟
فرض کنید شیء مفروض Customer را توسط متد session.Get از بانک اطلاعاتی دریافت و تعدادی از خواص آنرا جهت ساخت شیء جدیدی از کلاس Customer استفاده کردهایم. اکنون اگر بخواهیم این شیء جدید را در بانک اطلاعاتی ذخیره یا به روز رسانی کنیم، NHibernate این اجازه را نمیدهد! چرا؟
پاسخ:
خطای متداول این حالت عموما به صورت زیر است:
a different object with the same identifier value was already associated with the session
همانطور که عنوان شد، در طول یک سشن، نمیتوان دو شیء با یک ID را به عنوان یک رکورد بانک اطلاعاتی مورد استفاده قرار داد. اولین فراخوانی Get ، سبب کش شدن آن شیء در identity map سطح اول caching میگردد.
راه حل:
الف) از چندین و چند شیء استفاده نکنید. هر رکورد باید تنها با یک وهله از شیءایی متناظر باشد.
ب) میتوان پیش از update، کش سطح اول را به صورت دستی خالی کرد. برای این منظور از متد Clear شیء سشن استفاده کنید.
ج) بجای استفاده از متد saveOrUpdate شیء سشن، از متد Merge آن استفاده کنید. به این صورت شیء جدید ایجاد شده با شیء موجود در کش یکی خواهد شد.
د) میتوان بجای تخلیه کل کش (حالت ب)، کش مرتبط با شیء Customer را به صورت دستی خالی کرد. برای این منظور از متد Evict شیء سشن استفاده نمائید.
و لازم به ذکر است که متد Flush سبب تخلیه کش نمیگردد. کار این متد اعمال کلیه تغییرات اعمالی موجود در کش به بانک اطلاعاتی است و بیشتر جهت هماهنگ سازی این دو مورد استفاده قرار میگیرد.
سؤال: آیا میتوان سطح اول caching را غیرفعال کرد؟
پاسخ:بله.
توضیحات:
عموما کلیه ORMs جهت Batching یا Bulk data operations (برای مثال ثبت تعداد زیادی رکورد یا به روز رسانی تعداد بالایی از آنها، یا نمایش فقط خواندنی تعداد زیادی رکورد و گزارشگیری از آنها) کارآیی مطلوبی ندارند. نمونهای از آنرا در مبحث جاری ملاحظه کردهاید. هر شیءایی که به نحوی به سشن جاری وارد میشود تحت نظر قرار میگیرد و این مورد در تعداد بالای ثبت یا به روز رسانی رکوردها، یعنی کاهش سرعت و کارآیی، به علاوه مصرف بالای حافظه. به همین جهت باید به خاطر داشت که ORMs جهت سناریوهای OLTP مناسب هستند و کسانی که سرعت و کارآیی ORMs را با Batch processing اندازه گیری میکنند، کلا درکی از فلسفهی وجودی ORMs و ساختار درونی آنها ندارند!
خوشبختانه NHibernate با معرفی Stateless Sessions بر این مشکل فائق آمده است. در اینجا بجای ISession تنها کافی است از IStatelessSession استفاده گردد:
using (IStatelessSession statelessSession = sessionFactory.OpenStatelessSession())
using (ITransaction transaction = statelessSession.BeginTransaction())
{
//now insert 1,000,000 records!
}
تنها باید به خاطر داشت که در این حالت lazy loading پشتیبانی نمیشود و همچنین رخدادهای درونی NHibernate نیز لغو خواهند شد.