نظرات مطالب
نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
دو بحث وجود داره:
- برجسته سازی قسمتی از عبارت جستجو شده در لیست نمایش داده شده توسط افزونه auto-complete. این مورد خودکار است و توسط افزونه انجام می‌شود.
- برجسته سازی قسمتی از عبارت جستجو شده در نتایج یک جستجوی کامل بعدی که قرار است highlight آن توسط ما با کدنویسی خاصی انجام شود. مراجعه کنید به این مطلب برای توضیحات بیشتر.
نظرات مطالب
پیاده سازی UnitOfWork به وسیله MEF
در مورد سوال اول که چرا CategoryMap رو به متد AddFromAssembly پاس دادم.
متد AddFromAssembly نیاز به یک Assembly دارد تا بتونه تمام کلاس هایی رو که از کلاس EntityTypeConfiguration ارث برده اند رو پیدا کنه و اونها رو به صورت خودکار به ModelBuilder اضافه کنه. به همین دلیل من Assembly کلاس CategoryMap رو به اون پاس دادم. دقت کنید که اگر من n تا کلاس Map دیگه هم توی این ClassLibrary داشتم باز توسط همین دستور این کار به صورت خودکار انجام می‌شد. (پیشنهاد می‌کنم تست کنید)
نکته: این متد برگرفته شده از متد AddFromAssembly در NHibernate Session Configuration   است.
البته بهتر است که یک کلاس پایه برای این کار بسازید و اون کلاس رو به AddFromAssembly پاس بدید.

در مورد سوال دوست عزیز مبنی بر اینکه متد Save رو در خود توابع Repository قرار دادم.
اگر به کد‌های نوشته شده دقت کنید من اصلا مفهومی به نام Repository رو پیاده سازی نکردم. به این دلیل که خود DbContext ترکیبی از Repository Pattern , UnitOfWork است. متد SaveChange صدا زده شده همان متد SaveChange در DbContext است.
فرض کنید من یک کلاس Business دیگر به صورت زیر داشتم.

public class MyBusiness : BusinessBase<Entity.MyEntity>
    {
        [ImportingConstructor]
        public MyBusiness  ( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork )
            : base( unitOfWork )
        {
        }
        public void Add(Entity.MyEntity entity)
        {
            UnitOfWork.Set<MyEntity>().Add(entity);
        }
    }
حالا کد کلاس Business Category به صورت زیر تغییر می‌کنه.
public class Category : BusinessBase<Entity.Category>
    {      
        [ImportingConstructor]
        public Category( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork )
            : base( unitOfWork )
        {
        }

        public override void Add( Entity.Category entity )
        {
            new MyBusiness().Add( new Entity.MyEntity() );
            UnitOfWork.Set<Entity.Category>().Add( entity );
            UnitOfWork.SaveChanges();
        }
    }
همان طور که می‌بینید فقط یک بار متد SaveChange فراخوانی شده است. Virtual کردن متد BusinessBase دقیقا به همین دلیل است.

مطالب
آشنایی با NHibernate - قسمت هشتم

معرفی الگوی Repository

روش متداول کار با فناوری‌های مختلف دسترسی به داده‌ها عموما بدین شکل است:
الف) یافتن رشته اتصالی رمزنگاری شده به دیتابیس از یک فایل کانفیگ (در یک برنامه اصولی البته!)
ب) باز کردن یک اتصال به دیتابیس
ج) ایجاد اشیاء Command برای انجام عملیات مورد نظر
د) اجرا و فراخوانی اشیاء مراحل قبل
ه) بستن اتصال به دیتابیس و آزاد سازی اشیاء

اگر در برنامه‌های یک تازه کار به هر محلی از برنامه او دقت کنید این 5 مرحله را می‌توانید مشاهده کنید. همه جا! قسمت ثبت، قسمت جستجو، قسمت نمایش و ...
مشکلات این روش:
1- حجم کارهای تکراری انجام شده بالا است. اگر قسمتی از فناوری دسترسی به داده‌ها را به اشتباه درک کرده باشد، پس از مطالعه بیشتر و مشخص شدن نحوه‌ی رفع مشکل، قسمت عمده‌ای از برنامه را باید اصلاح کند (زیرا کدهای تکراری همه جای آن پراکنده‌اند).
2- برنامه نویس هر بار باید این مراحل را به درستی انجام دهد. اگر در یک برنامه بزرگ تنها قسمت آخر در یکی از مراحل کاری فراموش شود دیر یا زود برنامه تحت فشار کاری بالا از کار خواهد افتاد (و متاسفانه این مساله بسیار شایع است).
3- برنامه منحصرا برای یک نوع دیتابیس خاص تهیه خواهد شد و تغییر این رویه جهت استفاده از دیتابیسی دیگر (مثلا کوچ برنامه از اکسس به اس کیوال سرور)، نیازمند بازنویسی کل برنامه می‌باشد.
و ...

همین برنامه نویس پس از مدتی کار به این نتیجه می‌رسد که باید برای این‌کارهای متداول، یک لایه و کلاس دسترسی به داده‌ها را تشکیل دهد. اکنون هر قسمتی از برنامه برای کار با دیتابیس باید با این کلاس مرکزی که انجام کارهای متداول با دیتابیس را خلاصه می‌کند، کار کند. به این صورت کد نویسی یک نواختی با حذف کدهای تکراری از سطح برنامه و همچنین بدون فراموش شدن قسمت مهمی از مراحل کاری، حاصل می‌گردد. در اینجا اگر روزی قرار شد از یک دیتابیس دیگر استفاده شود فقط کافی است یک کلاس برنامه تغییر کند و نیازی به بازنویسی کل برنامه نخواهد بود.

این روزها تشکیل این لایه دسترسی به داده‌ها (data access layer یا DAL) نیز مزموم است! و دلایل آن در مباحث چرا به یک ORM نیازمندیم برشمرده شده است. جهت کار با ORM ها نیز نیازمند یک لایه دیگر می‌باشیم تا یک سری اعمال متداول با آن‌هارا کپسوله کرده و از حجم کارهای تکراری خود بکاهیم. برای این منظور قبل از اینکه دست به اختراع بزنیم، بهتر است به الگوهای طراحی برنامه نویسی شیء گرا رجوع کرد و از رهنمودهای آن استفاده نمود.

