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

ساختار درختی
در بسیاری از مواقع ما با گروهی از اشیاء یا داده‌هایی سر و کار داریم که هر کدام از آن‌ها به گروهی دیگر مرتبط هستند. در این حالت از ساختار خطی نمی‌توانیم برای توصیف این ارتباط استفاده کنیم. پس بهترین ساختار برای نشان دادن این ارتباط ساختار شاخه ای Branched Structure است.
یک ساختار درختی یا یک ساختار شاخه‌ای شامل المان‌هایی به اسم گره Node است. هر گره می‌تواند به یک یا چند گره دیگر متصل باشد و گاهی اوقات این اتصالات مشابه یک سلسه مراتب hierarchically می‌شوند.
درخت‌ها در برنامه نویسی جایگاه ویژه‌ای دارند به طوری که استفاده‌ی از آن‌ها در بسیاری از برنامه‌ها وجود دارد و بسیاری از مثال‌های واقعی پیرامون ما را پشتیبانی می‌کنند.
در نمودار زیر مثالی وجود دارد که در آن یک تیم نرم افزاری نمایش داده شده‌است. در اینجا هر یک از بخش‌ها وظایف و مسئولیت‌هایی را بر دوش خود دارند که این مسئولیت‌ها به صورت سلسله مراتبی در تصویر زیر نمایش داده شده‌اند.

ما در ساختار بالا متوجه می‌شویم که چه بخشی زیر مجموعه‌ی چه بخشی است و سمت بالاتر هر بخش چیست. برای مثال ما متوجه شدیم که مدیر توسعه دهندگان، "سرپرست تیم" است که خود نیز مادون "مدیر پروژه" است و این را نیز متوجه می‌شویم که مثلا توسعه دهنده‌ی شماره یک هیچ مادونی ندارد و مدیر پروژه در راس همه است و هیچ مدیر دیگری بالای سر او قرار ندارد.

اصطلاحات درخت
برای اینکه بیشتر متوجه روابط بین اشیا در این ساختار بشویم، به شکل زیر خوب دقت کنید:

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

در شکل بالا به هر یک از دایره‌ها یک گره Node می‌گویند و به هر خط ارتباط دهنده بین گره‌ها لبه Edge گفته می‌شود. گره‌های 19 و 21 و 14 زیر گره‌های گره 7 محسوب می‌شوند. گره‌هایی که به صورت مستقیم به زیر گره‌های خودشان اشاره می‌کنند را گره‌های والد Parent می‌گویند و زیرگره‌های 7 را گره‌های فرزند ChildNodes. پس با این حساب می‌توانیم بگوییم گره‌های 1 و 12 و 31 را هم فرزند گره 19 هستند و گره 19 والد آن هاست. همچنین گره‌های یک والد را مثل 19 و 21 و 14 که والد مشترک دارند، گره‌های خواهر و برادر یا حتی همنژاد Sibling می‌گوییم. همچنین ارتباط بین گره 7 و گره‌های سطح دوم  و الی آخر یعنی 1 و 12 و 31 و 23 و 6 را که والد بودن آن به صورت غیر مستقیم است را جد یا ancestor می‌نامیم و نوه‌ها و نتیجه‌های آن‌ها را نسل descendants.

ریشه Root: به گره‌ای می‌گوییم که هیچ والدی ندارد و خودش در واقع اولین والد محسوب می‌شود؛ مثل گره 7.

برگ  Leaf: به گره‌هایی که هیچ فرزندی ندارند، برگ می‌گوییم. مثال گره‌های 1 و12 و 31 و 23 و 6

گره‌های داخلی Internal Nodes: گره هایی که نه برگ هستند و نه ریشه. یعنی حداقل یک فرزند دارند و خودشان یک گره فرزند محسوب می‌شوند؛ مثل گره‌های 19 و 14.

مسیر Path: راه رسیدن از یک گره به گره دیگر را مسیر می‌گویند. مثلا گره‌های 1 و 19 و 7 و 21 به ترتیب یک مسیر را تشکیل می‌دهند ولی گره‌های 1 و 19 و 23 از آن جا که هیچ جور اتصالی بین آن‌ها نیست، مسیری را تشکیل نمی‌دهند.

طول مسیر Length of Path: به تعداد لبه‌های یک مسیر، طول مسیر می‌گویند که می‌توان از تعداد گره‌ها -1 نیز آن را به دست آورد. برای نمونه : مسیر 1 و19 و 7 و 21 طول مسیرشان 3 هست.

عمق Depth: طول مسیر یک گره از ریشه تا آن گره را عمق درخت می‌گویند. عمق یک ریشه همیشه صفر است و برای مثال در درخت بالا، گره 19 در عمق یک است و برای گره 23 عمق آن 2 خواهد بود.

تعریف خود درخت Tree: درخت یک ساختار داده برگشتی recursive است که شامل گره‌ها و لبه‌ها، برای اتصال گره‌ها به یکدیگر است.

جملات زیر در مورد درخت صدق می‌کند:

  • هر گره می‌تواند فرزند نداشته باشد یا به هر تعداد که می‌خواهد فرزند داشته باشد.
  • هر گره یک والد دارد و تنها گره‌ای که والد ندارد، گره ریشه است (البته اگر درخت خالی باشد هیچ گره ای وجود ندارد).
  • همه گره‌ها از ریشه قابل دسترسی هستند و برای دسترسی به گره مورد نظر باید از ریشه تا آن گره، مسیری را طی کرد.
ار تفاع درخت Height: به حداکثر عمق یک درخت، ارتفاع درخت می‌گویند.
درجه گره Degree: به تعداد گره‌های فرزند یک گره، درجه آن گره می‌گویند. در درخت بالا درجه گره‌های 7 و 19 سه است. درجه گره 14 دو است و درجه برگ‌ها صفر است.
ضریب انشعاب Branching Factor: به حداکثر درجه یک گره در یک درخت، ضریب انشعاب آن درخت گویند.

پیاده سازی درخت

برای پیاده سازی یک درخت، از دو کلاس یکی جهت ساخت گره که حاوی اطلاعات است <TreeNode<T و دیگری جهت ایجاد درخت اصلی به همراه کلیه متدها و خاصیت هایش <Tree<T کمک می‌‌گیریم.

public class TreeNode<T>
{
    // شامل مقدار گره است
    private T value;
 
    // مشخص می‌کند که آیا گره والد دارد یا خیر
    private bool hasParent;
 
    // در صورت داشتن فرزند ، لیست فرزندان را شامل می‌شود
    private List<TreeNode<T>> children;
 
    /// <summary>سازنده کلاس </summary>
    /// <param name="value">مقدار گره</param>
    public TreeNode(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
        this.value = value;
        this.children = new List<TreeNode<T>>();
    }
 
    /// <summary>خاصیتی جهت مقداردهی گره</summary>
    public T Value
    {
        get
        {
            return this.value;
        }
        set
        {
            this.value = value;
        }
    }
 
    /// <summary>تعداد گره‌های فرزند را بر میگرداند</summary>
    public int ChildrenCount
    {
        get
        {
            return this.children.Count;
        }
    }
 
    /// <summary>به گره یک فرزند اضافه می‌کند</summary>
    /// <param name="child">آرگومان این متد یک گره است که قرار است به فرزندی گره فعلی در آید</param>
    public void AddChild(TreeNode<T> child)
    {
        if (child == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        if (child.hasParent)
        {
            throw new ArgumentException(
                "The node already has a parent!");
        }
 
        child.hasParent = true;
        this.children.Add(child);
    }
 
    /// <summary>
    /// گره ای که اندیس آن داده شده است بازگردانده می‌شود
    /// </summary>
    /// <param name="index">اندیس گره</param>
    /// <returns>گره بازگشتی</returns>
    public TreeNode<T> GetChild(int index)
    {
        return this.children[index];
    }
}
 
/// <summary>این کلاس ساختار درخت را به کمک کلاس گره‌ها که در بالا تعریف کردیم میسازد</summary>
/// <typeparam name="T">نوع مقادیری که قرار است داخل درخت ذخیره شوند</typeparam>
public class Tree<T>
{
    // گره ریشه
    private TreeNode<T> root;
 
    /// <summary>سازنده کلاس</summary>
    /// <param name="value">مقدار گره اول که همان ریشه می‌شود</param>
    public Tree(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        this.root = new TreeNode<T>(value);
    }
 
    /// <summary>سازنده دیگر برای کلاس درخت</summary>
    /// <param name="value">مقدار گره ریشه مثل سازنده اول</param>
    /// <param name="children">آرایه ای از گره‌ها که فرزند گره ریشه می‌شوند</param>
    public Tree(T value, params Tree<T>[] children)
        : this(value)
    {
        foreach (Tree<T> child in children)
        {
            this.root.AddChild(child.root);
        }
    }
 
    /// <summary>
    /// ریشه را بر میگرداند ، اگر ریشه ای نباشد نال بر میگرداند
    /// </summary>
    public TreeNode<T> Root
    {
        get
        {
            return this.root;
        }
    }
 
    /// <summary>پیمودن عرضی و نمایش درخت با الگوریتم دی اف اس </summary>
    /// <param name="root">ریشه (گره ابتدایی) درختی که قرار است پیمایش از آن شروع شود</param>
    /// <param name="spaces">یک کاراکتر جهت جداسازی مقادیر هر گره</param>
    private void PrintDFS(TreeNode<T> root, string spaces)
    {
        if (this.root == null)
        {
            return;
        }
 
        Console.WriteLine(spaces + root.Value);
 
        TreeNode<T> child = null;
        for (int i = 0; i < root.ChildrenCount; i++)
        {
            child = root.GetChild(i);
            PrintDFS(child, spaces + "   ");
        }
    }
 
