۱۳۸۹/۰۲/۲۰

دات نت 4 و كلاس Lazy


يكي از الگوهاي برنامه نويسي شيء گرا، Lazy Initialization Pattern نام دارد كه دات نت 4 پياده سازي آن‌را سهولت بخشيده است.
در دات نت 4 كلاس جديدي به فضاي نام System اضافه شده است به نام Lazy و هدف از آن lazy initialization است؛ من ترجمه‌اش مي‌كنم وهله سازي با تاخير يا به آن on demand construction هم گفته‌اند (زماني كه به آن نياز هست ساخته خواهد شد).
فرض كنيد در برنامه‌ي خود نياز به شيءايي داريد و ساخت اين شيء بسيار پرهزينه است. نيازي نيست تا بلافاصله پس از تعريف، اين شيء ساخته شود و تنها زمانيكه به آن نياز است بايد در دسترس باشد. كلاس Lazy جهت مديريت اينگونه موارد ايجاد شده است. تنها كاري كه در اينجا بايد صورت گيرد، محصور كردن آن شيء هزينه‌بر توسط كلاس Lazy است:

Lazy<ExpensiveResource> ownedResource = new Lazy<ExpensiveResource>();

در اين حالت براي دسترسي به شيء ساخته شده از ExpensiveResource ، مي‌توان از خاصيت Value استفاده نمود (ownedResource.Value). تنها در حين اولين دسترسي به ownedResource.Value ، شيء ExpensiveResource ساخته خواهد شد و نه پيش از آن و نه در اولين جايي كه تعريف شده است. پس از آن اين حاصل cache شده و ديگر وهله سازي نخواهد شد.
ownedResource داراي خاصيت IsValueCreated نيز مي‌باشد و جهت بررسي ايجاد آن شيء مي‌تواند مورد استفاده قرار گيرد. براي مثال قصد داريم اطلاعات ExpensiveResource را ذخيره كنيم اما تنها در حالتيكه يكبار مورد استفاده قرار گرفته باشد.
كلاس Lazy داراي دو متد سازنده‌ي ديگر نيز مي‌باشد:
public Lazy(bool isThreadSafe);
public Lazy(Func<T> valueFactory, bool isThreadSafe);

و هدف از آن استفاده‌ي صحيح از اين متد در محيط‌هاي چند ريسماني است. بديهي است در اين نوع محيط‌ ها علاقه‌اي نداريم كه در يك لحظه توسط چندين ترد مختلف، سبب ايجاد وهله‌هاي ناخواسته‌ا‌ي از ExpensiveResource شويم و تنها يك مورد از آن كافي است يا به قولي thread safe, lazy initialization of expensive objects
بديهي است اگر برنامه‌ي شما چند ريسماني نيست مي‌توانيد اين مكانيزم را كنسل كرده و اندكي كارآيي برنامه را با حذف قفل‌هاي همزماني اين كلاس بالا ببريد.

مثال اول:

using System;
using System.Threading;

namespace LazyExample
{
class Program
{
static void Main()
{
Console.WriteLine("Before assignment");
var slow = new Lazy<Slow>();
Console.WriteLine("After assignment");

Thread.Sleep(1000);

Console.WriteLine(slow);
Console.WriteLine(slow.Value);

Console.WriteLine("Press a key...");
Console.Read();
}
}


class Slow
{
public Slow()
{
Console.WriteLine("Start creation");
Thread.Sleep(2000);
Console.WriteLine("End creation");
}
}
}
خروجي اين برنامه به شرح زير است:

Before assignment
After assignment
Value is not created.
Start creation
End creation
LazyExample.Slow
Press a key...

همانطور كه ملاحظه مي‌كنيد تنها در حالت دسترسي به مقدار Value شيء slow ، عملا وهله‌اي از آن ساخته خواهد شد.

مثال دوم:
شايد نياز به مقدار دهي خواص كلاس پرهزينه‌ وجود داشته باشد. براي مثال علاقمنديم خاصيت SomeProperty كلاس ExpensiveClass را مقدار دهي كنيم. براي اين منظور مي‌توان به شكل ذيل عمل كرد (يك Func<t>را مي‌توان به سازنده‌ي آن ارسال نمود):

using System;

namespace LazySample
{
class Program
{
static void Main()
{
var expensiveClass =
new Lazy<ExpensiveClass>
(
() =>
{
var fobj = new ExpensiveClass
{
SomeProperty = 100
};
return fobj;
}
);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("expensiveClass.SomeProperty value {0}",
(expensiveClass.Value).SomeProperty);

Console.WriteLine("expensiveClass has value yet {0}",
expensiveClass.IsValueCreated);

Console.WriteLine("Press a key...");
Console.Read();
}
}

class ExpensiveClass
{
public int SomeProperty { get; set; }

public ExpensiveClass()
{
Console.WriteLine("ExpensiveClass constructed");
}
}
}

كاربردها:
- علاقمنديم تا ايجاد يك شيء هزينه‌بر تنها پس از انجام يك سري امور هزينه‌بر ديگر صورت گيرد. براي مثال آيا تابحال شده است كه با يك سيستم ماتريسي كار كنيد و نياز به چند گيگ حافظه براي پردازش آن داشته باشيد؟! زمانيكه از كلاس Lazy استفاده نمائيد، تمام اشياء مورد استفاده به يكباره تخصيص حافظه پيدا نكرده و تنها در زمان استفاده از آن‌ها كار تخصيص منابع صورت خواهد گرفت.
- ايجاد يك شيء بسيار پر هزينه بوده و ممكن است در بسياري از موارد اصلا نيازي به ايجاد و يا حتي استفاده از آن نباشد. براي مثال يك شيء كارمند را درنظر بگيريد كه يكي از خواص اين شيء، ليستي از مكان‌هايي است كه اين شخص قبلا در آنجاها كار كرده است. اين اطلاعات نيز به طور كامل از بانك اطلاعاتي دريافت مي‌شود. اگر در متدي، استفاده كننده از شيء كارمند هيچگاه اطلاعات مكان‌هاي كاري قبلي او را مورد استفاده قرار ندهد، آيا واقعا نياز است كه اين اطلاعات به ازاي هر بار ساخت وهله‌اي از شيء كارمند از ديتابيس دريافت شده و همچنين در حافظه ذخيره شود؟