الگوی Repository یکی از الگوهای برنامه‌ نویسی با مقیاس سازمانی است. با کمک این الگو لایه‌ای بر روی لایه نگاشت اشیاء برنامه به دیتابیس تشکیل شده و عملا برنامه را مستقل از نوع ORM مورد استفاه می‌کند. به این صورت هم از تشکیل یک سری کدهای تکراری در سطح برنامه جلوگیری شده و هم از وابستگی بین مدل برنامه و لایه دسترسی به داده‌ها (که در اینجا همان NHibernate می‌باشد) جلوگیری می‌شود. الگوی Repository (مخزن)، کار ثبت،‌ حذف، جستجو و به روز رسانی داده‌ها را با ترجمه آن‌ها به روش‌های بومی مورد استفاده توسط ORM‌ مورد نظر، کپسوله می‌کند. به این شکل شما می‌توانید یک الگوی مخزن عمومی را برای کارهای خود تهیه کرده و به سادگی از یک ORM به ORM دیگر کوچ کنید؛ زیرا کدهای برنامه شما به هیچ ORM خاصی گره نخورده و این عملیات بومی کار با ORM توسط لایه‌ای که توسط الگوی مخزن تشکیل شده، صورت گرفته است.

طراحی کلاس مخزن باید شرایط زیر را برآورده سازد:
الف) باید یک طراحی عمومی داشته باشد و بتواند در پروژه‌های متعددی مورد استفاده مجدد قرار گیرد.
ب) باید با سیستمی از نوع اول طراحی و کد نویسی و بعد کار با دیتابیس، سازگاری داشته باشد.
ج) باید امکان انجام آزمایشات واحد را سهولت بخشد.
د) باید وابستگی کلاس‌های دومین برنامه را به زیر ساخت ORM مورد استفاده قطع کند (اگر سال بعد به این نتیجه رسیدید که ORM ایی به نام XYZ برای کار شما بهتر است، فقط پیاده سازی این کلاس باید تغییر کند و نه کل برنامه).
ه) باید استفاده از کوئری‌هایی از نوع strongly typed را ترویج کند (مثل کوئری‌هایی از نوع LINQ).


بررسی مدل برنامه

مدل این قسمت (برنامه NHSample4 از نوع کنسول با همان ارجاعات متداول ذکر شده در قسمت‌های قبل)، از نوع many-to-many می‌باشد. در اینجا یک واحد درسی توسط چندین دانشجو می‌تواند اخذ شود یا یک دانشجو می‌تواند چندین واحد درسی را اخذ نماید که برای نمونه کلاس دیاگرام و کلاس‌های متشکل آن به شکل زیر خواهند بود:



using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Course
{
public virtual int Id { get; set; }
public virtual string Teacher { get; set; }
public virtual IList<Student> Students { get; set; }

public Course()
{
Students = new List<Student>();
}
}
}


using System.Collections.Generic;

namespace NHSample4.Domain
{
public class Student
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Course> Courses { get; set; }

public Student()
{
Courses = new List<Course>();
}
}
}

کلاس کانفیگ برنامه جهت ایجاد نگاشت‌ها و سپس ساخت دیتابیس متناظر

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample4.Domain.Course).Assembly))
.ExportTo(System.Environment.CurrentDirectory)
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
چند نکته در مورد این کلاس:
الف) با توجه به اینکه برنامه از نوع ویندوزی است، برای مدیریت صحیح کانکشن استرینگ، فایل App.Config را به برنامه افروده و محتویات آن‌را به شکل زیر تنظیم می‌کنیم (تا کلید DbConnectionString توسط متد GetConfig مورد استفاده قرارگیرد ):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>
</configuration>

ب) در NHibernate سنتی (!) کار ساخت نگاشت‌ها توسط یک سری فایل xml صورت می‌گیرد که با معرفی فریم ورک Fluent NHibernate و استفاده از قابلیت‌های Auto Mapping آن، این‌کار با سهولت و دقت هر چه تمام‌تر قابل انجام است که توضیحات نحوه‌ی انجام ‌آن‌را در قسمت‌های قبل مطالعه فرمودید. اگر نیاز بود تا این فایل‌های XML نیز جهت بررسی شخصی ایجاد شوند، تنها کافی است از متد ExportTo آن همانگونه که در متد GetConfig استفاده شده، کمک گرفته شود. به این صورت پس از ایجاد خودکار نگاشت‌ها، فایل‌های XML متناظر نیز در مسیری که به عنوان آرگومان متد ExportTo مشخص گردیده است، تولید خواهند شد (دو فایل NHSample4.Domain.Course.hbm.xml و NHSample4.Domain.Student.hbm.xml را در پوشه‌ای که محل اجرای برنامه است خواهید یافت).

با فراخوانی متد CreateDb این کلاس، پس از ساخت خودکار نگاشت‌ها، database schema متناظر، در دیتابیسی که توسط کانکشن استرینگ برنامه مشخص شده، ایجاد خواهد شد که دیتابیس دیاگرام آن‌را در شکل ذیل مشاهده می‌نمائید (جداول دانشجویان و واحدها هر کدام به صورت موجودیتی مستقل ایجاد شده که ارجاعات آن‌ها در جدولی سوم نگهداری می‌شود).



پیاده سازی الگوی مخزن

اینترفیس عمومی الگوی مخزن به شکل زیر می‌تواند باشد:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace NHSample4.NHRepository
{
//Repository Interface
public interface IRepository<T>
{
T Get(object key);

T Save(T entity);
T Update(T entity);
void Delete(T entity);

IQueryable<T> Find();
IQueryable<T> Find(Expression<Func<T, bool>> predicate);
}
}

سپس پیاده سازی آن با توجه به کلاس SingletonCore ایی که در قسمت قبل تهیه کردیم (جهت مدیریت صحیح سشن فکتوری)، به صورت زیر خواهد بود.
این کلاس کار آغاز و پایان تراکنش‌ها را نیز مدیریت کرده و جهت سهولت کار اینترفیس IDisposable را نیز پیاده سازی می‌کند :

using System;
using System.Linq;
using NHSessionManager;
using NHibernate;
using NHibernate.Linq;