    /// <summary>متد پیمایش درخت به صورت عمومی که تابع خصوصی که در بالا توضیح دادیم را صدا می‌زند</summary>
    public void TraverseDFS()
    {
        this.PrintDFS(this.root, string.Empty);
    }
}
 
/// <summary>
/// کد استفاده از ساختار درخت
/// </summary>
public static class TreeExample
{
    static void Main()
    {
        // Create the tree from the sample
        Tree<int> tree =
            new Tree<int>(7,
                new Tree<int>(19,
                    new Tree<int>(1),
                    new Tree<int>(12),
                    new Tree<int>(31)),
                new Tree<int>(21),
                new Tree<int>(14,
                    new Tree<int>(23),
                    new Tree<int>(6))
            );
 
        // پیمایش درخت با الگوریتم دی اف اس یا عمقی
        tree.TraverseDFS();
 
        // خروجی
        // 7
        //       19
        //        1
        //        12
        //        31
        //       21
        //       14
        //        23
        //        6
    }
}
کلاس TreeNode وظیفه‌ی ساخت گره را بر عهده دارد و با هر شیء‌ایی که از این کلاس می‌سازیم، یک گره ایجاد می‌کنیم که با خاصیت Children و متد AddChild آن می‌توانیم هر تعداد گره را که می‌خواهیم به فرزندی آن گره در آوریم که باز خود آن گره می‌تواند در خاصیت Children یک گره دیگر اضافه شود. به این ترتیب با ساخت هر گره و ایجاد رابطه از طریق خاصیت children هر گره درخت شکل می‌گیرد. سپس گره والد در ساختار کلاس درخت Tree قرار می‌گیرد و این کلاس شامل متدهایی است که می‌تواند روی درخت، عملیات پردازشی چون پیمایش درخت را انجام دهد.


پیمایش درخت به روش عمقی (DFS (Depth First Search

هدف از پیمایش درخت ملاقات یا بازبینی (تهیه لیستی از همه گره‌های یک درخت) تنها یکبار هر گره در درخت است. برای این کار الگوریتم‌های زیادی وجود دارند که ما در این مقاله تنها دو روش DFS و BFS را بررسی می‌کنیم.

روش DFS: هر گره‌ای که به تابع بالا بدهید، آن گره برای پیمایش، گره ریشه حساب خواهد شد و پیمایش از آن آغاز می‌گردد. در الگوریتم DFS روش پیمایش بدین گونه است که ما از گره ریشه آغاز کرده و گره ریشه را ملاقات می‌کنیم. سپس گره‌های فرزندش را به دست می‌آوریم و یکی از گره‌ها را انتخاب کرده و دوباره همین مورد را رویش انجام می‌دهیم تا نهایتا به یک برگ برسیم. وقتی که به برگی می‌رسیم یک مرحله به بالا برگشته و این کار را آنقدر تکرار می‌کنیم تا همه‌ی گره‌های آن ریشه یا درخت پیمایش شده باشند.

همین درخت را در نظر بگیرید:


 پیمایش درخت را از گره 7 آغاز می‌کنیم و آن را به عنوان ریشه در نظر می‌گیریم. حتی می‌توانیم پیمایش را از گره مثلا 19 آغاز کنیم و آن را برای پیمایش ریشه در نظر بگیریم ولی ما از همان 7 پیمایش را آغاز می‌کنیم:

ابتدا گره 7 ملاقات شده و آن را می‌نویسیم. سپس فرزندانش را بررسی می‌کنیم که سه فرزند دارد. یکی از فرزندان مثل گره 19 را انتخاب کرده و آن را ملاقات می‌کنیم (با هر بار ملاقات آن را چاپ می‌کنیم) سپس فرزندان آن را بررسی می‌کنیم و یکی از گره‌ها را انتخاب می‌کنیم و ملاقاتش می‌کنیم؛ برای مثال گره 1. از آن جا که گره یک، برگ است و فرزندی ندارد یک مرحله به سمت بالا برمی‌گردیم و برگ‌های 12 و 31 را هم ملاقات می‌کنیم. حالا همه‌ی فرزندان گره 19 را بررسی کردیم، بر می‌گردیم یک مرحله به سمت بالا و گره 21 را ملاقات می‌کنیم و از آنجا که گره 21 برگ است و فرزندی ندارد به بالا باز می‌گردیم و بعد گره 14 و فرزندانش 23 و 6 هم بررسی می‌شوند. پس ترتیب چاپ ما اینگونه می‌شود:

7-19-1-12-31-21-14-23-6


پیمایش درخت به روش (BFS (Breadth First Search 

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

7-19-21-14-1-12-31-23-6

اگر خوب دقت کنید می‌بینید که پیمایش سطحی است و هر سطح به ترتیب ملاقات می‌شود. به این الگوریتم، پیمایش موجی هم می‌گویند. دلیل آن هم این است که مثل سنگی می‌ماند که شما برای ایجاد موج روی دریاچه پرتاب می‌کنید.

برای این پیمایش از صف کمک گرفته می‌شود که مراحل زیر روی صف صورت می‌گیرد:

  • ریشه  وارد صف Q می‌شود.
  • دو مرحله زیر مرتبا تکرار می‌شوند:
  1. اولین گره صف به نام V را از Q در یافت می‌کنیم و آن را چاپ می‌کنیم.
  2. فرزندان گره V  را به صف اضافه می‌کنیم.
این نوع پیمایش، پیاده سازی راحتی دارد و همیشه نزدیک‌ترین گره‌ها به ریشه را می‌خواند و در هر مرحله گره‌هایی که می‌خواند از ریشه دورتر و دورتر می‌شوند.
مطالب
اشیاء تغییر ناپذیر (Immutable Object)
کلمه‌ی mutable به معنای تغییر پذیر و کلمه‌ی immutable به معنای تغیر ناپذیر در زبان انگلیسی تعریف شده‌اند. در دنیای IT این دو واژه نیز همین معنا را دارند. بطور مثال: یک رشته‌ی mutable، یعنی رشته‌ای که بتوان آن را تغییر داد و یک رشته‌ی immutable یعنی رشته‌ای که غیر قابل تغییر است.



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

تعریف اشیاء تغییر ناپذیر (Immutable Objects) :
این اشیاء، اشیائی هستند که بعد از بارگزاری در حافظه، به هیچ وجه نمی‌توانند اصلاح و یا تغییر کنند. نه از طریق خارجی (کاربران External) و نه از طریق داخلی (اعضای کلاس Internal).

چه زمانی از این اشیاء استفاده می‌کنیم؟
اشیاء تغییر ناپذیر برای داده‌های استاتیک استفاده می‌شوند و نمونه‌هایی از آن در بخش زیر لیست شده‌اند:

 • داده‌های اصلی (Master Data): یکی از بیشترین کاربرد‌های اشیاء تغییر ناپذیر، برای بارگذاری داده‌های اصلی است (کشور‌ها، واحد‌های پولی، استان ها) و داده‌هایی که به ندرت تغییر می‌کنند. این داده‌های اصلی بعد از بارگزاری در حافظه، دیگر تغییر نخواهند کرد.

 • اطلاعات پیکره‌بندی (Configuration Data): همه‌ی برنامه‌ها نیاز به اطلاعات پیکره‌بندی دارند. در دنیای برنامه‌های مایکروسافت، عموما این اطلاعات پیکره بندی را در فایل‌های web.config و App.config ذخیره می‌کنیم. این نوع اطلاعات بصورت یک شیء در حافظه بارگذاری می‌شوند و بعدا تغییر نخواهند کرد.

 • اشیاء Singleton: اشیاء singleton اشیائی هستند که تنها یک نمونه از آنها را می‌توان ایجاد کرد. در برنامه‌ها از این اشیاء برای اشتراک گذاشتن اطلاعات استاتیک استفاده می‌کنند. اگر این اطلاعات تغییر نکنند، یکی از گزینه‌ها، استفاده از اشیاء تغییر ناپذیر هستند.

چگونه می‌توان در سی شارپ اشیاء تغییر ناپذیر را ایجاد کنیم؟

اشیاء تغییر ناپذیر (immutable objects) تنها توسط کلاس‌های تغییر ناپذیر (immutable classes) می‌توانند ایجاد شوند.
برای ایجاد کلاسی تغییر ناپذیر، سه مرحله باید طی شود:
 1- حذف بلاک Set: همانطور که گفته شد، بخش Set از property ‌ها باید حذف شود. با حذف این بخش بعد از بارگزاری شیء در حافظه، دیگر نمی‌توان آن را تغییر داد:
public class Currency
{
  private string _currencyName;
  private string _countryName;

  public string CurrencyName
  {
     get { return _currencyName; }
  }

  public string CountryName
  {
     get { return _countryName; }
  }
}

 2- مهیا کردن پارامتر‌ها از طریق سازنده‌ی کلاس: با حذف بلاک set، راهی برای بارگزاری اطلاعات، در کلاس وجود ندارد. از این رو می‌توان از طریق پارامتر‌های سازنده‌ی کلاس، اطلاعات را به شیء ارسال کرد.
  private string _currencyName;
  private string _countryName;
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }

 3- تعریف متغیر‌های کلاس به صورت فقط خواندنی READONLY
در تعریف اولیه گفته شد که اشیاء immutable نه از طریق خارجی (کاربر) و «کمی فراتر» نه از طریق داخلی (اعضای کلاس) قابل تغییر نیستند. اما کلاس ایجاد شده را می‌توان بعد از ایجاد نمونه‌ای از آن، مجددا تغییر داد. کافی است یک متد به شکل زیر در آن تعریف کنیم و به‌راحتی وضعیت شیء را از طریق آن تغییر دهیم.
  public void DoSomthing()
  {
     _countryName = "somthing else";
  }

راه حل ارائه شده‌ی برای حل این موضوع، معرفی متغیر‌ها به صورت readonly می‌باشد. متغیرهایی که بصورت فقط خواندنی تعریف می‌شوند، تنها از طریق سازنده‌ی شیء می‌توانند مقداردهی اولیه شوند.
باز طراحی نهایی کلاس Currency  به صورت زیر است:
 public class Currency
{
  private readonly string _currencyName;
  private readonly string _countryName;
  public string CurrencyName
  {
    get { return _currencyName; }
  }

  public string CountryName
  {
    get { return _countryName; }
  }
  public Currency(string paramCurrencyName,string paramCountryName)
  {
     _currencyName= paramCurrencyName;
     _countryName = paramCountryName;
  }
}
مطالب
آشنایی با 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 دیگری کوچ کرد؛ بدون اینکه نیازی به بازنویسی کل برنامه وجود داشته باشد.

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

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


مطالب دوره‌ها
بررسی قسمت‌های مختلف قالب پروژه WPF Framework تهیه شده
پس از ایجاد یک Solution جدید توسط قالب WPF Framework، هشت پروژه به صورت خودکار اضافه خواهند شد:



1) پروژه ریشه که بسته به نامی که در ابتدای کار انتخاب می‌کنید، تغییر نام خواهد یافت.
برای مثال اگر نام وارد شده در ابتدای کار MyWpfFramework باشد، این پروژه ریشه نیز، MyWpfFramework نام خواهد داشت. از آن صرفا جهت افزودن Viewهای برنامه استفاده می‌کنیم. کلیه Viewها در پوشه View قرار خواهند گرفت و با توجه به ساختار خاصی که در اینجا انتخاب شده، این Viewها باید از نوع Page انتخاب شوند تا با سیستم راهبری فریم ورک هماهنگ کار کنند.
در داخل پوشه Views، هر بخش از برنامه را می‌توان داخل یک زیر پوشه قرار داد. برای مثال قسمت Login سیستم، دارای سه صفحه ورود، نمایش پیام خوش آمد و نمایش صفحه عدم دسترسی است.
متناظر با هر Page اضافه شده، در پروژه MyWpfFramework.Infrastructure یک ViewModel در صورت نیاز اضافه خواهد شد. قرار داد ما در اینجا ترکیب نام View به علاوه کلمه ViewModel است. برای مثال اگر نام View اضافه شده به پروژه ریشه برنامه، LoginPage است، نام ViewModel متناظر با آن باید LoginPageViewModel باشد تا به صورت خودکار توسط برنامه ردیابی و وهله سازی گردد.
این پروژه از کتابخانه MahApps.Metro استفاده می‌کند و اگر به فایل MainWindow.xaml.cs آن مراجعه کنید، ارث بری پنجره اصلی برنامه را از کلاس MetroWindow مشاهده خواهید نمود. این فایل‌ها نیازی به تغییر خاصی نداشته و به همین نحو در این قالب قابل استفاده هستند.
و در پوشه Resources آن یک سری قلم و آیکون را می‌توانید مشاهده نمائید.

2) پروژه MyWpfFramework.Common
در این پروژه کلاس‌هایی قرار می‌گیرند که قابلیت استفاده در انواع و اقسام پروژه‌های WPF را دارند و الزاما وابسته به پروژه جاری نیستند. یک سری کلاس‌های کمکی در این پروژه Common قرار گرفته‌اند و قسمت‌های مختلف سیستم را تغذیه می‌کنند؛ مانند خواندن اطلاعات از فایل کانفیگ، هش کردن کلمه عبور، یک سری متد عمومی برای کار با EF، کلاس‌های عمومی مورد نیاز در حین استفاده از الگوی MVVM، اعتبارسنجی و امثال آن.
در این پروژه از کلاس PageAuthorizationAttribute آن جهت مشخص سازی وضعیت دسترسی به صفحات تعریف شده در پروژه ریشه استفاده خواهد شد.
نمونه‌ای از آن‌را برای مثال با مراجعه به سورس صفحه About.xaml.cs می‌توانید مشاهده کنید که در آن AuthorizationType.AllowAnonymous تنظیم شده و به این ترتیب تمام کاربران اعتبارسنجی نشده می‌توانند این صفحه را مشاهده کنند.
همچنین در این پروژه کلاس BaseViewModel قرار دارد که جهت مشخص سازی کلیه کلاس‌های ViewModel برنامه باید مورد استفاده قرار گیرد. سیستم طراحی شده، به کمک این کلاس پایه است که می‌تواند به صورت خودکار ViewModelهای متناظر با Viewها را یافته و وهله سازی کند (به همراه تمام وابستگی‌های تزریق شده به آن‌ها).
به علاوه کلاس DataErrorInfoBase آن برای یکپارچه سازی اعتبارسنجی با EF طراحی شده است. اگر به کلاس BaseEntity.cs مراجعه کنید که در پروژه MyWpfFramework.DomainClasses قرار دارد، نحوه استفاده آن‌را ملاحظه خواهید نمود. به این ترتیب حجم بالایی از کدهای تکرای، کپسوله شده و قابلیت استفاده مجدد را پیدا می‌کنند.
قسمت‌های دیگر پروژه Common، برای ثبت وقایع برنامه مورد استفاده قرار می‌گیرند. استفاده از آن‌ها را در فایل App.xaml.cs پروژه ریشه برنامه ملاحظه می‌کنید و نیاز به تنظیم خاص دیگری در اینجا وجود ندارد.

3) پروژه MyWpfFramework.DataLayer
کار تنظیمات EF در اینجا انجام می‌شود (و قسمت عمده‌ای از آن انجام شده است). تنها کاری که در آینده برای استفاده از آن نیاز است انجام شود، مراجعه به کلاس MyWpfFrameworkContext.cs و افزودن DbSetهای لازم است. همچنین اگر نیاز به تعریف نگاشت‌های اضافه‌تری وجود داشت، می‌توان از پوشه Mappings آن استفاده کرد.
در این پروژه الگوی واحد کار پیاده سازی شده است و همچنین سعی شده تمام کلاس‌های آن دارای کامنت‌های کافی جهت توضیح قسمت‌های مختلف باشند.
کلاس MyDbContextBase به اندازه کافی غنی سازی شده‌است، تا در وقت شما، در زمینه تنظیم مباحثی مانند اعتبارسنجی و نمایش پیغام‌های لازم به کاربر، یک دست سازی ی و ک ورودی در برنامه و بسیاری از نکات ریز دیگر صرفه جویی شود.
در اینجا از خاصیت ContextHasChanges جهت بررسی وضعیت Context جاری و نمایش پیغامی به کاربر در مورد اینکه آیا مایل هستید تغییرات را ذخیره کنید یا خیر استفاده می‌شود.
در متد auditFields آن یک سری خاصیت کلاس BaseEntity که پایه تمامی کلاس‌های Domain model برنامه خواهد بود به صورت خودکار مقدار دهی می‌شوند. مثلا این رکورد را چه کسی ثبت کرده یا چه کسی ویرایش و در چه زمانی. به این ترتیب دیگر نیازی نیست تا در برنامه نگران تنظیم و مقدار دهی آن‌ها بود.
کلاس MyWpfFrameworkMigrations به حالت AutomaticMigrationsEnabled تنظیم شده است و ... برای یک برنامه دسکتاپ WPF کافی و مطلوب است و ما را از عذاب به روز رسانی دستی ساختار بانک اطلاعاتی برنامه با تغییرات مدل‌ها، رها خواهد ساخت. عموما برنامه‌های دسکتاپ پس از طراحی، آنچنان تغییرات گسترده‌ای ندارند و انتخاب حالت Automatic در اینجا می‌تواند کار توزیع آن‌را نیز بسیار ساده کند. از این جهت که بانک اطلاعاتی انتخابی از نوع SQL Server CE نیز عمدا این هدف را دنبال می‌کند: عدم نیاز به نگهداری و وارد شدن به جزئیات نصب یک بانک اطلاعاتی بسیار پیشرفته مانند نگارش‌های کامل SQL Server. هرچند زمانیکه با EF کار می‌کنیم، سوئیچ به بانک‌های اطلاعاتی صرفا با تغییر رشته اتصالی فایل app.config برنامه اصلی و مشخص سازی پروایدر مناسب قابل انجام خواهد بود.
در فایل MyWpfFrameworkMigrations، توسط متد addRolesAndAdmin کاربر مدیر سیستم در آغاز کار ساخت بانک اطلاعاتی به صورت خودکار افزوده خواهد شد.


4) پروژه MyWpfFramework.DomainClasses
کلیه کلاس‌های متناظر با جداول بانک اطلاعاتی در پروژه MyWpfFramework.DomainClasses قرار خواهند گرفت. نکته مهمی که در اینجا باید رعایت شود، مزین کردن این کلاس‌ها به کلاس پایه BaseEntity می‌باشد که نمونه‌ای از آن‌را در کلاس User پروژه می‌توانید ملاحظه کنید.
BaseEntity چند کار را با هم انجام می‌دهد:
- اعمال خودکار DataErrorInfoBase جهت یکپارچه سازی سیستم اعتبارسنجی EF با WPF (برای مثال به این ترتیب خطاهای ذکر شده در ویژگی‌های خواص کلاس‌ها توسط WPF نیز خوانده خواهند شد)
- اعمال ImplementPropertyChanged به کلاس‌های دومین برنامه. به این ترتیب برنامه کمکی Fody که کار Aspect oriented programming را انجام می‌دهد، اسمبلی برنامه را ویرایش کرده و متدها و تغییرات لازم جهت پیاده سازی INotifyPropertyChanged را اضافه می‌کند. به این ترتیب به کلاس‌های دومین بسیار تمیزی خواهیم رسید با حداقل نیاز به تغییرات و نگهداری ثانویه.
- فراهم آوردن فیلدهای مورد نیاز جهت بازرسی سیستم؛ مانند اینکه چه کسی یک رکورد را ثبت کرده یا ویرایش و در چه زمانی

نقش‌های سیستم در کلاس SystemRole تعریف می‌شوند. به ازای هر نقش جدیدی که نیاز بود، تنها کافی است یک خاصیت bool را در اینجا اضافه کنید. سپس نام این خاصیت در ویژگی PageAuthorizationAttribute به صورت خودکار قابل استفاده خواهد بود. برای مثال به پروژه ریشه مراجعه و به فایل AddNewUser.xaml.cs دقت کنید؛ چنین تعریفی را در بالای کلاس مرتبط مشاهده خواهید کرد:
 [PageAuthorization(AuthorizationType.ApplyRequiredRoles, "IsAdmin, CanAddNewUser")]
