در ادامه مطالبمربوط به برنامه نویسی تابعی، قصد دارم بیشتر وارد کد شویم و مباحث عنوان شده را در دنیای کد پیاده سازی کنیم. هدف این قسمت، refactorکردن کد موجود به یک معماری immutableهست. پیشتردرباره immutableها صحبت کردیم.ابتدا برای یکسان سازی ادبیات مورد استفاده، چند کلمه را مجددا تعریف خواهیم کرد:
- Immutability: عدم توانایی تغییر داده
- State: دادههایی که در طول زمان تغییر میکنند
- Side Effect: تغییری که روی دادهها اتفاق میافتد
در قطعه کد زیر سعی شدهاست تفاوت یک کلاس Statelessو statefulرا به سادگی نشان دهیم:
//Stateful public class UserProfile { private User _user; private string _address; public void UpdateUser(int userId, string name) { _user = new User(userId, name); } } //Stateless public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
چرا Immutableبودن مهم است؟
هر عملmutable معادل کدی غیر شفاف است. در واقع وابستگی هر عملی که انجام میدهیم به state، باعث میشود که شرایط ناپایداری را در کد داشته باشیم. به طور مثال در یک عملیات چند نخی تصور کنید که چندین نخ به طور همزمان میتوانند stateرا تغییر دهند و مدیریت این قضیه باعث به وجود آمدن کدهایی ناخوانا و تحمیل پیچیدگی بیشتر به کد خواهد شد.
در واقع انتظار داریم که به ازای یک ورودی بر اساس بدنهی متد، یک خروجی داشته باشیم؛ ولی در واقعیت تاثیری که اجرای متد بر روی stateکل کلاس خواهد گذاشت، از دید ما پنهان است و باعث به وجود آمدن مشکلات بعدی خواهد شد. برای مثال قطعه کد بالا را به صورت Honestبازنویسی میکنیم:
public class UserProfile { private readonly User _user; private readonly string _address; public UserProfile(User user,string address) { _user = user; _address = address; } public UserProfile UpdateUser(int userId, string name) { var newUser = new User(userId, name); return new UserProfile(newUser,_address); } } public class User { public User(int id, string name) { Id = id; Name = name; } public int Id { get; } public string Name { get; } }
در این مثال متد UpdateUserبه جای void، یک شی از جنس کلاس UserProfileرا بر میگرداند. کلاس UserProfileهم برای وهله سازی نیاز به یک شیء از جنس Userو Addressرا دارد. بنابراین مطمئن هستیم که مقدار دهی شدهاند. نکته دیگر در قطعه کد بالا این است که به ازای هر بار فراخوانی متد، یک شیء جدید بدون وابستگی به وهله سازی اشیاء دیگر، برگردانده میشود.
Immutable
بودن باعث میشود:
- خوانایی کد افزایش پیدا کند
- جای واحدی برای Validateکردن داشته باشیم
- به صورت ذاتی Thread Safeباشیم
در مورد محدودیتهایی که در کار با اشیاء Immutableباید در نظر داشته باشیم، میتوان به مصرف بالای رم و سی پی یو، اشاره کرد. در واقع به نسبت حالت mutate، تعداد اشیاء بیشتری ساخته خواهند شد. در فریمورک دات نت برای کار با اشیا immutableامکاناتی در نظر گرفته شده که این هزینه را کاهش میدهند. به طور مثال میتوانیم از کلاس ImmutableListاستفاده کنیم و از ایجاد اشیاء اضافهتر و تحمیل بار اضافی به GCجلوگیری کنیم. یک مثال:
//Create Immutable List ImmutableList<string> list = ImmutableList.Create<string>(); ImmutableList<string> list2 = list.Add("Salam"); //Builder ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>(); builder.Add("avali"); builder.Add("dovomi"); builder.Add("sevomi"); ImmutableList<string> immutableList = builder.ToImmutable();
چطور با side effectکنار بیایم؟
یکی از الگوهای رایج برای این کار، مفهوم جدا سازی Command/Queryاست. به طور ساده تمامی عملیاتی را که تاثیر گذار هستند، به صورت Commandدر نظر میگیریم. Commandها معمولا هیچ نوعی را بازگشت نمیدهند و همینطور بر عکس این قضیه برای Queryها صادق است. اشتباه رایج درباره این الگو، محدود کردن این الگو به معماریهای خاصی مانند Domain Drivenمیباشد؛ در صورتیکه الزامی برای رعایت این الگو در سایر معماریها وجود ندارد.
به مثال زیر دقت کنید. سعی کردم قسمتهای Commandو Queryرا از هم جدا کنم:
در واقع هر برنامه میتواند شامل دو قسمت باشد:
قسمتی که در آن منطق تجاری برنامه پیاده سازی میشود و باید به صورت Immutable
باشد که یک خروجی را تولید میکند و قسمت دیگر برنامه که خروجی تولید شده را برای ذخیره
سازی وضعیت سیستم استفاده میکند.
برای مسائلی که در بالا صحبت شد، نمونهای را آماده کردهام. این نمونه به طور ساده یک سیستم مدیریت نوبت است که نوبتها را در فایلی ذخیره و بازیابی میکند (mutate) و منطق مربوط به نوبتها و زمان ویزیت آن میتواند به صورت immutableپیاده سازی شود. این کد در دو حالت functionalو غیر functionalپیاده سازی شده تا به خوبی تفاوت آن را در حالت قبل و بعد از برنامه نویسی تابعی بتوانیم درک کنیم. به جهت خوانایی بیشتر و دسترسی به کدها، آنها را روی گیتهاب قرار داده و شما میتوانید ازاینجا سورس کد مورد نظر را بررسی کنید. سعی شده در این مثال تمامی مواردی که در این قسمت ذکر شد را پیاده سازی کنیم. امیدوارم که مطالب مربوط به برنامه نویسی تابعی یا functional programmingتوانسته باشد دیدگاه جدیدی را به کدهایی که مینویسیم بدهد. در قسمتهای بعدی به مواردی مانند مدیریت exceptionها و کار با nullها و ... خواهیم پرداخت.