namespace NHSample4.NHRepository
{
public class Repository<T> : IRepository<T>, IDisposable
{
private ISession _session;
private bool _disposed = false;

public Repository()
{
_session = SingletonCore.SessionFactory.OpenSession();
BeginTransaction();
}

~Repository()
{
Dispose(false);
}

public T Get(object key)
{
if (!isSessionSafe) return default(T);

return _session.Get<T>(key);
}

public T Save(T entity)
{
if (!isSessionSafe) return default(T);

_session.Save(entity);
return entity;
}

public T Update(T entity)
{
if (!isSessionSafe) return default(T);

_session.Update(entity);
return entity;
}

public void Delete(T entity)
{
if (!isSessionSafe) return;

_session.Delete(entity);
}

public IQueryable<T> Find()
{
if (!isSessionSafe) return null;

return _session.Linq<T>();
}

public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (!isSessionSafe) return null;

return Find().Where(predicate);
}

void Commit()
{
if (!isSessionSafe) return;

if (_session.Transaction != null &&
_session.Transaction.IsActive &&
!_session.Transaction.WasCommitted &&
!_session.Transaction.WasRolledBack)
{
_session.Transaction.Commit();
}
else
{
_session.Flush();
}
}

void Rollback()
{
if (!isSessionSafe) return;

if (_session.Transaction != null && _session.Transaction.IsActive)
{
_session.Transaction.Rollback();
}
}

private bool isSessionSafe
{
get
{
return _session != null && _session.IsOpen;
}
}

void BeginTransaction()
{
if (!isSessionSafe) return;

_session.BeginTransaction();
}


public void Dispose()
{
Dispose(true);
// tell the GC that the Finalize process no longer needs to be run for this object.
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposeManagedResources)
{
if (_disposed) return;
if (!disposeManagedResources) return;
if (!isSessionSafe) return;

try
{
Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
Rollback();
}
finally
{
if (isSessionSafe)
{
_session.Close();
_session.Dispose();
}
}

_disposed = true;
}
}
}
اکنون جهت استفاده از این کلاس مخزن به شکل زیر می‌توان عمل کرد:

using System;
using System.Collections.Generic;
using NHSample4.Domain;
using NHSample4.NHRepository;