در اینجا AuthorizationType سه حالت را می‌تواند داشته باشد:
    /// <summary>
    /// وضعیت اعتبار سنجی صفحه را مشخص می‌کند
    /// </summary>
    public enum AuthorizationType
    {
        /// <summary>
        /// همه می‌توانند بدون اعتبار سنجی، دسترسی به این صفحات داشته باشند
        /// </summary>
        AllowAnonymous,

        /// <summary>
        /// کاربران وارد شده به سیستم بدون محدودیت به این صفحات دسترسی خواهند داشت
        /// </summary>
        FreeForAuthenticatedUsers,

        /// <summary>
        /// بر اساس نام نقش‌هایی که مشخص می‌شوند تصمیم گیری خواهد شد
        /// </summary>
        ApplyRequiredRoles
    }
اگر حالت ApplyRequiredRoles را انتخاب کردید، در پارامتر اختیاری دوم ویژگی PageAuthorization نیاز است نام یک یا چند خاصیت کلاس SystemRole را قید کنید. بدیهی است کاربر متناظر نیز باید دارای این نقش‌ها باشد تا بتواند به این صفحه دسترسی پیدا کند، یا خیر.


5) پروژه MyWpfFramework.Models
در پروژه MyWpfFramework.Models کلیه Modelهای مورد استفاده در UI که الزاما قرار نیست در بانک اطلاعاتی قرارگیرند، تعریف خواهند شد. برای نمونه مدل صفحه لاگین در آن قرار دارد و ذکر دو نکته در آن حائز اهمیت است:
 [ImplementPropertyChanged] // AOP
public class LoginPageModel : DataErrorInfoBase
- ویژگی ImplementPropertyChanged کار پیاده سازی INotifyPropertyChanged را به صورت خودکار سبب خواهد شد.
- کلاس پایه DataErrorInfoBase سبب می‌شود تا مثلا در اینجا اگر از ویژگی Required استفاده کردید، اطلاعات آن توسط برنامه خوانده شود و با WPF یکپارچه گردد.


6) پروژه MyWpfFramework.Infrastructure.csproj
در پروژه MyWpfFramework.Infrastructure.csproj تعاریف ViewModelهای برنامه اضافه خواهند شد.
این پروژه دارای یک سری کلاس پایه است که تنظیمات IoC برنامه را انجام می‌دهد. برای مثال FrameFactory.cs آن یک کنترل Frame جدید را ایجاد کرده است که کار تزریق وابستگی‌ها را به صورت خودکار انجام خواهد داد. فایل IocConfig آن جایی است که کار سیم کشی کلاس‌های لایه سرویس و اینترفیس‌های متناظر با آن‌ها انجام می‌شود. البته پیش فرض‌های آن را اگر رعایت کنید، نیازی به تغییری در آن نخواهید داشت. برای مثال در آن scan.TheCallingAssembly قید شده است. در این حالت اگر نام کلاس لایه سرویس شما Test و نام اینترفیس متناظر با آن ITest باشد، به صورت خودکار به هم متصل خواهند شد.
همانطور که پیشتر نیز عنوان شد، در پوشه ViewModels آن، به ازای هر View یک ViewModel خواهیم داشت که نام آن مطابق قرار داد، نام View مدنظر به همراه کلمه ViewModel باید درنظر گرفته شود تا توسط برنامه شناخته شده و مورد استفاده قرار گیرد. همچنین هر ViewModel نیز باید دارای کلاس پایه BaseViewModel باشد تا توسط IoC Container برنامه جهت تزریق وابستگی‌های خودکار در سازنده‌های کلاس‌ها شناسایی شده و وهله سازی گردد.


7) پروژه MyWpfFramework.ServiceLayer
کلیه کلاس‌های لایه سرویس که منطق تجاری برنامه را پیاده سازی می‌کنند (خصوصا توسط EF) در این لایه قرار خواهند گرفت. در اینجا دو نمونه سرویس کاربران و سرویس عمومی AppContextService را ملاحظه می‌کنید.
سرویس AppContextService قلب سیستم اعتبارسنجی سیستم است و در IocConfig برنامه به صورت سینگلتون تعریف شده است. چون در برنامه‌های دسکتاپ در هر لحظه فقط یک نفر وارد سیستم می‌شود و نیاز است تا پایان طول عمر برنامه، اطلاعات لاگین و نقش‌های او را در حافظه نگه داری کرد.


8) پروژه MyWpfFramework.Tests
یک پروژه خالی Class library هم در اینجا جهت تعریف آزمون‌های واحد سیستم درنظر گرفته شده است.

 
مطالب
معرفی سرویس‌های ارائه شده توسط شرکت‌های گوگل، آمازون و مایکروسافت در قالب رایانش ابری - قسمت اول

رایانش ابری مفهوم نسبتا جدیدی در عرصه‌ی فناوری اطلاعات است و در حال گسترش می­‌باشد. به طور خلاصه رایانش ابری به همه چیز اعم از برنامه کاربردی( Application )، سکو ی ( Platform ) توسعه نرم افزار، سخت افزار و زیرساخت، به عنوان سرویس نگاه می‌کند. زیرساخت های موجود در مراکز داده( Data Center ) به انضمام  نرم‌افزارهایی که در آن قرار دارند، مجموعه‌ه­ایی را تشکیل می­‌دهند که ابر نامیده می­‌شود. به عبارت ساده­‌تر رایانش ابری یعنی استفاده­ اشتراکی از برنامه­‌ها و منابع یک محیط شبکه­‌ای برای انجام یک کار، بدون این که مالکیت، مدیریت منابع شبکه و سخت­ افزار و برنامه­‌ها، برای استفاده کننده مهم باشد. در رایانش ابری منابع کامپیوترها، برای انجام یک کار استفاده می‌شوند و داده­‌های مربوط به پردازش، در هیچ کدام از کامپیوترهای شخصی ذخیره نمی­‌شوند، بلکه در جای دیگری در داخل همان منابع شبکه، ذخیره می­‌شوند تا در زمان و مکان دیگری قابل دسترسی باشند.

بر همین اساس شرکت های پیشرو در زمینه فناوری اطلاعات به ارائه سرویس هایی تحت عنوان خدمات رایانش ابری پرداخته اند و هدف از این سری مطالب ارائه شده، شرح مختصری بر سرویس‌های ارائه شده می باشد. در قسمت اول به معرفی سرویسهای شرکت گوگل پرداخته می شود و در قسمت‌های بعدی، سرویس‌های  شرکت‌های مایکروسافت و آمازون معرفی می‌گردد.

 سرویس­‌های رایانش ابری گوگل، در زیر دو چتر قرار دارند. گروه اول شامل مجموعه گسترده­ای از برنامه­‌های محبوب گوگل مانند Google Doc ،   Google Health ، Google Mail ، Google Earth هستند که با کلیک بر روی گزینه More و Even More که در بالای صفحه اصلی گوگل قرار دارند، می­توان به آن­ها دسترسی پیدا کرد.

دومین محصول مبتنی بر ابر گوگل، ابزار توسعه PaaS گوگل است. این سکو در سال 2008 برای توسعه برنامه‌­های تحت وب، با استفاده از زیرساخت گوگل به نام موتور Google App  معرفی شد. هدف از آن قادر ساختن توسعه دهندگان برای ساخت و استقرار برنامه­‌های وب بدون نگرانی از زیرساختی است که برنامه بر رویش اجرا می­شود. برنامه­‌های این موتور، با زبان­های سطح بالا به ویژه جاوا و پایتون و در چارچوب GAE نوشته می­‌شوند. گوگل به منظور گسترش این نوع برنامه­‌ها یک سطح رایگان مشخص از سرویس را ارائه می­دهد و زمانی که برنامه از سطح مشخصی از بار پردازشی، ذخیره­‌سازی و پهنای باند شبکه فراتر رفت، آنگاه شارژها بر مبنای میزان استفاده محاسبه می­شود.

برنامه­‌های GAE را باید به گونه­‌ای نوشت که با زیرساخت گوگل وفق یابند. این مسئله، باعث محدودیت برنامه­‌های قابل اجرا در GAE  می­گردد و علاوه بر آن، انتقال برنامه‌­ها به سکوی GAE و یا انتقال از این سکو به سایر سکو­های موجود دشوار می­شود.

از میان سرویس های ابری رایگان ارائه شده از سوی گوگل، به معرفی سرویس آنالیز گوگل بسنده کرده و تمرکز اصلی بر روی سکوی توسعه نرم‌افزاری این شرکت ( GAE ) می‌باشد.


Google Analytics

به اختصار GA نامیده می­شود و یک ابزار آماری است که تعداد و انواع بازدیدکنندگان وب­‌سایت و نحوه استفاده از وب­‌سایت را اندازه­‌گیری می­کند. این محصول بر روی بسته تحلیلی Urchin 5 که گوگل در سال 2006 آن را خریداری نمود، ساخته شده است. این سرویس رایگان عرضه می­شود و فرآیند تحلیل را با استفاده از یک قطعه کد جاوا اسکریپت به نام Google Analytics Tracking Code با پیاده‌سازی در تگ صفحه وب انجام می­‌شود.

این کد با اولین بارگذاری صفحه در سیستم­ کاربران، به جمع آوری اطلاعات مورد نیاز پرداخته و برای پردازش به سرورهای GA باز پس می­فرستد. این کد با کمک Cookie مرورگر اطلاعات مورد نیاز را جمع آوری می­نماید.

سرویس Google Analytics


آشنایی با Google App Engine

GAE یک سکو به عنوان سرویس می­باشد و مبتنی بر ابر گوگل است و بر روی زیر ساخت­ گوگل مستقر شده است. 

این سرویس توسعه دهندگان را قادر می­سازد تا برنامه‌­های وب ایجاد کرده و بر روی سرور­های گوگل مستقر سازند و گوگل مدیریت زیرساخت را بر عهده گیرد و اعمالی مانند نظارت، برطرف کردن اشکالات احتمالی، خوشه بندی، مدیریت وهله‌سازی ماشین‌های مجازی و غیره را انجام دهد. برای اجرای یک برنامه در GAE ابتدا باید استانداردهای سکوی گوگل رعایت شود. این استانداردها دامنه برنامه­‌هایی که قابل اجرا می­باشند را بسیار محدود می‌­نماید و قابلیت حمل آن­ها را کاهش می­دهد.

محدودیت­‌هایی که این سکو ایجاد می­‌کند، با خود مزایایی را به همراه می­‌آورد که در زیر به آن­ها اشاره می­گردد:

  • وب سرویس­های پویا بر مبنای استانداردهای رایج
  • توسعه خودکار و توازن بار بین ماشین‌های وهله‌سازی شده که مورد استفاده وب سرویس است.
  • اعتبارسنجی با استفاده از API موجود در گوگل.
  • فضای ذخیره سازی ماندگار با قابلیت جستجو، مرتب سازی و مدیریت تراکنش.
  • صف کاری و زمان بندی کاری
  • محیط توسعه سمت مشتری( توسعه دهندگان ) برای شبیه سازی GAE در سیستم محلی.
  • پشتیبانی از محیط اجرا جاوا و پایتون.

هنگامی که یک برنامه در GAE مستقر گردید، با استفاده از نام دامنه دلخواه یا با استفاده از آدرس تجاری Google Apps قابل دستیابی است. موتور Google Apps در حال حاضر برنامه­‌هایی که در جاوا و پایتون نوشته شده است را پشتیبانی می­کند و علاوه بر آن از زبان­های ماشین مجازی جاوا و چندین چارچوب تحت وب پایتون که WSGI و CGI را پشتیبانی می­کنند نیز با محیط GAE سازگاری دارند.

برنامه­‌هایی که در GAE اجرا می­شوند از سیستم عامل مستقل هستند یا به گفته گوگل بر روی   Sand Box  اجرا می­شوند. این ویژگی GAE را قادر می­سازد، سیستم را بهینه کند تا تقاضاهای وب، با بار ترافیکی فعلی منطبق شوند. همچنین برنامه­‌ها را قادر می­سازد با امنیت بالاتری کار کنند، زیرا تنها می­توانند به کامپیوترهایی متصل شوند که آدرس‌های مشخصی دارند و سرویس­ها را با استفاده از پروتکل Http و یا Https از پورت­های شناخته شده پاسخ دهند. از طرف دیگر برنامه‌­ها نیز به این میزان محدود شده که تنها فایل­ها را بخوانند. آن‌ها حق نوشتن فایل به صورت مستقیم بر روی سیستم­ها را ندارند و برای دستیابی به داده، باید از ذخیره داده در Cache یا سرویس ماندگار دیگری استفاده نمایند.

GAE یک سیستم انبار داده توزیع شده دارد که از پرس و جوها و تراکنش­ها پشتیبانی می­نماید. این انبار داده غیر رابطه­‌ای است، اما اشیاء داده یا موجودیت­‌هایی که خصوصیات لازم را دارند، ذخیره می­‌نماید. به همین علت در پرس و جوها می­توان از فیلتر نوع موجودیت بهره برد و آن­ها را به ترتیب خصوصیات مرتب نمود.

در نهایت توجه به مدل قیمت‌گذاری گوگل قابل توجه است. گوگل برای تشویق توسعه دهندگان در نوشتن برنامه با استفاده از GAE ، استقرار و توسعه برنامه را تا میزان مشخصی از منابع رایگان کرده است و با عبور از مقدار رایگان باید هزینه را به ازای مصرف پرداخت نمود. بر اساس جدول ارائه شده در سایت شرکت گوگل به ازای تجاوز از میزان مصرف رایگان، سیستم هزینه گذاری بر اساس تعرفه‌های زیر، اقدام به محاسبه حق شارژ می‌نماید و بدیهی است برای آگاهی از آخرین تعرفه‌ها و کسب اطلاعات بیشتر، مراجعه به  صفحه سایت شرکت گوگل  توصیه می‌شود:

  • مبلغ به ازای هر یک ساعت استفاده از CPU معادل 0.08 دلار
  • داده ذخیره شده به ازای هر گیگابایت در ماه معادل 0.18 دلار
  • پهنای باند خروجی به ازای هر گیگابایت معادل 0.12 دلار
  • پهنای باند ورودی رایگان
  • هزینه دریافت هر ایمیل معادل 0.0001 دلار 

به منظور ذخیره اطلاعات در منبع داده پایدار، از API استفاده می­گردد که به ازای تعداد تراکنش­هایی که تبادل می­گردد، هزینه پرداخت می­شود. از آنجایی که بنا به تعداد تبادلات و نوع حافظه پایداری که استفاده می­گردد، هزینه متغیر است، خواننده محترم برای رویت لیست مذکور به منبع ذکر شده، ارجاع داده می­شود.


  منبع سهمیه 
سهمیه پیش فرض رایگان به ازای هر برنامه   
  مصرف  CPU 
  28 ساعت به ازای هر برنامه در روز 
  منبع ذخیره پایدار داده 
1 گیگابایت به ازای هر برنامه در ماه
  پهنای باند ورودی 
  1 گیگابایت به ازای هر برنامه در روز 
  پهنای باند خروجی 
  1 گیگابایت به ازای هر برنامه در روز 
  تراکنش با منبع داده  Datastore 
  50 هزار تراکنش برای خواندن و نوشتن به ازای هر برنامه در ماه 
  تراکنش با منبع داده  Blobstore 
  5 گیگابایت به ازای هر برنامه در روز 
  ایمیل دریافتی 
  100 دریافت به ازای هر برنامه در روز 

مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت هشتم - دریافت اطلاعات از سرور
اغلب برنامه‌های AngularJS 2.0، اطلاعات خود را از طریق پروتکل HTTP، از سرور دریافت می‌کنند. برنامه یک درخواست Get را صادر کرده و سپس سرور پاسخ مناسبی را ارائه می‌دهد.


مقدمه‌ای بر RxJS

اگر به پیشنیازهای نصب AngularJS 2.0 در قسمت اول این سری دقت کرده باشید، یکی از موارد آن، RxJS است:
"dependencies": {
    "rxjs": "5.0.0-beta.2"
 },
یک Observable، آرایه‌ای است که اعضای آن به صورت غیر همزمان (asynchronously) در طول زمان دریافت می‌شوند. برای مثال پس از شروع یک عملیات async، ابتدا عنصر اول آرایه دریافت می‌شود، پس از مدتی عنصر دوم و در آخر عنصر سوم آن. به همین جهت از Observable‌ها برای مدیریت داده‌های async مانند دریافت اطلاعات از یک وب سرور، استفاده می‌شود.
قرار است Observableها به ES 2016 یا نگارش پس از ES 6 اضافه شوند و یکی از پیشنهادات آن هستند. اما هم اکنون AngularJS 2.0 از این امکان، توسط یک کتابخانه‌ی ثالث، به نام reactive extensions یا Rx، استفاده می‌کند. از RxJS در سرویس HTTP و همچنین مدیریت سیستم رخدادهای AngularJS 2.0 استفاده می‌شود. Observableها امکانی را فراهم می‌کنند تا به ازای دریافت هر اطلاعات async از سرور، بتوان توسط رخداد‌هایی از وقوع آن‌ها مطلع شد.

در نگارش قبلی AngularJS از Promises برای مدیریت اعمال غیرهمزمان استفاده می‌شد. Observableها تفاوت‌های قابل ملاحظه‌ای با Promises دارند:
- یک Promise تنها یک مقدار، یا خطا را بر می‌گرداند؛ اما یک Observable چندین مقدار را در طول یک بازه‌ی زمانی باز می‌گرداند.
- برخلاف Promises، می‌توان عملیات یک Observable را لغو کرد.
- Observableها از عملگرهایی مانند map، reduce، filter و غیره نیز پشتیبانی می‌کنند.

البته باید عنوان کرد که هنوز هم می‌توان از Promises در صورت تمایل در AngularJS 2.0 نیز استفاده کرد.


تنظیمات اولیه‌ی کار با RxJS در AngularJS 2.0

برای استفاده از RxJS در AngularJS 2.0، مراحلی مانند افزودن مدخل اسکریپت http.dev.js، ثبت پروایدر HTTP و importهای لازم، باید طی شوند که در ادامه آن‌ها را بررسی خواهیم کرد:
الف) سرویس HTTP جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن، باید به صفحه‌ی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفته‌است:
 <!-- Required for http -->
<script src="~/node_modules/angular2/bundles/http.dev.js"></script>
این تعریف در فایل Views\Shared\_Layout.cshtml (و یا index.html) پروژه‌ی جاری موجود است. همچنین در این صفحه، مدخل Rx.js نیز ذکر شده‌است.

ب) اکنون فایل app.component.ts را گشوده و سرویس HTTP را به آن اضافه می‌کنیم. با نحوه‌ی ثبت سرویس‌ها در قسمت قبل آشنا شدیم:
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import 'rxjs/Rx';   // Load all features
 