namespace NHSample4
{
class Program
{
static void Main(string[] args)
{
//ایجاد دیتابیس در صورت نیاز
//NHSessionManager.Config.CreateDb();


//ابتدا یک دانشجو را اضافه می‌کنیم
Student student = null;
using (var studentRepo = new Repository<Student>())
{
student = studentRepo.Save(new Student() { Name = "Vahid" });
}

//سپس یک واحد را اضافه می‌کنیم
using (var courseRepo = new Repository<Course>())
{
var course = courseRepo.Save(new Course() { Teacher = "Shams" });
}

//اکنون یک واحد را به دانشجو انتساب می‌دهیم
using (var courseRepo = new Repository<Course>())
{
courseRepo.Save(new Course() { Students = new List<Student>() { student } });
}

//سپس شماره دروس استادی خاص را نمایش می‌دهیم
using (var courseRepo = new Repository<Course>())
{
var query = courseRepo.Find(t => t.Teacher == "Shams");

foreach (var course in query)
Console.WriteLine(course.Id);
}

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

همانطور که ملاحظه می‌کنید در این سطح دیگر برنامه هیچ درکی از ORM مورد استفاده ندارد و پیاده سازی نحوه‌ی تعامل با NHibernate در پس کلاس مخزن مخفی شده است. کار آغاز و پایان تراکنش‌ها به صورت خودکار مدیریت گردیده و همچنین آزاد سازی منابع را نیز توسط اینترفیس IDisposable مدیریت می‌کند. به این صورت امکان فراموش شدن یک سری از اعمال متداول به حداقل رسیده، میزان کدهای تکراری برنامه کم شده و همچنین هر زمانیکه نیاز بود، صرفا با تغییر پیاده سازی کلاس مخزن می‌توان به ORM دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.

دریافت سورس برنامه قسمت هشتم

ادامه دارد ...


مطالب
تعدادی از توابع kendo UI Treeview
kendo ui یکی از جذاب‌ترین و بهترین فریم ورک‌های HTML5 است که استفاده از آن بین برنامه نویسان جا افتاده است و تلریک هم پشتیبانی خوبی از آن به عمل آورده است. من هم به تازگی از شیء treeview آن  استفاده کردم و موقعیکه کارم با شیء Treeview به پایان رسید، یک فایل کوچک جاوااسکریپت به کار اضافه شد که شامل تعدادی از توابع چون حذف گره و ... بود که تصمیم گرفتم بر اساس مستندات و نیازهای عمومی، تعداد این توابع را بالا ببرم که برای استفاده در صفحات و پروژه‌های دیگر و بالا بردن امتیاز استفاده مجدد از کد از آن استفاده کنم. سورس آن در گیت هاب موجود است.
بعد از صدا زدن فایل‌های مورد نیاز کندو، این فایل جاوااسکریپتی را هم به پروژه اضافه کنید. بیشتر توابع تست شده و جواب گرفته و فکر نکنم مشکلی باشد؛ هر چند عیب یابی آن هم ساده است و مشکلی برای برطرف کردن آن وجود ندارد و هر تابع دو یا سه خط بیشتر نیست.

معرفی توابع


    var treeview;

    function FindTreeViewObj(objName) {
        treeview = $(objName).data("kendoTreeView");
    }
اولین و مهمترین تابع می‌باشد که باید قبل از همه در زمان لود پروژه بعد از ایجاد درخت آن را صدا بزنید، در صورتی که صدا زده نشود، بقیه‌ی توابع کار نخواهند کرد. این تابع وظیفه دارد شیء درخت معرفی شده را پیدا کرده و داخل یک متغیر عمومی به اسم treeview قرار دهد:
FindTreeViewObj("#treeview");

    function GetSelectedNode() {
        return treeview.select();
    }
گره انتخابی را باز می‌گرداند:
GetSelectedNode();


    function DisableSelectedNode() {
        treeview.enable(GetSelectedNode(), false);
    }
گره انتخابی را غیرفعال می‌کند:
DisableSelectedNode();

    function EnableSelectedNode() {
        treeview.enable(GetSelectedNode(), true);
    }
گره انتخابی را فعال می‌کند:
EnableSelectedNode();

    function EnableAllNodes() {
        treeview.enable(".k-item");
    }
همه‌ی گره‌ها را فعال می‌کند:
EnableAllNodes();

    function ExpandAllNodes() {
        treeview.expand(".k-item");
    }
همه‌ی گره‌ها را به سمت فرزندان باز می‌کند:
ExpandAllNodes();

    function CollapseAllNodes() {
        treeview.collapse(".k-item");
    }
همه گره‌ها به سمت والد را جمع می‌کند:
CollapseAllNodes();

    function RemoveSelectedNode() {
        treeview.remove(GetSelectedNode());
    }
گره انتخابی را حذف می‌کند:
RemoveSelectedNode()

    function FilterTreeView(filterText) {
        if (filterText !== "") {
            treeview.dataSource.filter({
                field: "text",
                operator: "contains",
                value: filterText
            });
        } else {
            treeview.dataSource.filter({});
        }
    }
گره‌هایی که عبارت مد نظر در آن‌ها باشند، نمایش میابند:
FilterTreeView('my node')

    function SortTreeView(sortType) {
        treeview.dataSource.sort({
            field: "text",
            dir: sortType
        });
    }
گره‌ها را به صورت صعودی یا نزولی مرتب می‌کند. مقادیر پارامتر ورودی آن یا "asc" است یا "desc"
SortTreeView('asc');
SortTreeView('desc');

    function GetSelectedDataItem() {
        return treeview.dataItem(GetSelectedNode());
    }
قسمت اطلاعاتی یک گره که شامل مواردی چون عنوان یا Id است را باز می‌گرداند:
GetSelectedDataItem();

    function GetSelectedNodeId() {
        var data = GetSelectedDataItem();
        return data.id;
    }
Id گره را بر میگرداند:
GetSelectedNodeId();

    function GetSelectedNodeText() {
        var data = GetSelectedDataItem();
        return data.Name;
    }
متن یا مقدار گره را باز میگرداند:
GetSelectedNodeText();

    function SetSelectedNodeText(value) {
        var node = GetSelectedNode();
        treeview.text(node, value);
    }
مقدار گره را به مقدار جدید تغییر می‌دهد:
SetSelectedNodeText('new value');

    function GetNodeByText(text) {
        return treeview.findByText(text);
    }
اولین گره با این متن را باز میگرداند:
GetNodeByText('mynode');

    function GetNodeByText(id) {
        return treeview.findByUid(id);
    }
گره ای با این Id را باز میگرداند:
GetNodeByText(4);

    function InsertAfter(item, nextItem) {
        treeview.insertAfter({ text: "item" }, GetNodeByText(nextItem));
    }
متن دو گره را دریافت می‌کند، گره‌ای را بر اساس متن پارامتر دوم پیدا کرده و بعد از آن گره اول را با مقدار پارامتر اول درج می‌کند.
InsertAfter('new item', 'old item')

    function MoveToAfter(firstItem, secondItem) {
        treeview.insertAfter(GetNodeByText(firstItem), GetNodeByText(secondItem));
    }
مقدار گره‌ها را دریافت می‌کند و بعد از پیدا کردن آن‌ها، گره اول را به بعد از گره دوم انتقال می‌دهد:
MoveToAfter('firstItem', 'secondItem');

    function InsertBefore(item, nextItem) {
        treeview.insertBefore({ text: "item" }, GetNodeByText(nextItem));
    }

    function MoveToBefore(firstItem, secondItem) {
        treeview.insertBefore(GetNodeByText(firstItem), GetNodeByText(secondItem));
    }
دو تابع بعد مثل توابع بالا هستند و فقط به قبل آن اضافه می‌کند یا انتقال می‌دهد.
InsertBefore('new item', 'old item');
MoveToBefore('firstItem', 'secondItem');

    function GetParent(node) {
        return treeview.parent(node);
    }
والد گره انتخابی را بر میگرداند:
GetParent(GetSelectedNode());

    function Toggle(node) {
        treeview.toggle(node);
    }
گره را (در صورت داشتن فرزند)  باز و بسته می‌کند:
Toggle(GetSelectedNode());

    function NewNode(nodeText, nodeValue, selectedNode) {
        treeview.append({
            Name: nodeText,
            Id: nodeValue
        }, selectedNode);
    }
گره جدیدی را اضافه می‌کند. پارامتر اول مقدار گره، پارامتر دوم Id گره و پارامتر سوم در صورتی که بخواهید یک زیر گره باشد، باید گره والد را به آن پاس کنید و درصورتی که زیر گره نیست آن را نال مقدار دهی کنید.
NewNode('new node', 1, null);
NewNode('new sub node', 2, GetSelectedNode());
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت اول - Redux چیست؟
Redux و Mobx، کتابخانه‌های کمکی هستند برای مدیریت حالت برنامه‌های پیچیده‌ی React. هرچند React به صورت توکار به همراه امکانات مدیریت حالت است، اما این کتابخانه‌ها مزایای ویژه‌ای را به آن اضافه می‌کنند. در این سری ابتدا کتابخانه‌ی Redux را به صورت خالص و مجزای از React بررسی می‌کنیم. از این کتابخانه در برنامه‌های Angular و Ember هم می‌توان استفاده کرد و به صورت اختصاصی برای React طراحی نشده‌است. سپس آن‌را به برنامه‌های React متصل می‌کنیم. در آخر کتابخانه‌ی محبوب دیگری را به نام Mobx بررسی می‌کنیم که برای مدیریت حالت، اصول برنامه نویسی شیءگرا و همچنین Reactive را با هم ترکیب می‌کند و این روزها در برنامه‌های React، بیشتر از Redux مورد استفاده قرار می‌گیرد.


چرا به ابزارهای مدیریت حالت نیاز داریم؟

به محض رد شدن از مرز پیاده سازی امکانات اولیه‌ی یک برنامه، نیاز به ابزارهای مدیریت حالت نمایان می‌شوند؛ خصوصا زمانیکه نیاز است با اطلاعات قابل توجهی سر و کار داشت. مهم‌ترین دلیل استفاده‌ی از یک ابزار مدیریت حالت، مدیریت منطق تجاری برنامه است. منطق نمایشی برنامه مرتبط است به نحوه‌ی نمایش اجزای آن در صفحه؛ مانند نمایش یک صفحه‌ی مودال، تغییر رنگ عناصر با عبور کرسر ماوس از روی آن‌ها و در کل منطقی که مرتبط و یا وابسته‌ی به هدف اصلی برنامه نیست. از سوی دیگر منطق تجاری برنامه مرتبط است با مدیریت، تغییر و ذخیره سازی اشیاء تجاری مورد نیاز آن؛ مانند اطلاعات حساب کاربری شخص و دریافت اطلاعات برنامه از یک API که مختص به برنامه‌ی خاص ما است و به همین دلیل نیاز به ابزاری برای مدیریت بهینه‌ی آن وجود دارد. برای مثال اینکه در کجا باید منطق تجاری و نمایشی را به هم متصل کرد، می‌تواند چالش بر انگیر باشد. چگونه باید اطلاعات کاربر را ذخیره کرد؟ چگونه React باید متوجه شود که اطلاعات ما تغییر کرده‌است و در نتیجه‌ی آن کامپوننتی را مجددا رندر کند؟ یک ابزار مدیریت حالت، تمام این مسایل را به نحو یک‌دستی در سراسر برنامه، مدیریت می‌کند.
اگر از یک ابزار مدیریت حالت استفاده نکنیم، مجبور خواهیم شد تمام اطلاعات منطق تجاری را در داخل state کامپوننت‌ها ذخیره کنیم که توصیه نمی‌شود؛ چون مقیاس پذیر نیست. برای مثال فرض کنید قرار است تمام اطلاعات state را داخل یک کامپوننت ذخیره کنیم. هر زمانیکه بخواهیم این state را از طریق یک کامپوننت فرزند تغییر دهیم، نیاز خواهد بود این اطلاعات را به والد آن کامپوننت ارسال کنیم که اگر از تعداد زیادی کامپوننت تو در تو تشکیل شده باشد، زمانبر و به همراه کدهای تکراری زیادی خواهد بود. همچنین اینکار سبب رندر مجدد کل برنامه با هر تغییری در state آن می‌شود که غیرضروری بوده و کارآیی برنامه را کاهش می‌دهد. به علاوه در این بین مشخص نیست هر قسمت از state، از کدام کامپوننت تامین شده‌است. به همین جهت نیاز به روشی برای مدیریت حالت در بین کامپوننت‌های برنامه وجود دارد.


داشتن تنها یک محل برای ذخیره سازی state در برنامه

همانطور که در قسمت 8 ترکیب کامپوننت‌ها در سری React 16x بررسی کردیم، هر کامپوننت در React، دارای state خاص خودش است و این state از سایر کامپوننت‌ها کاملا مستقل و ایزوله‌است. این مورد با بزرگ‌تر شدن برنامه و برقراری ارتباط بین کامپوننت‌ها، مشکل ایجاد می‌کند. برای مثال اگر بخواهیم دکمه‌ای را در صفحه قرار داده و توسط این دکمه درخواست صفر شدن مقدار هر کدام از شمارشگرها را صادر کنیم، با صفر کردن value هر کدام از این کامپوننت‌ها، اتفاقی رخ نمی‌دهد. چون state محلی این کامپوننت‌ها، با سایر اجزای صفحه به اشتراک گذاشته نمی‌شود و باید آن‌را تبدیل به یک controlled component کرد، بطوریکه دارای local state خاص خودش نیست و تمام داده‌های دریافتی را از طریق this.props دریافت می‌کند و هر زمانیکه قرار است داده‌ای تغییر کند، رخ‌دادی را به والد خود صادر می‌کند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل می‌شود. تازه این روش در مورد کامپوننت‌هایی صدق می‌کند که رابطه‌ی والد و فرزندی بین آن‌ها وجود دارد. اگر چنین رابطه‌ای وجود نداشت، باید state را به یک سطح بالاتر انتقال داد. برای مثال باید state کامپوننت Counters را به والد آن که کامپوننت App است، منتقل کرد. پس از آن چون کامپوننت‌های ما، از کامپوننت App مشتق می‌شوند، اکنون می‌توان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت. این مورد هم مانند مثال انتقال اطلاعات کاربر لاگین شده‌ی به سیستم، به تمام زیر قسمت‌های برنامه، نیاز به ارسال اطلاعات از طریق props یک کامپوننت، به کامپوننت بعدی را دارد و به همین ترتیب برای مابقی که به props drilling مشهور است و روش پسندیده‌ای نیست.


Redux چیست؟ ذخیره سازی کل درخت state یک برنامه، در یک محل. به این ترتیب به یک شیء جاوا اسکریپتی بزرگ خواهیم رسید که در برگیرنده‌ی تمام state برنامه‌است. یکی از مزایای آن امکان serialize و deserialize کل این شیء، به سادگی است. برای مثال توسط متد JSON.stringify می‌توان آن‌را در جائی ذخیره کرد و سپس آن‌را به صورت یک شیء جاو اسکریپتی در زمانی دیگر بازیابی کرد. یکی از مزایای آن، امکان بازیابی دقیق شرایط کاربری است که دچار مشکل شده‌است و سپس دیباگ و رفع مشکل او، در زمانی دیگر.


تاریخچه‌ای از سیستم‌های مدیریت حالت

همه چیز با AngularJS 1x شروع شد که از data binding دو طرفه پشتیبانی می‌کرد. هرچند این روش برای همگام نگه داشتن View و مدل برنامه، مفید است، اما در Viewهای پیچیده، برنامه را کند می‌کند. در همین زمان فیس‌بوک، روش مدیریت حالتی را به نام Flux ارائه داد که از data binding یک طرفه پشتیبانی می‌کرد. به این معنا که در این روش، همواره اطلاعات از View به مدل، جریان پیدا می‌کند. کار کردن با آن ساده‌است؛ چون نیازی نیست حدس زده شود که اکنون جریان اطلاعات از کدام سمت است. اما مشکل آن عدم هماهنگی model و view، در بعضی از حالات است. Flux از این جهت به وجود آمد که مدیریت حالت در برنامه‌های React آن زمان، پیچیده بود و مقیاس پذیری کمی داشت (پیش از ارائه‌ی Context و Hooks). در کل Flux صرفا یکسری الگوی مدیریت حالت را بیان می‌کند و یک کتابخانه‌ی مجزا نیست. بر مبنای این الگوها و قراردادها، می‌توان کتابخانه‌های مختلفی را ایجاد کرد. از این رو در سال 2015، کتابخانه‌های زیادی مانند Reflux, Flummox, MartyJS, Alt, Redux و غیره برای پیاده سازی آن پدید آمدند. در این بین، کتابخانه‌ی Redux ماندگار شد و پیروز این نبرد بود!


توابع خالص و ناخالص (Pure & Impure Functions)

پیش از شروع بحث، نیاز است با یک‌سری از واژه‌ها مانند توابع خالص و ناخالص آشنا شد. این نکات از این جهت مهم هستند که Redux فقط با توابع خالص کار می‌کند.
توابع خالص: تعدادی آرگومان را دریافت کرده و بر اساس آن‌ها، مقداری را باز می‌گردانند.
// Pure
const add = (a, b) => {
  return a + b;
}
در اینجا یک تابع خالص را مشاهده می‌کنید که a و b را دریافت کرده و بر این اساس، یک خروجی کاملا مشخص را بازگشت می‌دهد.

توابع ناخالص: این نوع توابع سبب تغییراتی در متغیرهایی خارج از میدان دید خود می‌شوند و یا به همراه یک سری اثرات جانبی (side effects) مانند تعامل با دنیای خارج (وجود یک console.log در آن تابع و یا دریافت اطلاعاتی از یک API خارجی) هستند.
// Impure
const b;

const add = (a) => {
  return a + b;
}
تابع تعریف شده‌ی در اینجا ناخالص است؛ چون با اطلاعاتی خارج از میدان دید خود مانند متغیر b، تعامل دارد. این تعامل با دنیای خارج، حتی در حد نوشتن یک console.log:
// Impure
const add = (a, b) => {
  console.log('lolololol');
  return a + b;
}
یک تابع خالص را تبدیل به یک تابع ناخالص می‌کند و یا نمونه‌ی دیگر این تعاملات، فراخوانی سرویس‌های backend در برنامه هستند که یک تابع را ناخالص می‌کنند:
// Impure
const add = (a, b) => {
   Api.post('/add', { a, b }, (response) => {
    // Do something.
   });
};


روش‌هایی برای جلوگیری از تغییرات در اشیاء در جاوا اسکریپت

ایجاد تغییرات در آرایه‌ها و اشیاء (Mutating arrays and objects) نیز ناخالصی ایجاد می‌کند؛ از این جهت که سبب تغییراتی در دنیای خارج (خارج از میدان دید تابع) می‌شویم. به همین جهت نیاز به روش‌هایی وجود دارد که از این نوع تغییرات جلوگیری کرد:
// Copy object
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
برای تغییری در یک شیء، تنها کافی است خاصیتی را به آن اضافه کنیم و یا با استفاده از واژه‌ی کلیدی delete، خاصیتی را از آن حذف کنیم. به همین جهت برای اینکه تغییرات ما بر روی شیء اصلی اثری را باقی نگذارند، یکی از روش‌ها، استفاده از متد Object.assign است. کار آن، یکی کردن اشیایی است که به آن ارسال می‌شوند. به همین جهت در اینجا با یک شیء خالی، از صفر شروع می‌کنیم. سپس دومین آرگومان آن را به همان شیء مدنظر، تنظیم می‌کنیم. به این ترتیب به یک کپی از شیء اصلی می‌رسیم که دیگر به آن، اتصالی را ندارد. به همین جهت اگر بر روی این شیء کپی تغییراتی را ایجاد کنیم، به شیء اصلی کپی نمی‌شود و سبب تغییرات در آن (mutation) نخواهد شد.
برای مثال در React، برای انجام رندر نهایی، در پشت صحنه کار مقایسه‌ی اشیاء صورت می‌گیرد. به همین جهت اگر همان شیءای را که ردیابی می‌کند تغییر دهیم، دیگر نمی‌تواند به صورت مؤثری فقط قسمت‌های تغییر کرده‌ی آن‌را تشخیص داده و کار رندر را فقط بر اساس آن‌ها انجام دهد و مجبور خواهد شد کل یک شیء را بارها و بارها رندر کند که اصلا بهینه نیست. به همین جهت، ایجاد تغییرات مستقیم در شیءای که به state آن انتساب داده می‌شود، مجاز نیست.

متد Object.assign، چندین شیء را نیز می‌تواند با هم یکی کند و شیء جدیدی را تشکیل دهد:
// Extend object
const original = { a: 1, b: 2 };
const extension = { c: 3 };
const extended = Object.assign({}, original, extension);
روش دیگر ایجاد یک کپی و یا clone از یک شیء را که پیشتر در سری «React 16x» بررسی کردیم، به کمک امکانات ES-6، به صورت زیر است:
// Copy object
const original = { a: 1, b: 2 };
const copy = { ...original };
در اینجا نیز ابتدا یک شیء خالی را ایجاد می‌کنیم و سپس توسط spread operator، خواص شیء قبلی را درون آن باز کرده و قرار می‌دهیم. به این ترتیب به یک clone از شیء اصلی می‌رسیم. این حالت نیز از ترکیب چندین شیء با هم، پشتیبانی می‌کند:
// Extend object
const original = { a: 1, b: 2 };
const extension = { c: 3 };
const extended = { ...original, ...extension };


روش‌هایی برای جلوگیری از تغییرات در آرایه‌ها در جاوا اسکریپت

متد slice آرایه‌ها نیز بدون ذکر آرگومانی، یک کپی از آرایه‌ی اصلی را ایجاد می‌کند:
// Copy array
const original = [1, 2, 3];
const copy = [1, 2, 3].slice();
همچنین معادل همین قطعه کد در ES-6 به همراه spread operator به صورت زیر است:
// Copy array
const original = [1, 2, 3];
const copy = [ ...original ];
و یا اگر بخواهیم یک کپی از چندین آرایه را ایجاد کنیم می‌توان از متد concat استفاده کرد:
// Extend array
const original = [1, 2, 3];
const extended = original.concat(4);
const moreExtended = original.concat([4, 5]);
متد Array.push، هرچند سبب افزوده شدن عنصری به یک آرایه می‌شود، اما یک mutation را نیز ایجاد می‌کند؛ یعنی تغییرات آن به دنیای خارج اعمال می‌گردد. اما Array.concat یک آرایه‌ی کاملا جدید را ایجاد می‌کند و همچنین امکان ترکیب آرایه‌ها را نیز به همراه دارد.
معادل قطعه کد فوق در ES-6 و به همراه spread operator آن به صورت زیر است:
// Extend array
const original = [1, 2, 3];
const extended = [ ...original, 4 ];
const moreExtended = [ ...original, ...extended, 5 ];


مفاهیم ابتدایی Redux


در Redux برای ایجاد تغییرات در شیء کلی state، از مفهومی به نام dispatch actions استفاده می‌شود. action در اینجا به معنای رخ‌دادن چیزی است؛ مانند کلیک بر روی یک دکمه و یا دریافت اطلاعاتی از یک API. در این حالت مقایسه‌ای بین وضعیت قبلی state و وضعیت فعلی آن صورت می‌گیرد و تغییرات مورد نیاز جهت اعمال به UI، محاسبه خواهند شد.
اصلی‌ترین جزء Redux، تابعی است به نام Reducer. این تابع، یک تابع خالص است و دو آرگومان را دریافت می‌کند:


تابع Reducer، بر اساس action و یا رخ‌دادی، ابتدا کل state برنامه را دریافت می‌کند و سپس خروجی آن بر اساس منطق این تابع، یک state جدید خواهد بود. اکنون که این state جدید را داریم، برنامه‌ی React ما می‌تواند به تغییرات آن گوش فرا داده و بر اساس آن، UI را به روز رسانی کند. به این ترتیب کار اصلی مدیریت state، به خارج از برنامه‌ی React منتقل می‌شود.

در این تصویر، تابع action creator را هم ملاحظه می‌کند که کاملا اختیاری است. یک action می‌تواند یک رشته و یا یک عدد باشد. با پیچیده شدن برنامه، نیاز به ارسال یک‌سری متادیتا و یا اطلاعات بیشتری از اکشن رسیده‌است. کار action creator، ایجاد شیء action، به صورت یک دست و یکنواخت است تا دیگر نیازی به ایجاد دستی آن نباشد.


مزایای کار با Redux

- داشتن یک مکان مرکزی برای ذخیره سازی کلی حالت برنامه (به آن «source of truth» و یا store هم گفته می‌شود): به این ترتیب مشکل ارسال خواص در بین کامپوننت‌های عمیق و چند سطحی، برطرف شده و هر زمانیکه نیاز بود، از آن اطلاعاتی را دریافت و یا با قالب خاصی، آن‌را به روز رسانی می‌کنند.
- رسیدن به به‌روز رسانی‌های قابل پیش بینی state: هرچند در حالت کار با Redux، یک شیء بزرگ جاوا اسکریپتی، کل state برنامه را تشکیل می‌دهد، اما امکان کار مستقیم با آن و تغییرش وجود ندارد. به همین جهت است که برای کار با آن، باید رویدادی را از طریق actionها به تابع Reducer آن تحویل داد. چون Reducer یک تابع خالص است، با دریافت یک سری ورودی مشخص، همواره یک خروجی مشخص را نیز تولید می‌کند. به همین جهت قابلیت ضبط و تکرار را پیدا می‌کند؛ همان بحث serialize و deseriliaze، توسط ابزاری مانند: logrocket. به علاوه قابلیت undo و redo را نیز می‌توان به این ترتیب پیاده سازی کرد (state جدید محاسبه شده، مشخص است، کل state قبلی را نیز داریم یا می‌توان ذخیره کرد و سپس برای undo، آن‌را جایگزین state جدید نمود). افزونه‌ی redux dev tools نیز قابلیت import و export کل state را به همراه دارد.
- چون تابع Reducer، یک تابع خالص است و همواره خروجی‌های مشخصی را به ازای ورودی‌های مشخصی، تولید می‌کند، آزمایش کردن، پیاده سازی و حتی logging آن نیز ساده‌تر است. در این بین حتی یک افزونه‌ی مخصوص نیز برای دیباگ آن تهیه شده‌است: redux-devtools-extension. تابع خالص، تابعی است که به همراه اثرات جانبی نیست (side effects)؛ به همین جهت عملکرد آن کاملا قابل پیش بینی بوده و آزمون پذیری آن به دلیل نداشتن وابستگی‌های خارجی، بسیار بالا است.


Context API خود React چطور؟

در قسمت 33 سری React 16x، مفهوم React Context را بررسی کردیم. پس از معرفی آن با React 16.3، مقالات زیادی منتشر شدند که ... Redux مرده‌است (!) و یا بجای Redux از React context استفاده کنید. اما واقعیت این است که React Redux در پشت صحنه از React context استفاده می‌کند و تابع connect آن دقیقا به همین زیر ساخت متصل می‌شود.
کار با Redux مزایایی مانند کارآیی بالاتر، با کاهش رندر‌های مجدد کامپوننت‌ها، دیباگ ساده‌تر با افزونه‌های اختصاصی و همچنین سفارشی سازی، مانند نوشتن میان‌افزارها را به همراه دارد. اما شاید واقعا نیازی به تمام این امکانات را هم نداشته باشید؛ اگر هدف، صرفا انتقال ساده‌تر اطلاعات بوده و برنامه‌ی مدنظر نیز کوچک است. React Context برخلاف Redux، نگهدارنده‌ی state نیست و بیشتر هدفش محلی برای ذخیره سازی اطلاعات مورد استفاده‌ی در چندین و چند کامپوننت تو در تو است. هرچند شبیه به Redux می‌توان اشاره‌گرهایی از متدها را به استفاده کنندگان از آن ارسال کرد تا سبب بروز رویدادها و اکشن‌هایی در کامپوننت تامین کننده‌ی Contrext شوند (یا یک کتابخانه‌ی ابتدایی شبیه به Redux را توسط آن تهیه کرد). بنابراین برای انتخاب بین React Context و Redux باید به اندازه‌ی برنامه، تعداد نفرات تیم، آشنایی آن‌ها با مفاهیم Redux دقت داشت.
مطالب
بازسازی کد: استخراج کلاس (Extract class)
زمانیکه کلاسی، دو یا چند کار را انجام می‌دهد، بهتر است این امور در کلاس‌های مجزایی انجام شوند. راه اصلی این کار، بازسازی کد استخراج کلاس است. ایده اصلی این بازسازی کد با ساختن کلاسی جدید و انتقال خصوصیت‌ها، فیلدها و متدهای مورد نظر به آن انجام می‌شود.
کلاس‌ها معمولا از ابتدا به صورت چند وظیفه‌ای و پیچیده طراحی و پیاده سازی نمی‌شوند. اما با گذشت زمان معمولا کلاس‌ها پیچیده‌تر می‌شوند. این پیچیدگی تاثیر مستقیمی را بر روی قابلیت نگهداری نرم افزار خواهد داشت. 
تشخیص کلاسی که نیاز به جداسازی دارد امر مهمی است. روش‌های مختلفی نیز برای این کار وجود دارد که در زیر اشاره شده‌است. با دیدن نشانه‌هایی مانند نشانه‌های زیر می‌توانید اطمینان داشته باشید که کلاس مورد نظر شما نیاز به جداسازی دارد.
  • تعدادی خصوصیت و فیلد و متد در کلاس وجود دارند که به نظر می‌رسد فقط باهم کار می‌کنند.
  • زیر مجموعه‌ای از داده‌های کلاس، معمولا همراه هم تغییر می‌کنند یا به یکدیگر وابسته هستند.
  • با تغییر بدنه متدی، نیاز شود متدهای دیگری در همان راستا تغییر کنند.
  • ارث بری‌ها تنها به صورت دسته‌ای بر اعضای کلاس اثر می‌گذارند.  

مراحل انجام این بازسازی کد  

  1. تصمیم بگیرید که به چه صورت کلاسی را تقسیم خواهید کرد.
  2. کلاس جدیدی بسازید که نشان دهنده مسئولیت‌های جدید تقسیم شده باشد. اگر نام کلاس قدیمی با مسئولیت‌های باقی مانده برای آن همخوانی نداشت، نام آن را تغییر دهید 
  3. فیلدها و خصوصیات کلاس قدیمی را با استفاده از بازسازی Move field به کلاس جدید منتقل نمایید.
  4. کد را برای هر انتقال کامپایل و تست نمایید.
  5. متدهای کلاس قدیمی را با استفاده از بازسازی جابجایی متد به کلاس جدید منتقل نمایید.
  6. کد را برای هر انتقال، کامپایل و تست نمایید.
مثال   (برای سادگی توضیح، موضوع مثال‌ها بسیار ساده در نظر گرفته شده‌اند)
فرض کنید بخشی از نرم افزار تولید شده، مسئولیت مدیریت صورت حساب‌ها و پرداخت‌ها را بر عهده دارد. در این زیر سیستم کلاس مدیریت کننده صورت حساب‌ها و پرداخت‌ها به اسم InvoiceService ساخته شده‌است. بدنه این کلاس به صورت زیر است:  
public class InvoiceService 
{ 
    public void AddInvoice() 
    { 
    } 
    public void DeleteInvoice() 
    { 
    } 
    public void AddOfflinePayment(int invoiceId) 
    { 
    } 
    public void AddOnlinePayment(int invoiceId) 
    { 
    } 
}
متدهای AddOfflinePayment و AddOnlinePayment مسئولیت ذخیره یک پرداخت را برای صورت حساب مشخص شده، دارند. 
متدهای دیگری نیز برای ذخیره و حذف صورت حساب‌ها در این کلاس مشاهده می‌شود. 
اگر کلاس InvoiceService با این تعداد عضو باقی بماند احتمالا مشکل خاصی پیش نخواهد آمد. مشکل زمانی بوجود می‌آید که تعداد اعضای بیشتری برای مدیریت پرداخت‌ها و صورت حساب‌ها نیاز باشد. طراحی فعلی این کلاس موارد مربوط به پرداخت و صورت حساب را تلفیق کرده‌است. طراحی بهتر این کلاس، بازسازی استخراج کلاس است. به این صورت که کلاسی مجزا برای مدیریت امور پرداخت ایجاد شود. به این ترتیب کلاس‌هایی با مسئولیت‌های مشخص خواهیم داشت. تکه کد زیر تغییرات کلاس InvoiceService را نشان می‌دهد:  
public class InvoiceService 
{ 
    public void AddInvoice() 
    { 
    } 
    public void DeleteInvoice() 
    { 
    } 
} 
public class PaymentService 
{ 
    public void AddOfflinePayment(int invoiceId) 
    { 
    } 
    public void AddOnlinePayment(int invoiceId) 
    { 
    } 
}
تغییرات اعمال شده ساده هستند، اما با در نظر گرفتن تاثیر مثبت فراوان کلاسهای تک وظیفه‌ای، این تغییر می‌تواند اثر بسیار خوبی بر روی قابلیت نگهداری نرم افزار نیز داشته باشد. 
نمونه‌های دیگری از بازسازی کد استخراج متد که بیشتر مشاهده کرده‌ام، به صورت زیر هستند:  
  • تقسیم کلاس‌های controller بر اساس قوانین طراحی REST 
  • تقسیم کلاس‌های داده (data model) بر اساس قوانین شیءگرایی و تک وظیفگی  
نظر شما چیست؟ بیشترین موارد نیاز به استخراج کلاس را در چه بخش‌هایی از کد مشاهده کرده‌اید؟
نظرات مطالب
آشنایی با TransactionScope
با سلام و عرض خسته نباشید
مطلب خوبی بود ولی هیچ راهی نداره تو سیستمی که دیتا زیاد داره و کاربر آنلاین هم زیاد با یک مدلی تراکنش را پیاده سازی کرد
من خودم هم تو سیستمی که کار می‌کنم به این مشکل برخورد کردم و کلاً بیخیال تراکنش شدم ولی 1 درصد از عملیات سیستمم معلوم نیست چی میشن
یعنی 1 در صد از عملیات هام نصفه و نیمه باقی موندن . اگه روشی برای پیاده سازی این مدل دارید خواهشن عنوان کنید- به صورت عملی هم توضیح بدید خیلی بهتره
با تشکر
نظرات مطالب
بهبود کارآیی حلقه‌های foreach در دات نت 7
یک نکته‌ی تکمیلی: آشنایی با مفهوم «C# Lowering»


در این مطلب، جهت بررسی درک علت یکسان بودن کارآیی حلقه‌هایی که از دیدگاه ما یکی نیستند، از قابلیت نمایش #Low-level C استفاده شد که نام اصلی آن «C# Lowering» است. Lowering به معنای ترجمه‌ی امکانات سطح بالای یک زبان به امکانات سطح پایین آن است. یعنی حاصل عملیات صورت گرفته نیز باز به همان زبان اولیه است که نمونه‌ی آن، تبدیل یک حلقه‌ی foreach سطح بالا به نمونه‌ی سطح پایینی است که توسط NET Runtime. بهتر درک شده و ساده‌تر اجرا می‌شود.

مزایای Lowering
- بهبود کارآیی برنامه: برای مثال یکی از کارهایی که در این بین عموما انجام می‌شود «Loop unrolling» است. یعنی یک حلقه به چندین حلقه‌ی کوچکتر تقسیم می‌شود تا سربار instructions کنترلی حلقه کاهش پیدا کنند.
- طراحی ساده‌تر زبان: اینکار به تیم طراحی زبان امکان نوشتن کدهای اضافه‌تری را می‌دهد که کار برنامه نویس‌ها را کمتر می‌کند. برای مثال یک record واقعا چیزی نیست بجز یک کلاس پیاده سازی کننده‌ی IEquatable به صورت خودکار و در پشت صحنه.

Lowering چه زمانی رخ می‌دهد؟
Lowering جزئی از عملیات صورت گرفته‌ی در حین کامپایل است. زمانیکه دستور dotnet build را صادر می‌کنیم، ابتدا semantics & syntax analysis صورت می‌گیرد تا اگر برای مثال خطای دستوری وجود دارد، مشخص شود. سپس کدها به CIL یا Common intermediate language تبدیل می‌شوند. در حین این قسمت است که عملیات lowering نیز انجام می‌شود.
اگر علاقمند به مشاهده‌ی این کد #C ثانویه‌ی تولید شده‌ی توسط کامپایلر هستید، می‌توان از ابزار https://sharplab.io نیز استفاده کرد. برای مثال در سمت چپ آن کدهای زیر را قرار دهید:
using System;
using System.Collections.Generic;

var list = new List<int> { 1, 2 };

foreach(var item in list)
    Console.Write(item);
سپس در سمت راست آن، گزینه‌ی #Results C را انتخاب کنید تا بتوانید نمونه‌ی معادل تبدیل شده‌ی توسط کامپایلر را مشاهده نمائید.