import { ProductListComponent } from './products/product-list.component';
import { ProductService } from './products/product.service';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <pm-products></pm-products>
    </div>
    `,
    directives: [ProductListComponent],
    providers: [
        ProductService,
        HTTP_PROVIDERS
    ]
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
از آنجائیکه می‌خواهیم سرویس HTTP، در تمام کامپوننت‌های برنامه در دسترس باشد، آن‌را در بالاترین سطح سلسه مراتب کامپوننت‌های موجود، یا همان کامپوننت ریشه‌ی سایت، ثبت و معرفی می‌کنیم. بنابراین سرویس توکار HTTP یا HTTP_PROVIDERS به لیست پروایدرها، اضافه شده‌است.

ج) پس از آن نیاز است importهای متناظر نیز به ابتدای ماژول فعلی، جهت شناسایی این سرویس و همچنین امکانات rx.js اضافه شوند.
تعریف 'import 'rxjs/Rx به این شکل، به module loader اعلام می‌کند که این کتابخانه را بارگذاری کن، اما چیزی را import نکن. هنگامیکه این کتابخانه بارگذاری می‌شود، کدهای جاوا اسکریپتی آن اجرا شده و سبب می‌شوند که عملگرهای ویژه‌ی Observable آن مانند map و filter نیز در دسترس برنامه قرار گیرند.


ساخت یک سرویس سمت سرور بازگشت لیست محصولات به صورت JSON

چون در ادامه می‌خواهیم لیست محصولات را از سرور دریافت کنیم، برنامه‌ی ASP.NET MVC فعلی را اندکی تغییر می‌دهیم تا این لیست را به صورت JSON بازگشت دهد.
بنابراین ابتدا کلاس مدل محصولات را به نحو ذیل به پوشه‌ی Models اضافه کرده:
namespace MVC5Angular2.Models
{
    public class Product
    {
        public int ProductId { set; get; }
        public string ProductName { set; get; }
        public string ProductCode { set; get; }
        public string ReleaseDate { set; get; }
        public decimal Price { set; get; }
        public string Description { set; get; }
        public double StarRating { set; get; }
        public string ImageUrl { set; get; }
    }
}
و سپس اکشن متد Products، لیست محصولات فرضی این سرویس را بازگشت می‌دهد:
using System.Collections.Generic;
using System.Text;
using System.Web.Mvc;
using MVC5Angular2.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
 
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult Products()
        {
            var products = new List<Product>
            {
               new Product
               {
                    ProductId= 2,
                    ProductName= "Garden Cart",
                    ProductCode= "GDN-0023",
                    ReleaseDate= "March 18, 2016",
                    Description= "15 gallon capacity rolling garden cart",
                    Price= (decimal) 32.99,
                    StarRating= 4.2,
                    ImageUrl= "app/assets/images/garden_cart.png"
               },
               new Product
               {
                    ProductId= 5,
                    ProductName= "Hammer",
                    ProductCode= "TBX-0048",
                    ReleaseDate= "May 21, 2016",
                    Description= "Curved claw steel hammer",
                    Price= (decimal) 8.9,
                    StarRating= 4.8,
                    ImageUrl= "app/assets/images/rejon_Hammer.png"
               }
            };
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(products, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا از JSON.NET جهت بازگشت camel case نام خواص مورد نیاز در سمت کاربر، استفاده شده‌است.
برای مطالعه‌ی بیشتر:
«استفاده از JSON.NET در ASP.NET MVC»
«تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET»

به این ترتیب، آدرس http://localhost:2222/home/products، خروجی JSON سرویس لیست محصولات را در مثال جاری، ارائه می‌دهد.


ارسال یک درخواست HTTP به سرور توسط AngularJS 2.0

اکنون پس از تنظیمات ثبت و معرفی سرویس HTTP و همچنین برپایی یک سرویس سمت سرور ارائه‌ی لیست محصولات، می‌خواهیم سرویس ProductService را که در قسمت قبل ایجاد کردیم (فایل product.service.ts)، جهت دریافت لیست محصولات از سمت سرور، تغییر دهیم:
import { Injectable } from 'angular2/core';
import { IProduct } from './product';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
 
@Injectable()
export class ProductService {
    private _productUrl = '/home/products';
 
    constructor(private _http: Http) { }
 
    getProducts(): Observable<IProduct[]> {
        return this._http.get(this._productUrl)
                         .map((response: Response) => <IProduct[]>response.json())
                         .do(data => console.log("All: " + JSON.stringify(data)))
                         .catch(this.handleError);
    }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}
از آنجائیکه این سرویس دارای یک وابستگی تزریق شده‌است، ذکر ()Injectable@، پیش از تعریف نام کلاس، ضروری است.
در سازنده‌ی کلاس ProductService، کار تزریق وابستگی سرویس Http انجام شده‌است. به این ترتیب با استفاده از متغیر خصوصی http_، می‌توان در کلاس جاری به امکانات این سرویس دسترسی یافت (همان «تزریق سرویس‌ها به کامپوننت‌ها» در قسمت قبل).
سپس متد get آن، یک درخواست HTTP از نوع GET را به آدرس مشخص شده‌ی در متغیر productUrl_ ارسال می‌کند (یا همان سرویس سمت سرور برنامه).
سرویس Http و همچنین شیء Response آن در ماژول‌های Http و Response قرار دارند که در ابتدای صفحه import شده‌اند.

متد http get یک Observable را بازگشت می‌دهد که در نهایت خروجی این متد نیز به همان <[]Observable<IProduct، تنظیم شده‌است. Observable یک شیء جنریک است و در اینجا نوع آن، آرایه‌ای از محصولات درنظر گرفته شده‌است.
اکنون که امضای این متد تغییر یافته است (پیش از این صرفا یک آرایه‌ی ساده از محصولات بود)، استفاده کننده (در کلاس ProductListComponent) باید به تغییرات آن از طریق متد subscribe گوش فرا دهد.
فعلا در کلاس جاری، پس از پایان کار دریافت اطلاعات از سرور، اطلاعات نهایی در متد map در دسترس قرار می‌گیرد (که یکی از عملگرهای RxJs است). کار متد map، اصطلاحا projection است. این متد، هر عضو دریافتی از خروجی سرور را به فرمتی جدید نگاشت می‌کند.
هر درخواست HTTP، در اصل یک عملیات async است. یعنی در اینجا توالی که در اختیار Observable ما قرار می‌گیرد، تنها یک المان دارد که همان شیء HTTP Response است.
بنابراین کار متد map فوق، تبدیل شیء خروجی از سرور، به آرایه‌ای از محصولات است.
در اینجا یک سری کدهای مدیریت استثناءها را نیز در صورت بروز مشکلی می‌توان تعریف کرد. برای مثال در اینجا متد catch، کار پردازش خطاهای رخ داده را انجام می‌دهد.
از متد do جهت لاگ کردن عملیات رخ داده و داده‌های دریافتی در کنسول developer tools مرورگرها استفاده شده‌است.

یک نکته:
اگر خروجی JSON از سرور، برای مثال داخل خاصیتی به نام data محصور شده بود، بجای ()response.json می‌بایستی از response.json().data استفاده می‌شد.


گوش فرا دادن به Observable دریافتی از سرور

تا اینجا یک درخواست HTTP GET را به سمت سرور ارسال کردیم و خروجی آن به صورت Observable در اختیار ما است. اکنون نیاز است کدهای ProductListComponent را جهت گوش فرا دادن به این Observable تغییر دهیم. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
errorMessage: string;
ngOnInit(): void {
    //console.log('In OnInit');
    this._productService.getProducts()
                        .subscribe(
                              products => this.products = products,
                              error => this.errorMessage = <any>error);
}
در کلاس ProductListComponent، در متد ngOnInit که در آن کار آغاز و مقدار دهی وابستگی‌های کامپوننت انجام می‌شود، متد ()productService.getProducts_ فراخوانی شده‌است. این متد یک Observable را بر می‌گرداند. بنابراین برای پردازش نتیجه‌ی آن نیاز است متد subscribe را در ادامه‌ی آن، زنجیر وار ذکر کرد.
اولین پارامتر متد subscribe، کار دریافت نتایج حاصل را به عهده دارد. برای مثال اگر حاصل عملیات در طی سه مرحله صورت گیرد، سه بار نتیجه‌ی دریافتی را می‌توان در اینجا پردازش کرد. البته همانطور که عنوان شد، یک عملیات غیرهمزمان HTTP، تنها در طی یک مرحله، HTTP Response را دریافت می‌کند؛ بنابراین، پارامتر اول متد subscribe نیز تنها یکبار اجرا می‌شود. در اینجا فرصت خواهیم داشت تا آرایه‌ی دریافتی حاصل از متد map قسمت قبل را به خاصیت عمومی products کلاس جاری نسبت دهیم.
پارامتر دوم متد subscribe در صورت شکست عملیات فراخوانی می‌شود. در اینجا حاصل آن به خاصیت جدید errorMessage نسبت داده شده‌است.


اکنون برنامه را مجددا اجرا کنید، هنوز باید لیست محصولات، مانند قبل نمایش داده شود.


یک نکته
اگر برنامه را اجرا کردید و خروجی مشاهده نشد، به کنسول developer tools مرورگر مراجعه کنید؛ احتمالا خطای ذیل در آن درج شده‌است:
 EXCEPTION: No provider for Http!
به این معنا که پروایدر HTTP یا همان HTTP_PROVIDERS، جایی معرفی نشده‌است. البته مشکلی از این لحاظ در برنامه وجود ندارد و این پروایدر در بالاترین سطح ممکن و در فایل app.component.ts ثبت شده‌است. مشکل اینجا است که مرورگر، فایل قدیمی http://localhost:2222/app/app.component.js را کش کرده‌است (به همراه تمام اسکریپت‌های دیگر) و این فایل قدیمی، فاقد تعریف سرویس HTTP است. بنابراین با حذف کش مرورگر و دریافت فایل‌های js جدید، مشکل برطرف خواهد شد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part8.zip


خلاصه‌ی بحث

برای کار با سرور و ارسال درخواست‌های HTTP، ابتدا نیاز است مدخل تعریف http.dev.js به index.html اضافه شود و سپس HTTP_PROVIDERS را در بالاترین سطح کامپوننت‌های تعریف شده، ثبت و معرفی کرد. پس از آن نیاز است RxJs را نیز import کرد. در ادامه، سرویس دریافت لیست محصولات، وابستگی سرویس HTTP را توسط سازنده‌ی خود دریافت کرده و از آن برای صدور یک فرمان HTTP GET استفاده می‌کند. سپس با استفاده از متد map، کار نگاشت شیء Response دریافتی، به فرمت مناسب مدنظر، صورت می‌گیرد.
در ادامه هر کلاسی که نیاز دارد با این کلاس سرویس دریافت اطلاعات کار کند، متد subscribe را فراخوانی کرده و نتیجه‌ی عملیات را پردازش می‌کند.
مطالب
React 16x - قسمت 30 - React Hooks - بخش 1 - معرفی useState و useEffect
با استفاده از React Hooks که در نگارش 16.7.0 آن معرفی شدند، می‌توان در کامپوننت‌های تابعی «تا پیش از این» بدون حالت، به state و تمام قابلیت‌های دیگر React، دسترسی یافت. جهت یادآوری، در قسمت 8 این سری، کامپوننت‌های تابعی بدون حالت را معرفی کردیم. تا پیش از معرفی React Hooks، برای ردیابی تغییرات مقادیری خاص، می‌بایستی آن‌ها را در خاصیت state کامپوننت‌هایی که به صورت کلاس تعریف شده بودند، قرار می‌دادیم. بنابراین class components، تنها نوع کامپوننت‌هایی در React بودند که دسترسی به state را داشتند. شبیه به همین مورد، برای life-cycle hooks معرفی شده‌ی در قسمت 9 برقرار بود. برای مثال متد componentDidMount، تنها در class components، جهت دسترسی به یک API خارجی و انجام اعمال Ajax ای، قابل تعریف بود و کار کامپوننت‌های تابعی بدون حالت، بیشتر نمایش عناصر HTML و دریافت مقادیر خود از class components، توسط props بود. به این ترتیب امکان تجزیه‌ی یک UI پیچیده، به یک component tree با خوانایی بیشتری، میسر می‌شد. اما با معرفی React v16.7.0، از لحاظ فنی دیگر الزاما نیازی به class components وجود ندارد و می‌توان با استفاده از React Hooks، تمام قابلیت‌هایی را که پیشتر فقط توسط class components در اختیار داشتیم، اکنون با کامپوننت‌های تابعی نیز پیاده سازی کنیم.


برپایی پیش‌نیازها

در اینجا برای بررسی React Hooks، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app sample-30
> cd sample-30
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

همچنین اگر به فایل package.json موجود در ریشه‌ی پروژه دقت کنیم، برای کار با React-hooks، نیاز است نگارش بسته‌های React و React-dom، حداقل مساوی 16.7 باشند که در زمان نگارش این مطلب، نگارش 16.12.0 آن به صورت خودکار نصب می‌شود. بنابراین بدون مشکلی می‌توانیم شروع به کار با React hooks کنیم.


معرفی useState Hook

در اینجا قصد داریم یک شمارشگر را به همراه یک دکمه، در صفحه نمایش دهیم؛ بطوریکه این شمارشگر، تعداد بار کلیک بر روی دکمه را ردیابی می‌کند. از پیش می‌دانیم که برای ردیابی مقدار تعداد بار کلیک شدن، باید متغیر آن‌را درون state یک class component قرار داد:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = { count: 0 };

  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <button onClick={this.incrementCount} className="btn btn-primary m-3">
        I was clicked {this.state.count} times!
      </button>
    );
  }
}

export default App;
در اینجا یک class component متداول را داریم که در آن دکمه‌ای تعریف شده‌است. سپس برای شمارش تعداد بار کلیک بر روی آن، خاصیت count را به شیء منتسب به state، با مقدار اولیه‌ی صفر، اضافه کرده‌ایم. اکنون هربار که بر روی آن کلیک می‌شود، رویدادگردان incrementCount فراخوانی شده و توسط متد this.setState، مقدار پیشین خاصیت count، بر اساس مقدار فعلی آن، یک واحد افزایش می‌یابد. نتیجه‌ی آن نیز در برچسب دکمه، نمایش داده می‌شود:


اکنون می‌خواهیم همین کامپوننت را توسط React hooks بازنویسی کنیم. در ابتدا، فایل app.js را به App‍Class.js، تغییر نام می‌دهیم تا نگارش قبلی class component را برای مقایسه، در اختیار داشته باشیم. در ادامه فایل جدید AppFunction.js را برای بازنویسی آن توسط یک کامپوننت تابعی، توسط میانبرهای imrc و سپس sfc در VSCode، ایجاد می‌کنیم. البته این تغییر نام فایل‌ها، نیاز به تغییر نام ماژول‌های import شده‌ی در فایل index.js را نیز به صورت زیر دارد:
//import App from "./App‍Class";
import App from "./AppFunction";

اولین سؤالی که اینجا مطرح می‌شود، این است: در این کامپوننت تابعی جدید، state را از کجا بدست بیاوریم؟
با React Hooks، بجای تعریف یک state به صورت خاصیت، آن‌را صرفا use می‌کنیم و این دقیقا نام اولین React Hooks ای است که بررسی می‌کنیم؛ یا همان useState. بنابراین ابتدا این شیء را import خواهیم کرد:
import React, { useState } from 'react';
useState یک تابع است و باید در ابتدای کامپوننت، مورد استفاده قرار گیرد. این متد برای شروع به کار، نیاز به یک state آغازین را دارد؛ دقیقا مانند همان‌کاری که در class component فوق انجام دادیم:
const App = () => {
    const [count, setCount] = useState(0);
در اینجا عدد صفر، همان مقدار آغازین متغیر count است (شبیه به کاری که در state = { count: 0 } انجام دادیم). سپس اولین خروجی متد useState که داخل یک آرایه مشخص شده‌است (JavaScript array destructuring ؛ با مزیت امکان انتخاب نام‌هایی دلخواه، بدون نیاز به تعریف alias، برخلاف حالت object destructuring)، همان متغیر count است که توسط state ردیابی خواهد شد. اینبار بجای متد this.setState قبلی که یک متد عمومی بود، متدی اختصاصی را صرفا جهت تغییر مقدار همین متغیر count، به نام setCount به عنوان دومین خروجی متد useState، تعریف می‌کنیم. در حقیقت تا اینجا امضای متد جنریک useState استفاده شده، به صورت زیر تغییر کرده‌است:
useState<number>(initialState: number | (() => number)): [number, React.Dispatch<React.SetStateAction<number>>]
متد useState، یک initialState را دریافت می‌کند و سپس یک عدد را (در اینجا چون مقدار آغازین، عددی است)، به همراه یک متد، جهت تغییر state آن، بازگشت می‌دهد:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <button onClick={incrementCount} className="btn btn-primary m-3">
      I was clicked {count} times!
    </button>
  );
};

export default App;
- پس از تعریف useState، متد رویدادگردان onClick را همانند قبل تعریف می‌کنیم؛ با یک تغییر مهم: چون این متد داخل یک کامپوننت تابعی تعریف شده‌است، باید با const شروع شود و همانند یک متغیر که به آن متدی انتساب داده شده، معرفی گردد. پیشتر incrementCount تعریف شده‌ی داخل یک class component، یک خاصیت بود که متدی به آن انتساب داده شده بود.
- همچنین در اینجا (داخل این متد) دیگر خبری از this‌ها نیست؛ onClick، مستقیما به متغیر incrementCount اشاره می‌کند و {count} نیز مستقیما از خروجی useState، که به مقدار جاری count اشاره می‌کند، تامین می‌شود.
- اکنون با هربار کلیک بر روی این دکمه، متد منتسب به متغیر incrementCount فراخوانی شده و در داخل آن، همان متد setCount را جهت به روز رسانی state، فراخوانی می‌کنیم (بجای فراخوانی this.setState عمومی قبلی). در اینجا ابتدا مقدار جاری متغیر count در state، دریافت شده و سپس یک واحد به آن اضافه می‌شود. امضای متد جنریک setCount به صورت زیر است:
 const setCount: (value: React.SetStateAction<number>) => void


استفاده از مقدار قبلی state توسط useState

زمانیکه متد this.setState فراخوانی می‌شود، اینکار سبب در صف قرار گرفتن رندر مجدد کامپوننت جاری خواهد شد. همچنین اعمال این متد نیز ممکن است در صف قرار گیرد. یعنی اگر پس از فراخوانی this.setState، سعی در خواندن state به روز شده را داشته باشیم، ممکن است مقدار اشتباهی را دریافت کنیم:
  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };
برای نمونه در فراخوانی فوق، منظور از this.state.count، مقدار جاری یا همان مقدار قبلی count است که قرار است یک واحد به آن اضافه شود. اما چون متد this.setState کار به روز رسانی‌های state را نیز در صف قرار می‌دهد، دفعه‌ی بعدی که بر روی آن کلیک شد، الزامی ندارد که this.state.count، حتما در همان لحظه به روز رسانی شده باشد. برای رفع این مشکل می‌توان نوشت:
  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };
prevState در اینجا یعنی state قبلی و توسط متد setState، به صورت یک arrow function قابل دریافت است که در نهایت یک شیء را باز می‌گرداند. اکنون بر اساس state قبلی می‌توان به روز رسانی دقیقی را انجام داد.

این نکته در مورد کامپوننت‌های تابعی نیز وجود دارد:
  const incrementCount = () => {
    setCount(count + 1);
  };
در اینجا متغیر count، همانند خاصیت this.state.count کامپوننت‌های کلاسی عمل می‌کند. بنابراین الزامی ندارد که با هر بار فراخوانی setCount، مقدار جاری متغیر count، دقیقا به مقدار قبلی تنظیم شده‌ی آن اشاره کند. برای انجام مشابه اینکار با کامپوننت‌های تابعی که از useState استفاده می‌کنند، می‌توان به صورت زیر عمل کرد:
const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };
در اینجا نیز امکان دسترسی به مقدار قبلی count، توسط یک arrow function وجود دارد که برخلاف حالت prevState، فقط یک مقدار عددی مرتبط با count را باز می‌گرداند.


به روز رسانی بیش از یک خاصیت در state

فرض کنید قصد داریم به مثال جاری، یک مربع را در صفحه اضافه کنیم که با کلیک بر روی آن، رنگش تغییر می‌کند (خاموش و روشن می‌شود):


در حالت AppClass یا کامپوننت کلاسی، کدهای برنامه به صورت زیر تغییر می‌کنند:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 0,
    isOn: false
  };

  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  toggleLight = () => {
    this.setState(prevState => ({
      isOn: !prevState.isOn
    }));
  };

  render() {
    return (
      <>
        <h1>App Class</h1>
        <h2>Counter</h2>
        <button onClick={this.incrementCount} className="btn btn-primary m-3">
          I was clicked {this.state.count} times!
        </button>

        <h2>Toggle Light</h2>
        <div
          style={{
            height: "50px",
            width: "50px",
            cursor: "pointer"
          }}
          className={
            this.state.isOn ? "alert alert-info m-3" : "alert alert-warning m-3"
          }
          onClick={this.toggleLight}
        />
      </>
    );
  }
}

export default App;
توضیحات:
- در متد رندر، نیاز است تا تنها یک child، بازگشت داده شود. به همین جهت می‌توان از React.Fragment، برای محصور سازی المان‌های تعریف شده، استفاده کرد. البته در React 16.7.0، دیگر نیازی به ذکر صریح React.Fragment نبوده و فقط می‌توان نوشت </><> تا بیانگر یک فرگمنت باشد.
- سپس یک div تعریف شده که با استفاده از ویژگی style، یک سری شیوه‌نامه‌ی ابتدایی، مانند طول و عرض و نوع اشاره‌گر ماوس آن، تعیین شده‌اند.
- اکنون برای اینکه بتوان با کلیک بر روی این div، رنگ آن‌را تغییر داد، نیاز است بتوان توسط متغیری، مقدار خاموش و روشن بودن را ردیابی کرد. به همین جهت خاصیت جدید isOn را به state اضافه می‌کنیم.
- در آخر، رویداد onClick این div را به متد رویدادگران toggleLight متصل می‌کنیم تا توسط آن و فراخوانی this.setState، بتوان مقدار قبلی isOn را در state، دریافت و سپس آن‌را معکوس کرد و بجای مقدار جاری isOn در state درج کنیم. این فراخوانی، سبب رندر مجدد کامپوننت جاری شده و در نتیجه‌ی آن، مقدار className را بر اساس مقدار this.state.isOn، به صورت پویا تغییر می‌دهد.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Class تامین شود:
import App from "./App‍Class";
//import App from "./AppFunction";

اکنون قصد داریم دقیقا معادل همین قطعه کد را در کامپوننت تابعی خود پیاده سازی کنیم. به همین جهت به فایل src\AppFunction.js بازگشته و تغییرات زیر را اعمال می‌کنیم:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [isOn, setIsOn] = useState(false);

  const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };

  const toggleLight = () => {
    setIsOn(prevIsOn => !prevIsOn);
  };

  return (
    <>
      <h1>App Function</h1>
      <h2>Counter</h2>
      <button onClick={incrementCount} className="btn btn-primary m-3">
        I was clicked {count} times!
      </button>

      <h2>Toggle Light</h2>
      <div
        style={{
          height: "50px",
          width: "50px",
          cursor: "pointer"
        }}
        className={isOn ? "alert alert-info m-3" : "alert alert-warning m-3"}
        onClick={toggleLight}
      />
    </>
  );
};

export default App;
توضیحات:
- اگر دقت کنید، کلیات این کامپوننت تابعی، با کامپوننت کلاسی، آنچنان متفاوت نیست. متد رندر آن دقیقا همان markup را بازگشت می‌دهد؛ با یک تفاوت مهم: در اینجا دیگر نیازی به ذکر thisها نیست، چون تمام ارجاعات، به متغیرهای داخل تابع App انجام شده‌است و نه به متدها و یا خاصیت‌های یک کلاس.
- مرحله‌ی بعد تغییر، جایگزینی this.state.isOn قبلی، با یک متغیر درون تابع App است. به همین جهت در اینجا یک useState دیگر را تعریف می‌کنیم. هر useState، تنها به قسمتی از state اشاره می‌کند و مانند خاصیت کلی state مربوط به یک کلاس نیست. همچنین در خاصیت state یک کلاس، مقدار آن همواره به یک شیء اشاره می‌کند؛ اما با useState چنین اجباری وجود ندارد و می‌تواند هر نوع مجاز جاوا اسکریپتی باشد. برای مثال در اینجا مقدار اولیه‌ی آن به false تنظیم شده‌است. پس از آن، خروجی این متد، قسمتی از state را که کنترل می‌کند، به نام متغیر isOn و تنظیم کننده‌ی آن‌را به نام متد setIsOn، معرفی خواهد کرد. متد useState، یک متد جنریک است. یعنی نوع خروجی‌های آن بر اساس نوع مقدار اولیه‌ای که به آن انتساب داده می‌شود، تعیین می‌شود. برای مثال اگر نوع مقدار اولیه‌ی آن، Boolean باشد، به صورت خودکار نوع متغیر و پارامتر متد خروجی از آن نیز Boolean خواهند بود.
- در آخر خاصیت toggleLight کلاس کامپوننت، تبدیل به یک متغیر یا ثابت، در تابع جاری App می‌شود و بجای this.setState کلی قبلی، از متد اختصاصی‌تر setIsOn، برای تغییر مقدار state متناظر، کمک گرفته خواهد شد. در اینجا با استفاده از prevIsOn، به مقدار دقیق پیشین متغیر isOn در state، دسترسی یافته و سپس آن‌را معکوس می‌کنیم.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Function تامین شود:
// import App from "./App‍Class";
import App from "./AppFunction";


معرفی useEffect Hook

فرض کنید قصد داریم برچسب دکمه‌ی «I was clicked {this.state.count} times» را در المان Title صفحه، نمایش دهیم. برای انجام چنین کاری نیاز است با DOM API تعامل داشته باشیم؛ اما پیشتر یک چنین کاری را تنها با class components می‌شد انجام داد. برای رفع این محدودیت در کامپوننت‌های تابعی، hook جدیدی به نام useEffect، ارائه شده‌است که باید import شود:
import React, { useEffect, useState } from "react";
اکنون این سؤال مطرح می‌شود که در متد useEffect، واژه‌ی Effect به چه چیزی اشاره می‌کند؟
در اینجا effect به معنای side effect و یا اثرات جانبی است؛ مانند: تعامل با یک API خارجی، کار با API مرورگر و کلا هر جائیکه در برنامه با دنیای خارج تعاملی وجود دارد، به عنوان یک side effect شناخته می‌شود. بنابراین با استفاده متد useEffect، می‌توان در کامپوننت‌های تابعی نیز با دنیای خارج، تعامل برقرار کرد.
import React, { useEffect, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You have clicked ${count} times`;
  });
متد useEffect، یک تابع را به عنوان ورودی دریافت کرده (effect function) و آن‌را پس از هر بار رندر کامپوننت جاری، اجرا می‌کند. برای مثال با تغییر state، کار رندر کامپوننت جاری، در صف قرار می‌گیرد و پس از اتمام رندر، تابع effect ذکر شده، اجرا می‌شود. برای مثال در اینجا پس از هر بار رندر کامپوننت،  document.title با عبارتی که به همراه تعداد بار کلیک کردن بر روی دکمه‌است، به روز رسانی می‌شود:


در اولین بار اجرای برنامه، عبارت «You have clicked 0 times»، در عنوان صفحه‌ی جاری، ظاهر می‌شود که از مقدار پیش‌فرض count، استفاده کرده‌است. اکنون اگر بر روی دکمه، کلیک کنیم، پس از تغییر برچسب دکمه (یا همان رندر کامپوننت، پس از تغییری در state)، آنگاه عنوان نمایش داده شده‌ی در مرورگر نیز تغییر می‌کند.

یک نکته: چون useEffect دارای همان scope متغیر count است، نیاز به API خاصی برای خواندن مقدار آن در اینجا نیست.

سؤال: برای پیاده سازی چنین قابلیتی در یک کامپوننت کلاسی چه باید کرد؟ در این مثال، در ابتدای کار باید مقدار پیش‌فرض موجود در state را در عنوان صفحه مشاهده کرد و پس از هربار به روز رسانی state نیز باید این عنوان، تغییر کند.
برای پیاده سازی معادل متد useEffect ای که در یک کامپوننت تابعی استفاده شد، در اینجا باید از دو life-cycle hook متفاوت، به نام‌های componentDidMount و componentDidUpdate، استفاده کنیم:
class App extends Component {

  componentDidMount() {
    document.title = `You have been clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You have been clicked ${this.state.count} times`;
  }
- برای به روز رسانی عنوان صفحه، در اولین بار نمایش آن، باید از متد componentDidMount استفاده کرد.
- همچنین چون می‌خواهیم به ازای هر تغییری در state نیز این عنوان تغییر کند (با هر بار کلیک بر روی دکمه)، باید از متد componentDidUpdate هم استفاده کنیم.


پاکسازی اثرات جانبی در useEffect Hook

فرض کنید قصد داریم موقعیت فعلی کرسر ماوس را در مرورگر نمایش دهیم. برای انجام اینکار در کامپوننت کلاسی، می‌توان از متد componentDidMount جهت دسترسی به DOM API و استفاده از متد addEventListener آن، برای گوش فرا دادن به حرکات کرسر ماوس، استفاده کرد:
class App extends Component {

  componentDidMount() {
    // ...
    window.addEventListener("mousemove", this.handleMouseMove);
  }
 دومین پارامتر این متد، یک callback function است که این حرکات را ردیابی می‌کند:
  handleMouseMove = event => {
    this.setState({
      x: event.pageX,
      y: event.pageY
    });
  };
در اینجا می‌توان با استفاده از شیء رخداد دریافتی، موقعیت x و y کرسر ماوس را در صفحه دریافت کرد و سپس با فراخوانی متد this.setState، این خواص را در state کامپوننت، اضافه و یا به روز رسانی نمود. بنابراین در این حالت الزامی به تعریف این خواص در شیء منتسب به state نیست؛ اما حداقل با تعریف آن‌ها می‌توان مقدار اولیه‌ای را برایشان درنظر گرفت:
class App extends Component {
  state = {
    //...
    x: 0,
    y: 0
  };
در آخر برای نمایش این اطلاعات موجود در state، می‌توان چنین المان‌هایی را به متد رندر کامپوننت، اضافه کرد:
<h2>Mouse Position</h2>
<p>X position: {this.state.x}</p>
<p>Y position: {this.state.y}</p>
بنابراین ردیابی تغییرات محل کرسر ماوس نیز یک side effect است که برای دسترسی به آن، نیاز است با window API کار کرد و این side effect‌ها دو نوع هستند:
- تعدادی از آن‌ها نیازی به پاکسازی و خارج شدن از حافظه را ندارند؛ مانند به روز رسانی عنوان صفحه در مرورگر. می‌توان یک چنین side effect هایی را اجرا و سپس آن‌ها را فراموش کرد.
- اما تعدادی از side effect‌ها را حتما باید پاکسازی کرد؛ مانند mousemove listener تعریف شده‌ی در مثال فوق. در اینجا زمانیکه کامپوننت جاری mount می‌شود، این listener را تعریف می‌کنیم؛ اما با Unmount شدن آن، باید این listener را حذف کرد تا برنامه دچار نشتی حافظه نشود (اگر اینکار انجام نشود، در این مثال مرورگر هنگ خواهد کرد!). روش انجام اینکار در متد componentWillUnmount، به صورت زیر است:
  componentWillUnmount() {
    window.removeEventListener("mousemove", this.handleMouseMove);
  }
سؤال: یک چنین قابلیتی را چگونه می‌توان در یک کامپوننت تابعی تعریف کرد؟
در این حالت نیز می‌توان از متد useEffect استفاده کرد. البته ابتدا باید state شیء ای را برای نگهداری اطلاعات به روز موقعیت مکانی کرسر ماوس، ایجاد کرد:
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
همانطور که عنوان شد، متد useState، برخلاف خاصیت کلی state یک کامپوننت کلاسی که فقط اشیاء را می‌پذیرد، هر نوع جاوا اسکریپتی را می‌تواند بپذیرد که در اینجا برای نمونه به یک شیء، تنظیم شده‌است.
سپس side effect خود را در قسمت effect function متد useEffect قرار می‌دهیم که آن نیز به متغیر handleMouseMove اشاره می‌کند:
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    // ...
    window.addEventListener("mousemove", handleMouseMove);
  });

  const handleMouseMove = event => {
    setMousePosition({
      x: event.pageX,
      y: event.pageY
    });
  };
به متغیر handleMouseMove، متدی انتساب داده شده‌است که با فراخوانی آن توسط mousemove listener، یک شیء evenet دریافت و سپس بر اساس خواص آن، خواص x و y شیء موجود در state، توسط متد setMousePosition، به روز رسانی می‌شود.
سپس برای نمایش x و y به روز رسانی شده‌ی در state، می‌توان از markup زیر در متد رندر استفاده کرد.
<h2>Mouse Position</h2>
{JSON.stringify(mousePosition, null, 2)}
<br />
اکنون سؤال اینجا است که معادل متد componentWillUnmount در اینجا چیست؟
اگر effect function تعریف شده، دارای یک خروجی (از نوع تابع) باشد، به این معنا است که این side effect، نیاز به پاکسازی دارد و این متد را در زمان Unmount آن فراخوانی می‌کند:
  useEffect(() => {
    // …
    // componentDidMount & componentDidUpdate
    window.addEventListener("mousemove", handleMouseMove);

    // componentWillUnmount
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  });
پیشتر آموختیم که effect function، تنها یکبار اجرا نمی‌شود؛ بلکه به ازای هر بار رندر، یکبار پس از آن اجرا می‌شود. یعنی خروجی آن نه فقط در زمان Unmount اجرا می‌شود، بلکه یکبار هم پیش از هر بار اجرای خود effect اجرا می‌گردد. به این ترتیب در اینجا فرصت پاکسازی اجرای قبلی را نیز خواهیم داد. بنابراین روش پاکسازی آن نسبت به متد componentWillUnmount کامپوننت‌های تابعی، پیشرفته‌تر است. یعنی توسط آن می‌توان یک side effect خاص را پیش و پس از هر بار رندر، در صورت نیاز فراخوانی کرد.

سؤال: اگر بخواهیم از اجرای یک side effect، به ازای هر بار رندر جلوگیری کنیم، چه باید کرد؟

برای اینکار می‌توان آرگومان دومی را به متد useEffect اضافه کرد که آرایه‌ای از مقادیر است. توسط اعضای آن می‌توان مقدار و یا مقادیری را مشخص کرد که side effect تعریف شده، به آن وابسته‌است. اکنون اگر این مقدار تغییر کند، آنگاه side effect متناظر با آن نیز اجرا می‌شود:
  useEffect(() => {
    document.title = `You have clicked ${count} times`;
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  },[]);
در این مثال، چون پارامتر دوم را به صورت یک آرایه‌ی خالی مقدار دهی کرده‌ایم، effect function تعریف شده تنها در زمان mount و unmount اجرا می‌شود. البته اگر این تغییر را ذخیره کرده و برنامه را اجرا کنیم، تمام قسمت‌های تعریف شده‌ی آن به خوبی کار می‌کنند (با کلیک بر روی دکمه، برچسب آن به روز رسانی می‌شود و یا با حرکت کرسر ماوس در صفحه، موقعیت آن گزارش داده می‌شود)، منهای قسمت به روز رسانی عنوان صفحه با هر بار کلیک. علت اینجا است که با تغییر معرفی شده، دیگر حالت componentDidUpdate توسط متد useEffect پوشش داده نمی‌شود و به این ترتیب با هر بار به روز رسانی state و رندر کامپوننت، دیگر کار به اجرای مجدد side effect آن نمی‌رسد.
برای رفع این مشکل، باید به useEffect اعلام کنیم که side effect تعریف شده‌ی در آن، وابسته‌است به مقدار count و با تغییرات آن در state، نیاز است مجددا اجرا شود:
  useEffect(() => {
   // ...
  },[count]);
به همین جهت در آرایه‌ی تعریف شده، مقداری را که به آن وابستگی وجود دارد، مشخص می‌کنیم. با این تغییر اگر بر روی دکمه کلیک کنیم، چون اکنون useEffect می‌داند که باید تغییرات count را ردیابی کند، با اجرای مجدد effect function خود، عنوان صفحه نیز به روز رسانی خواهد شد.


کار با چندین listener مختلف در متد useEffect

سؤال: آیا تنظیم یک وابستگی خاص در متد useEffect، امکان تنظیم event listenerهای دیگر را غیرممکن می‌کند؟
برای پاسخ به این سؤال، چند event listener دیگر را ثبت می‌کنیم. برای مثال یکی دیگر از APIهای مرورگر، navigator نام دارد که توسط آن می‌توان وضعیت آنلاین و آفلاین بودن را به کمک خروجی خاصیت navigator.onLine آن، مشخص کرد. به کمک این API می‌خواهیم این وضعیت را نمایش دهیم. برای این منظور ابتدا state آن‌را در کامپوننت تابعی، ایجاد می‌کنیم:
const [status, setStatus] = useState(navigator.onLine);
مقدار اولیه‌ی این state را نیز توسط مقدار جاری خاصیت navigator.onLine، مشخص کرده‌ایم.
اکنون برای گوش فرا دادن به تغییرات این خاصیت (online و یا offline شدن کاربر)، نیاز است دو event listener را به کمک متد addEventListener ثبت کنیم و همچنین این متدها نیاز به پاکسازی هم دارند:
  useEffect(() => {
    // ...

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      // ...
  
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, [count]);
پس از ثبت event listener ها، اکنون می‌توان با هربار فراخوانی آن‌ها توسط مرورگر، وضعیت state آن‌را به true و یا false، تغییر داد:
  const handleOnline = () => {
    setStatus(true);
  };

  const handleOffline = () => {
    setStatus(false);
  };
و در آخر مقدار متغیر status را به نحو زیر در متد رندر، نمایش داد:
<h2>Network Status</h2>
<p>
  You are <strong>{status ? "online" : "offline"}</strong>
</p>

برای آزمایش حالت offline آن، فقط کافی است به ابزار توسعه دهندگان مرورگر مراجعه کرده و در برگه‌ی network آن، حالت online را offline کنید:


در این حالت هم نمایش وضعیت آنلاین بودن کاربر به درستی کار می‌کند و هم سایر قسمت‌هایی که تاکنون اضافه کرده‌ایم. به این معنا که هر چند توسط ذکر پارامتر [count]، وابستگی خاصی برای side effect ویژه‌ای، مشخص شده‌است، اما ما را از تنظیم event listener‌های دیگری در قسمت‌های mount و unmount محروم نمی‌کند.


پاکسازی اثرات جانبی در useEffect Hook، زمانیکه روشی برای آن وجود ندارد

در مثالی دیگر می‌خواهیم از API موقعیت جغرافیایی کاربر در مرورگر یا navigator.geolocation استفاده کنیم. توسط این API هم می‌توان طول و عرض جغرافیایی را به دست آورد و هم تغییرات آن‌را تحت نظر قرار داد. برای مثال با بررسی این تغییرات می‌توان سرعت را نیز به دست آورد.
در این حالت نیز ابتدا با تعریف state مختص به آن شروع می‌کنیم و اینبار به عنوان مثال، مقدار اولیه‌ی آن‌را خارج از تابع جاری تنظیم می‌کنیم (جهت نمایش یک گزینه‌ی مهیای دیگر):
const initialLocationState = {
  latitude: null,
  longitude: null,
  speed: null
};

const App = () => {
  // ...
  const [location, setLocation] = useState(initialLocationState);
سپس جهت کار با این API، مقدار اولیه‌ی موقعیت کاربر و همچنین ردیابی تغییرات آن‌را در متد useEffect، تنظیم می‌کنیم:
  useEffect(() => {
    // ...

    navigator.geolocation.getCurrentPosition(handleGeolocation);
    const watchId = navigator.geolocation.watchPosition(handleGeolocation);

    return () => {
      // ...
      navigator.geolocation.clearWatch(watchId);
    };
  }, [count]);
در اینجا متد getCurrentPosition، با متد addEventListener متفاوت است و تنها یک callback function را دریافت می‌کند که در آن می‌توان setLocation را جهت به روز رسانی موقعیت جاری جغرافیایی کاربر، فراخوانی کرد. مشکلی که با این متد وجود دارد، نداشتن معادلی شبیه به متد removeEventListener است. یعنی API آن روشی را برای unmount آن فراهم نکرده‌است. البته متد watchPosition که تغییرات موقعیت کاربر را ردیابی می‌کند، دارای API مخصوص پاکسازی آن نیز هست که در این کدها ذکر شده‌است.

یک روش برای حل این مشکل و غیرفعال کردن دستی listener متد getCurrentPosition پس از unmount، تعریف یک متغیر mounted پیش از متد useEffect است:
  let mounted = true;

  useEffect(() => {
   // ...

    return () => {
      // ...
      mounted = false;
    };
  }, [count]);
و آن‌را در زمان پاکسازی useEffect، با false مقدار دهی می‌کنیم. اکنون زمانیکه می‌خواهیم رویدادگردان این Listener را تعریف کنیم، تنها بر اساس مقدار متغیر mounted عمل خواهیم کرد که از نشتی حافظه‌ی آن جلوگیری می‌کند:
  const handleGeolocation = event => {
    if (mounted) {
      setLocation({
        latitude: event.coords.latitude,
        longitude: event.coords.longitude,
        speed: event.coords.speed
      });
    }
  };
در آخر هم برای نمایش آن در متد رندر به صورت زیر عمل می‌کنیم:
<h2>Geolocation</h2>
<p>Latitude is {location.latitude}</p>
<p>Longitude is {location.longitude}</p>
<p>Your speed is {location.speed ? location.speed : "0"}</p>

سؤال: در اینجا شیء location چندین بار تکرار شده‌است. آیا می‌توان مقادیر خواص آن‌را توسط Object Destructuring نیز به دست آورد؟
پاسخ: بله. در اینجا هم Object Destructuring بر روی location state، کار می‌کند:
const [{ latitude, longitude, speed }, setLocation] = useState(
    initialLocationState
  );
که پس از آن، دیگر نیازی به تکرار شیء location نخواهد بود:
<h2>Geolocation</h2>
<p>Latitude is {latitude}</p>
<p>Longitude is {longitude}</p>
<p>Your speed is {speed ? speed : "0"}</p>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-01.zip
مطالب
React 16x - قسمت 4 - کامپوننت‌ها - بخش 1 - کار با عبارات JSX
برپایی پروژه‌ی ایجاد اولین کامپوننت React

در اینجا برای بررسی مقدماتی کامپوننت‌ها، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-04
> cd sample-04
> npm start
اکنون در ادامه اولین کاری را که انجام می‌دهیم، نصب توئیتر بوت استرپ 4 است تا بتوانیم توسط امکانات آن، ظاهر بهتری را برای برنامه‌ی تهیه شده تدارک ببینیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های `+ctrl را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
این دستور علاوه بر نصب بوت استرپ 4.3.1 (آخرین نگارش موجود در زمان نگارش این مطلب)، به دلیل ذکر سوئیچ save، مدخل آن‌را نیز به فایل package.json برنامه اضافه می‌کند.
پس از اجرای این دستور، ممکن است پیام‌های اخطاری مانند «requires a peer of jquery@1.9.1 - 3 but none is installed» را نیز مشاهده کنید که مهم نیستند. چون در اینجا صرفا از امکانات CSS ای بوت استرپ استفاده خواهیم کرد و کاری با jQuery نداریم. محل نصب آن نیز پوشه‌ی node_modules\bootstrap برنامه است.

سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.


ایجاد اولین کامپوننت React

در پوشه‌ی src برنامه، پوشه‌ی جدیدی را به نام components ایجاد می‌کنیم و تمام کامپوننت‌های خود را در آن قرار خواهیم داد. سپس داخل این پوشه، یک فایل جدید و خالی را به نام counter.jsx ایجاد می‌کنیم. پسوند این فایل jsx است و نام فایل‌های کامپوننت‌ها را نیز camel case وارد می‌کنیم؛ یعنی اولین حرف اولین واژه‌ی وارد شده، با حروف کوچک و تمام واژه‌های پس از آن با حروف بزرگ شروع خواهند شد مانند coolApp. مزیت استفاده‌ی از پسوند jsx نسبت به js، فراهم شدن امکانات مخصوص React در VSCode است.
در ابتدای فایل counter.jsx، نیاز است وابستگی‌های React را import کنیم. اگر از قسمت اول بخاطر داشته باشید، «simple react snippets» را نیز در VSCode نصب کردیم. به کمک آن می‌تواند این نوع importها را ساده‌تر وارد کرد. برای این منظور imrc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید.  به این ترتیب یک سطر زیر به صورت خودکار تولید می‌شود:
import React, { Component } from 'react';
پس از این سطر، cc را تایپ کرده و سپس دکمه‌ی tab را فشار دهید تا ساختار کلاس یک کامپوننت React تولید شود. همان لحظه‌ای که این ساختار تولید می‌شود، اگر دقت کنید دو کرسر در صفحه ظاهر شده‌اند که با تایپ نام کامپوننت، نام کلاس و نام export آن‌را تکمیل می‌کنند. نام این کامپوننت را Counter که با حروف بزرگ شروع می‌شود، وارد می‌کنیم. اکنون کدهای آن را به نحو زیر ویرایش می‌کنیم:
import React, { Component } from "react";

class Counter extends Component {
  render() {
    return <h1>Hello world!</h1>;
  }
}

export default Counter;
مفهوم ساختار یک چنین کلاس و export ای را در قسمت قبل با معرفی کلاس‌ها، ارث بری، ماژول‌ها و همچنین exportهای آن‌ها بررسی کردیم. البته در قسمت قبل، export default class را مشاهده کردید و در اینجا بجای آن، سطر آخر این ماژول به export default ختم شده‌است که روش دیگری برای تعریف این export است.
خروجی متد render در اینجا، یک رشته‌ی معمولی نیست؛ بلکه یک عبارت jsx است که در قسمت اول معرفی شد. این عبارت در نهایت توسط کامپایلر Babel به معادل React.createElement ترجمه می‌شود. به همین جهت نیاز است تا import React را در ابتدای این ماژول درج کرد؛ هرچند به ظاهر به صورت مستقیم از آن استفاده نمی‌کنیم.

تا اینجا این کامپوننت در UI برنامه نمایش داده نمی‌شود. به همین جهت به فایل index.js مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
- ابتدا نیاز است تا شیء Counter را در اینجا import کنیم و چون خروجی پیش‌فرض است، نیازی به ذکر {} برای معرفی آن نیست:
import Counter from "./components/counter";
- سپس در سطر ReactDOM.render، بجای رندر کامپوننت App، کامپوننت Counter را ذکر می‌کنیم:
 ReactDOM.render(<Counter />, document.getElementById("root"));
اکنون برنامه هر زمانیکه به المان جدید Counter برسد، بجای آن به متد render کامپوننت متناظر مراجعه کرده و خروجی آن‌را رندر می‌کند. پس از این تغییر اگر به مرورگر مراجعه کنید، خروجی hello world را مشاهده خواهید کرد.


درج چند عنصر در عبارات JSX

می‌خواهیم در کامپوننت Counter، یک دکمه را نیز نمایش دهیم. برای انجام اینکار، به نحو زیر عمل می‌کنیم:
  render() {
    return <h1>Hello world!</h1><button>Increment</button>;
  }
در این حالت هم در VSCode و هم در کنسول توسعه دهندگان مرورگر، خطای «JSX expressions must have one parent element» ظاهر می‌شود.
عبارات JSX در نهایت باید تبدیل به متد React.createElement شوند. اولین پارامتر این متد، نوع المانی است که قرار است ایجاد شود که در اینجا h1 است. اما در اینجا دو المان را داریم. در این حالت Babel نمی‌داند که چگونه باید یک چنین عبارتی را به React.createElement ترجمه کند. یک راه حل این است که کل این عبارت را داخل یک div قرار داد:
  render() {
    return (
      <div>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </div>
    );
در اینجا فرمت چند سطری return، توسط افزونه‌ی Prettier که در قسمت اول معرفی شد، پس از ذخیره‌ی فایل، به صورت خودکار اعمال شده‌است. همچنین اگر دقت کنید یک () جدید را نیز مشاهده می‌کنید. علت آن مقابله با automatic semicolon insertion است (درج ; خودکار). در جاوا اسکریپت اگر یک return را داشته باشید و پس از آن در همان سطر، چیزی درج نشده باشد، یک سمی‌کالن را به صورت خودکار درج/تفسیر می‌کند. به این ترتیب عبارت JSX چند سطری درج شده‌ی در سطرهای بعد از return، دیده نخواهد شد؛ یعنی چیزی شبیه به عبارات زیر تفسیر می‌شود:
return ;
  <div></div>
برای رفع این مشکل باید دقیقا جلوی واژه‌ی کلیدی return، یک پرانتز را باز کرد و آن‌را پس از خاتمه‌ی عبارت JSX، بست (که البته افزونه‌ی Prettier اینکار را به صورت خودکار برای شما انجام می‌دهد):
return (
  <div></div>
);
نکته 1: بدیهی است زمانیکه المان‌های درج شده را درون یک div محصور کردیم، به همین نحو نیز در DOM اصلی ظاهر خواهند شد. اگر علاقمند نیستید که این div در خروجی نهایی رندر شود، می‌توان بجای آن از React.Fragment استفاده کرد که هیچ نوع المان اضافه‌تری را در DOM بجای آن درج نمی‌کند:
    return (
      <React.Fragment>
        <h1>Hello world!</h1>
        <button>Increment</button>
      </React.Fragment>
    );

نکته 2: در VSCode برای ویرایش همزمان ابتدا و انتهای یک تگ (برای مثال ویرایش همزمان عبارت div در اینجا و تبدیل آن به React.Fragment در دو قسمت)، عبارت آن تگ را انتخاب کرده و سپس دکمه‌های ctrl+d را فشار دهید تا بتوانید همزمان هر دو عبارت انتخاب شده را با هم ویرایش کنید. به اینکار multi-cursor editing می‌گویند.


نمایش پویای اطلاعات در عبارات JSX

در ادامه بجای نمایش عبارت ثابت «Hello world»، می‌خواهیم آن‌را به صورت پویا تنظیم کنیم. برای این منظور یک خاصیت جدید را در کلاس جاری، به نام state تعریف کرده و آن‌را با یک شیء، مقدار دهی می‌کنیم. state، یک خاصیت ویژه در کامپوننت‌های React است و بیانگر داده‌هایی است که آن کامپوننت نیاز دارد. این داده‌ها می‌توانند یک key/value ساده باشند و یا حتی value تعریف شده نیز می‌تواند یک شیء پیچیده باشد.
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.state.count}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }
}

export default Counter;
در اینجا خاصیت state را با شیءای که حاوی key/value مساوی count با مقدار صفر است، مقدار دهی کرده‌ایم. سپس برای نمایش این اطلاعات در عبارت JSX، از یک {} استفاده می‌شود. داخل {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را درج کرد. برای مثال با this شروع می‌کنیم که بیانگر اشاره‌گری به وهله‌ای از شیء جاری است. سپس می‌توان توسط آن به خاصیت state و سپس کلید count شیء منتسب به آن دسترسی یافت. به این ترتیب عدد صفر، در کنار دکمه‌ای با برچسب Increment، در مرورگر ظاهر خواهد شد.

همانطور که عنوان شد در بین {}‌ها می‌توان هر نوع عبارت مجاز جاوا اسکریپتی را ذکر کرد و عبارت چیزی است که مقداری را بازگشت می‌دهد. بنابراین عبارتی مانند {2+2} را نیز می‌توان در اینجا بکار برد و یا حتی در اینجا می‌توان متدی را فراخوانی کرد که مقداری را بازگشت می‌دهد:
import React, { Component } from "react";

class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    return (
      <React.Fragment>
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? "Zero" : count;
  }
}

export default Counter;
در این مثال می‌خواهیم اگر مقدار count مساوی صفر بود، بجای عدد صفر، واژه‌ی Zero را نمایش دهد. به همین جهت این منطق را به یک متد مانند formatCount منتقل کرده و سپس آن‌را به صورت {()this.formatCount}، فراخوانی کرده و نمایش می‌دهیم.
در متد formatCount حتی می‌توان عبارات JSX را نیز بجای یک رشته‌ی ساده، بازگشت داد:
  formatCount() {
    const { count } = this.state; // Object Destructuring
    return count === 0 ? <h1>Zero</h1> : count;
  }


مقدار دهی ویژگی‌های عناصر در عبارات JSX

فرض کنید یک المان img را به عبارت JSX کلاس Counter اضافه کرده‌ایم. اکنون می‌خواهیم ویژگی src آن‌را مقدار دهی کنیم. در اینجا هر چیزی که بین "" قرار گیرد، به صورت یک رشته‌ی ثابت پردازش می‌شود. برای تنظیم آن به یک متغیر، ابتدا خاصیت state را به صورت زیر جهت درج imageUrl، ویرایش می‌کنیم:
  state = {
    count: 0,
    imageUrl: "/logo192.png"
  };
پس از آن عبارت مقدار خاصیت this.state.imageUrl را توسط یک {}، به ویژگی src تصویر نسبت می‌دهیم:
  render() {
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span>{this.formatCount()}</span>
        <button>Increment</button>
      </React.Fragment>
    );
  }

مقدار دهی class و style المان‌ها، نسبت به مقدار دهی attributes که مشاهده کردید، اندکی متفاوت است؛ از این جهت که در نهایت یک عبارت JSX توسط کامپایلر Babel به معادل جاوا اسکریپتی آن ترجمه می‌شود و اگر در اینجا به عنوان مثال از ویژگی class استفاده شود، چون نام class، یک نام و واژه‌ی کلیدی از پیش معین شده‌ی جاوا اسکریپتی است، امکان استفاده‌ی از آن در اینجا وجود ندارد. به همین جهت در React برای تنظیم ویژگی class عناصر، از className استفاده می‌شود:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span className="badge badge-primary m-2">{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
در اینجا اعمال یک سری از کلاس‌های بوت استرپ را که در ابتدای مطلب به پروژه اضافه کردیم، به ویژگی‌های className المان‌های span و button مشاهده می‌کنید.
تا اینجا اگر فایل کامپوننت Counter را ذخیره کنید، خروجی ذیل در مرورگر ظاهر خواهد شد:


روش مقدار دهی ویژگی style نیز متفاوت است. در اینجا React انتظار دارد تا شیءای را که به صورت زیر تشکیل می‌شود:
  styles = {
    fontSize: 50,
    fontWeight: "bold"
  };
به صورت {this.styles} به ویژگی style انتساب دهیم:
    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className="badge badge-primary m-2">
          {this.formatCount()}
        </span>
        <button className="btn btn-secondary btn-sm">Increment</button>
      </React.Fragment>
    );
نحوه‌ی تشکیل خاصیت styles، بر اساس ذکر خواص CSS، به صورت خواصی camel-case است؛ مانند fontSize. در اینجا عدد 50 توسط react به صورت خودکار به 50px تبدیل می‌شود.
اعمال این styles نمونه، یک چنین خروجی را به همراه خواهد داشت:


مزیت تعریف شیء styles به صورت یک خاصیت در کلاس، امکان استفاده‌ی مجدد از آن در سایر المان‌ها است. اگر چنین چیزی مدنظر شما نیست، می‌توان این شیء را به صورت inline هم تعریف کرد:
<button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
در اینجا، ابتدا یک {} درج می‌شود تا بیانگر نمایش دریافت یک عبارت معتبر جاوا اسکریپتی باشد. سپس داخل آن یک {} دیگر نیز قرار گرفته‌است که بیانگر تعریف یک شیء جاوا اسکریپتی است و در این حالت باید با نحوه‌ی تشکیل عناصر شیء style مورد نظر React که به صورت caml-case هستند، تطابق داشته باشد.


مقدار دهی پویای ویژگی className عناصر در عبارات JSX

در ادامه می‌خواهیم اگر مقدار count مساوی صفر بود، span ای که هم اکنون با یک badge آبی (با کلاس badge-primary) نمایش داده می‌شود، زرد رنگ (با کلاس badge-warning) شود و در غیراینصورت آبی رنگ. بنابراین می‌خواهیم بر اساس مقدر count، مقدار کلاس‌های انتسابی به className را به صورت پویا تغییر دهیم و این الگویی است که در برنامه‌های واقعی بسیار استفاده می‌شود:
  render() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";

    return (
      <React.Fragment>
        <img src={this.state.imageUrl} alt="" />
        <span style={this.styles} className={classes}>
          {this.formatCount()}
        </span>
        <button style={{ fontSize: 30 }} className="btn btn-secondary btn-sm">
          Increment
        </button>
      </React.Fragment>
    );
برای این منظور متغیر classes را تعریف کرده‌ایم که در ابتدا با مقادیری که ثابت هستند، مقدار دهی شده‌اند. سپس بر اساس مقدار this.state.count، مقدار مشخص warning و یا primary به این رشته افزوده می‌شود. در آخر هم از این متغیر به صورت className={classes} استفاده شده‌است؛ با این خروجی:


البته باید دقت داشت که می‌توان منطق تشکیل متغیر classes را به یک متد، جهت خلوت سازی متد render نیز منتقل کرد. برای این کار، دو سطر مرتبط با متغیر classes را در VSCode انتخاب کنید. سپس یک آیکن لامپ مانند ظاهر می‌شود که با کلیک بر روی آن، منوی extract to method نیز قابل انتخاب است:
  render() {
    let classes = this.getBadgeClasses();
    // ...
  }

  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }
البته در اینجا می‌توان متغیر classes را نیز حذف کرد و مستقیما متد getBadgeClasses را مورد استفاده قرار داد:
<span style={this.styles} className={this.getBadgeClasses()}>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part01.zip
مطالب
Performance در AngularJS قدم دوم
در مقاله‌ی قبل روش درست استفاده کردن از Binding را برای بهبود Performance، توضیح دادم. در این مقاله می‌خواهم در مورد ng-if و فرق آن با ng-show صحبت کنم و اینکه کدامیک Performance بهتری را برای AngularJS فراهم می‌کنند.

سول اول، کار ng-show چیست؟
ng-show یکی از پر کاربردترین Directiveهای AngularJS است که وظیفه‌ی Show و Hide قسمتی از Vew را به عهده دارد. به کد زیر توجه کنید:
<div ng-show="has">
     <div ng-repeat="item in items">
          <span>{{item.title}}</span>
     </div>
</div>
در این کد ما از ng-show استفاده کرده‌ایم. اگر has مقدار true داشته باشد div نمایش داده می‌شود و اگر false باشد، div نمایش داده نمی‌شود. در داخل div، لیستی وجود دارد که توسط ng-repeat تکرار شده و یک لیست ساده را درست می‌کند.

سول دوم، کار ng-if چیست؟  
کد بالا را دوباره تکرار می‌کنیم. ولی با این تفاوت که اینبار بجای ng-show از ng-if استفاده خواهیم کرد:
<div ng-if="has">
     <div ng-repeat="item in items">
          <span>{{item.title}}</span>
     </div>
</div>
خوب؟ آیا عملکرد این دو کد با هم تفاوت دارد؟ جواب سؤال در ظاهر خیر هست. یعنی مانند کد بالایی، اگر has مقدار true داشته باشد، div نمایش داده می‌شود؛ در غیر اینصورت، خیر.

سوال سوم، پس اگر عملکرد یکسانی دارند، تفاوت آن‌ها در چیست؟
تفاوت این دو Directive در Performance هست. اجازه دهید بیشتر توضیح دهم. ng-show اگر مقدار false دریافت کند، tag مورد نظر را نمایش نمی‌دهد؛ ولی تگ‌های داخلی آن توسط AngularJS پردازش می‌شوند. یعنی چه ng-show مقدار true بگیرید و یا false، لیست داخل tag، توسط AngularJS پردازش و Render می‌شود. ولی در ظاهر ما در View چیزی را نمی‌بینیم. ng-if بر حسب مقادیری که دریافت می‌کند، می‌تواند به بالا رفتن Performance AngularJS کمک کند. فرض کنید ng-if مقدار false گرفته است؛ یعنی has مقدار false دارد. علاوه بر اینکه tag div نمایش داده نمی‌شود، بلکه داخل tag نیز پردازش نمی‌شود. یعنی لیستی که ما در کد نوشته‌ایم، به هیچ عنوان توسط AngularJS پردازش نخواهد شد که باعث می‌شود Watcher، کار کمتری انجام دهد. پس در نتیجه بهبودی را در کارآیی Rendering و Binding خواهیم داشت.
مطالب
طراحی شیء گرا: OO Design Heuristics - قسمت چهارم

Dynamic Semantics

Objectها علاوه بر داده و رفتار به عنوان توصیفات ثابت، در زمان اجرا دارای یک Local State (‏‏a snapshot) از مقادیر داینامیک مربوط به اعضای داده‌ای خود، می‌باشند. مجموعه تمام حالاتی که وهله‌های یک کلاس می‌توانند بین آنها گذر (transition) داشته باشد، dynamic semantics مربوط به کلاس نامیده می‌شود و به وهله‌های کلاس این امکان را می‌دهند تا به یک پیغام مشابه رسیده و در زمان‌های مختلف از چرخه زندگی خود، به اشکال مختلف پاسخ دهند.

Method junk for the class X 
if (local state #1) then
do something
else if (local state #2) then
do something different
End Method

بخش اصلی هر طراحی شیء گرا، dynamic semantics وهله‌ها می‌باشد. dynamic semantics هر کلاسی باید در قالب یک دیاگرام state-transition مستند شود. شکل زیر dynamic semantics پروسه‌های موجود در یک سیستم عامل را در قابل یک دیاگرام حالت نمایش می‌دهد. این پروسه‌ها توانایی این را دارند که در هر کدام از حالات: runnable، current process، blocked، sleeping و یا در حالت exited، قرار داشته باشند. همچنین به عنوان مثال، یک پروسه زمانی می‌تواند در حالت current process قرار گیرد که حتما قبلا در حالت runnable قرار داشته باشد. این اطلاعات برای ایجاد تست برای کلاس‌ها و وهله‌های آنها می‌تواند مفید واقع شود.

شکل 2.8 State-transition diagram notation 

برخی از طراحان به طور تصادفی، dynamic semantics یک کلاس را به عنوان static semantics آن کلاس مدل می‌کنند. به عنوان مثال اگر color یکی از اعضای داده ای (data member) کلاس توپ باشد و بعد از وهله سازی از کلاس توپ، color آن بازهم قابل تغییر باشد، منظور اینکه توپ آبی به عنوان یک وهله از کلاس توپ در زمان حیات خود تغییر رنگ دهد، اصطلاحا می‌گویند: color جزء dynamic semantics کلاس توپ می‌باشد. با توجه به توضحیاتی که داده شد، حال اگر طراحی برای هر رنگ توپ یک کلاس جدا در نظر گرفته باشد، dynamic semantics را به عنوان static semantics مدل کرده و به احتمال زیاد ما را به سمت ایجاد مشکل Class Proliferation (ازدیاد کلاس ها) سوق خواهد داد.

Abstract Classes

به سوالات زیر توجه کنید:

  • آیا هرگز میوه خورده‌اید؟
  • آیا هرگز پیش غذا خورده‌اید؟ 
  • آیا هرگز دسر خورده‌اید؟ 
اکثر مردم به این سوالات جواب «بله» را خواهند داد.
حال با توجه به سوالات «مزه غذا چطور بود؟ دسری که خوردید، چه تعداد کالری داشت؟ هزینه پیش غذایی که خوردید چقدر بود» پاسخ چه خواهد بود؟
من (نویسنده) ادعا میکنم که هیچ کسی تا به حال میوه نخورده است. بیشتر مردم، سیب، موز و پرتقال خورده‌اند؛ میوه‌ی قرمز رنگی به ارزش 3 پوند را نخورده‌اند.

شبیه به این مسئله برای زمانی است که گارسون رستوران از شما سوال می‌کند: «برای شام چه چیزی میل دارید» و شما جواب می‌دهید: «یک پیش غذا، یک غذای اصلی و یک دسر». در این حالت چون شما دقیقا مشخص نکرده‌اید چه نوعی می‌خواهید، گارسون، مات و مبهوت خواهد ماند. همه می‌دانیم که چیزی تحت عنوان میوه، پیش غذا و یا وهله دسر در واقعیت وجود ندارد؛ بله این عبارات اطلاعات مفیدی را تسخیر می‌کنند. اگر من در دستم یک ساعت زنگی گرفته و از شما می‌پرسیدم: «نظرتان در مورد میوه من چیست؟»؛ بدون شک فکر می‌کردید من دیوانه شده‌ام. حال اگر در دستم سیبی گرفته و سوال قبلی را می‌پرسیدم، این بار از نظر شما من یک شخص عاقل بودم.
با وجود اینکه نمی‌توان از میوه وهله سازی کرد، اما اطلاعات مفیدی را تسخیر می‌کند. در واقع میوه، یک کلاسی (concept) است که دانشی از نحوه وهله سازی وهله هایش به وسیله Type پیاده ساز خود، ندارد.

کلاسی که دانشی از نحوه وهله سازی وهله‌های خود ندارد، abstract class (کلاس مجرد یا انتزاعی) نامیده می‌شود.
کلاسی که دانش نحوه وهله سازی وهله‌های خود دارد، concrete class نامیده می‌شود.

در پارادایم شیء گرا، مهم‌ترین استفاده از کلاس‌های انتزاعی در مباحث ارث بری مطرح می‌شود.

Roles Versus Classes

قاعده شهودی 2.11
مطمئن باشید انتزاع هایی را که مدل می‌کنید کلاس بوده و نه نقش‌هایی که وهله‌های آنها بازی می‌کنند. (Be sure the abstractions that you model are classes and not simply the roles objects play)
آیا مادر و پدر به عنوان یک کلاس هستند یا نقش‌هایی هستند که وهله‌های کلاس شخص، بازی می‌کند؟ پاسخ این سوال وابسته به دامینی (domain) است که طراح در حال مدل سازی آن می‌باشد. اگر در دامین مورد نظر، مادر و پدر رفتارهای مختلفی دارند، احتمالا باید به عنوان کلاس‌های جدا مدل شوند. اگر رفتارهای یکسانی دارند، در نتیجه نقش‌های مختلفی هستند که وهله‌های کلاس شخص بازی می‌کنند. به عنوان مثال، می‌توان کلاس خانواده را متشکل از وهله‌ای از کلاس پدر، وهله‌ای از کلاس مادر و مجموعه‌ای از وهله‌های کلاس فرزند در نظر گرفت. در مقابل ممکن است کلاس خانواده را متشکل از وهله‌ای از کلاس شخص به عنوان پدر، وهله‌ای از کلاس شخص به عنوان مادر و آرایه‌ای از وهله‌های شخص به عنوان فرزندان، مدل کنید. قرار گرفتن در وضیعتی که هر نقش، بخشی از رفتاری‌های شخص را مورد استفاده قرار می‌دهد، کافی نیست و باید مطمئن شوید که رفتار‌ها واقعا متفاوت می‌باشند. همچنین باید به یاد داشته باشید که زمانیکه وهله‌ای از بخشی از رفتارهای کلاس خود استفاده می‌کند، نیز مشکلی وجود ندارد و لازم نیست کلاس‌های دیگری را به خاطر این موضوع در طراحی خود در نظر بگیرید.

شکل 2.9 Two views of a family   

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

قواعد شهودی فصل دوم

قاعده شهودی 2.1 
همه داده‌ها باید در داخل کلاس خود پنهان شده باشند. (All data should be hidden within its class) 
قاعده شهودی 2.2
استفاده کنندگان از کلاس باید به واسط عمومی آن وابسته باشند، اما یک کلاس نباید به استفاده کنندگان خود، وابسته باشد. (Users of a class must be dependent on its public interface, but a class should not be dependent on its users)
قاعده شهودی 2.3
تعداد پیغام‌های موجود در قرارداد یک کلاس را کمینه سازید. (Minimize the number of messages in the protocol of a class) 
قاعده شهودی 2.4
پیاده سازی یک واسط عمومی یکسان کمینه برای همه کلاس‌ها  (Implement a minimal public interface that all classes understand [e.g., operations such as copy (deep versus shallow), equality testing, pretty printing, parsing from an ASCII description, etc.].) 
قاعده شهودی 2.5 
جزئیات پیاده سازی، مانند توابع خصوصی common-code  ( توابعی که کد مشترک سایر متدهای کلاس را در بدنه خود دارند) را در واسط عمومی یک کلاس قرار ندهید.  (Do not put implementation details such as common-code private functions into the public interface of a class)
قاعده شهودی 2.6 
واسط عمومی کلاس را با اقلامی که یا استفاده کنندگان از کلاس توانایی استفاده از آن را نداشته و یا تمایلی به استفاده از آنها ندارند، آمیخته نکنید.  (Do not clutter the public interface of a class with items that users of that class are not able to use or are not interested in using )
قاعده شهودی 2.7
اتصال و پیوستگی مابین کلاس‌ها باید از نوع Nil یا Export باشد؛ به این معنی که یک کلاس فقط از واسط عمومی کلاس دیگر استفاده کند یا کاری با آن نداشته باشد. (Classes should only exhibit nil or export coupling with other classes, that is, a class should only use operations in the public interface of another class or have nothing to do with that class.)
قاعده شهودی 2.8 
یک کلاس باید یک و تنها یک Key Abstraction را تسخیر نماید. (A class should capture one and only one key abstraction) 
قاعده شهودی 2.9 
داده و رفتار مرتبط را در یک جا (کلاس) نگه دارید. (Keep related data and behavior in one place)
قاعده شهودی 2.10 
اطلاعات نامرتبط به هم را در کلاس‌های جدا از هم قرار دهید. ((Spin off nonrelated information into another class (i.e., noncommunicating behavior)
قاعده شهودی 2.11
مطمئن باشید انتزاع هایی را که مدل می‌کنید کلاس بوده و نه نقش‌هایی که وهله‌های آنها بازی می‌کنند. (Be sure the abstractions that you model are classes and not simply the roles objects play)