Skip to content

7. اپلیکیشن [SimuPaie] – نسخهٔ – معماری سه‌لایه با NHibernate


مطالعهٔ پیشنهادی: «C# 2008، فصل ۴: معماری‌های سه‌لایه، تست‌های NUnit، فریم‌ورک Spring».


7.1. معماری کلی برنامه

اپلیکیشن [SimuPaie] اکنون ساختار سه‌لایه زیر را خواهد داشت:

  • لایه [1-dao] (DAO = Data Access Object) دسترسی به داده‌ها را مدیریت خواهد کرد.
  • لایه [2-métier] منطق کسب‌وکار برنامه، یعنی محاسبه حقوق و دستمزد، را مدیریت خواهد کرد.
  • لایه [3-ui] (ui=رابط کاربری) ارائه داده‌ها به کاربر و اجرای پرس‌وجوهای او را بر عهده دارد. ما مجموعه ماژول‌هایی را که این وظیفه را انجام می‌دهند، NET می‌نامیم. این لایه به‌عنوان رابط کاربری عمل می‌کند.
  • این سه لایه از طریق استفاده از رابط‌های .NET مستقل خواهند شد
  • یکپارچه‌سازی لایه‌های مختلف توسط Spring IoC انجام خواهد شد.

پردازش یک درخواست مشتری مراحل زیر را دنبال می‌کند:

  1. کلاینت درخواستی را به برنامه ارسال می‌کند.
  2. برنامه این درخواست را پردازش می‌کند. برای این کار ممکن است به کمک لایه [métier] نیاز داشته باشد که خود این لایه نیز در صورت نیاز به تبادل داده با پایگاه داده، ممکن است به لایه [dao] نیاز پیدا کند.
  3. برنامه پاسخ را از لایه [métier] دریافت می‌کند. بر اساس این پاسخ، نمای مناسب (= پاسخ) را برای کلاینت ارسال می‌کند.

بیایید مثال محاسبه حقوق یک پرستار بچه را در نظر بگیریم. این کار به چندین مرحله نیاز دارد:

  1. لایه [ui] باید از کاربر بپرسد
    • هویت فردی که برای او برگه پرداخت صادر می‌شود
    • تعداد روزهایی که کار کرده‌اند
    • تعداد ساعات کاری
  1. برای این کار، باید فهرست افراد (فامیل، نام، SS) موجود در جدول [EMPLOYES] را به کاربر نمایش دهد تا کاربر بتواند یکی از آنها را انتخاب کند. لایه [ui] از مسیر [2, 3, 4, 5, 6, 7] برای بازیابی این اطلاعات استفاده خواهد کرد. عملیات [2] درخواست فهرست کارمندان است، در حالی که عملیات [7] پاسخ به آن درخواست است. پس از انجام این کار، لایه [ui] می‌تواند فهرست کارمندان را از طریق [8] به کاربر نمایش دهد.
  2. کاربر تعداد روزهای کاری و تعداد ساعات کاری را به لایه [ui] ارسال می‌کند. این همان عملیات [1] است که در بالا ذکر شد. در این مرحله، کاربر تنها با لایه [ui] تعامل دارد. این لایه است که به‌ویژه اعتبار داده‌های وارد شده را بررسی می‌کند. پس از انجام این کار، کاربر محاسبه حقوق و دستمزد را درخواست خواهد کرد.
  3. لایه [ui] از لایه کسب‌وکار درخواست می‌کند تا این محاسبه را انجام دهد. برای این کار، داده‌هایی را که از کاربر دریافت کرده است، به آن منتقل می‌کند. این عملیات [2] است.
  4. لایه [metier] برای انجام وظیفه خود به اطلاعات خاصی نیاز دارد:
    • اطلاعات دقیق‌تر دربارهٔ فرد (آدرس، شاخص و غیره)
    • اضافه‌حقوق‌های مرتبط با شاخص مربوطه
    • نرخ‌های انواع حق بیمه‌های تأمین اجتماعی که باید از حقوق ناخالص کسر شوند

این لایه این اطلاعات را از طریق مسیر [3, 4, 5, 6] از لایه [dao] درخواست خواهد کرد. [3] درخواست اولیه و [6] پاسخ به آن درخواست است.

  1. پس از دریافت تمام داده‌های مورد نیاز، لایه [metier] حقوق فرد انتخاب‌شده توسط کاربر را محاسبه می‌کند.
  2. لایه [metier] اکنون می‌تواند به درخواست لایه [ui] که در (d) ارسال شده است، پاسخ دهد. این مسیر [7] است.
  3. لایه [ui] این نتایج را قالب‌بندی می‌کند تا آن‌ها را در شکلی مناسب به کاربر ارائه دهد و سپس نمایش دهد. این مسیر [8] است.
  4. ممکن است تصور شود که این نتایج باید در یک فایل یا پایگاه داده ذخیره شوند. این کار می‌تواند به‌طور خودکار انجام شود. در این صورت، پس از عملیات (f)، لایه [metier] به لایه [dao] دستور می‌دهد نتایج را ذخیره کند. این مسیر [3, 4, 5, 6] خواهد بود. این کار همچنین می‌تواند به درخواست کاربر انجام شود. مسیر [1-8] توسط چرخه درخواست–پاسخ استفاده خواهد شد.

از این توضیح برمی‌آید که یک لایه از منابع لایه سمت راست خود استفاده می‌کند و هرگز از منابع لایه سمت چپ خود بهره نمی‌برد.

اولین پیاده‌سازی ما از این معماری سه‌لایه، یک برنامه کاربردی ASP.NET خواهد بود، که در آن

  • لایه‌های [dao] و [metier] توسط DLL پیاده‌سازی خواهند شد.
  • لایه [ui] توسط فرم وب نسخه ۱ پیاده‌سازی خواهد شد (به بخش ۴.۲.۱ مراجعه کنید).

ما با پیاده‌سازی لایه [dao] با استفاده از چارچوب NHibernate شروع خواهیم کرد.

7.2. لایه دسترسی به داده‌های [dao]

7.2.1. پروژهٔ C# ویژوال استودیو برای لایهٔ [dao]

پروژه ویژوال استودیو برای لایه [dao] به شرح زیر است:

  • در [1]، پروژه به طور کلی
  • در [2]، کلاس‌های مختلف پروژه
  • در [3]، ارجاعات پروژه.
  • در [4]، پوشه‌ای به نام [lib] حاوی فایل‌های DLL مورد نیاز برای پروژه‌های مختلفی که در ادامه می‌آیند

مراجع پروژه [3] شامل فایل‌های زیر DLL هستند:

  • NHibernate: برای ORM و NHibernate
  • MySql.Data: درایور برای ADO.NET، که بخشی از SGBD و MySQL است
  • Spring.Core: برای فریم‌ورک Spring
  • log4net: یک کتابخانهٔ لاگ‌گیری
  • nunit.framework: یک کتابخانه تست واحد

این ارجاعات از پوشه [lib] [4] گرفته شده‌اند. باید اطمینان حاصل شود که ویژگی «Local Copy» برای همه این ارجاعات روی «True» تنظیم شده باشد [5]:

7.2.2. اشیاء در لایه [dao]

   

اشیاء (آبجکت‌ها) مورد نیاز برای لایه [dao] در پوشه [entites] درون پروژه گردآوری شده‌اند. برخی از این موارد قبلاً برای ما آشنا هستند: [Cotisations] که در بند 6.3.2.1 توصیف شده، [Employe] که در بند 6.3.2.3 توصیف شده، [Indemnites] که در بند 6.3.2.2 توصیف شده است. همگی در فضای نام [Pam.Dao.Entites] قرار دارند.

کلاس [Employe] به شرح زیر تکامل می‌یابد:


namespace Pam.Dao.Entites {
    public class Employe {
        // ویژگی‌های خودکار
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // توسعه‌دهندگان
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

7.2.3. کلاس [PamException]

لایه [dao] مسئول تبادل داده‌ها با یک منبع خارجی است. این تبادل ممکن است با شکست مواجه شود. برای مثال، اگر اطلاعاتی از یک سرویس دور از اینترنت درخواست شود، بازیابی آن به دلیل هرگونه خرابی شبکه با شکست مواجه خواهد شد. برای این نوع خطا، در جاوا مرسوم است که یک استثنا پرتاب شود. اگر استثناء از نوع [RunTimeException] یا یکی از انواع مشتق‌شده از آن نباشد، امضای متد باید مشخص کند که متد یک استثناء را پرتاب می‌کند. در .NET، تمام استثناءها بدون رسیدگی باقی می‌مانند، c.a.d. معادل نوع جاوا [RunTimeException] است. بنابراین نیازی نیست اعلام شود که متدهای [GetAllIdentitesEmployes, GetEmploye, GetCotisations] احتمالاً یک استثنا پرتاب می‌کنند.

با این حال، تشخیص تفاوت بین استثناءهای مختلف مفید است، زیرا نحوهٔ رسیدگی به آن‌ها ممکن است متفاوت باشد. بنابراین، کدی که به انواع مختلف استثناءها رسیدگی می‌کند، می‌تواند به شکل زیر نوشته شود:

try{
    ... code pouvant générer divers types d'exceptions
}catch (Exception1 ex1){
...on gère un type d'exceptions
}catch (Exception2 ex2){
...on gère un autre type d'exceptions
}finally{
...
}

بنابراین ما یک نوع استثنا برای لایه [dao] برنامه‌مان ایجاد می‌کنیم. این همان نوع [PamException] زیر است:


using System;
namespace Pam.Dao.Entites {

    public class PamException : Exception {

        // کد خطا 
        public int Code { get; set; }

        // سازنده‌ها 
        public PamException() {
        }

        public PamException(int Code)
            : base() {
            this.Code = Code;
        }

        public PamException(string message, int Code)
            : base(message) {
            this.Code = Code;
        }

        public PamException(string message, Exception ex, int Code)
            : base(message, ex) {
            this.Code = Code;
        }
    }
}
  • خط ۲: کلاس به فضای نام [Pam.Dao.Entites] تعلق دارد
  • خط ۴: این کلاس از کلاس [Exception] ارث می‌برد
  • خط ۷: دارای یک ویژگی عمومی [Code] است که یک کد خطا است
  • در لایه [dao] ما، از دو نوع سازنده استفاده خواهیم کرد:
    • نوع اول در خطوط ۱۸–۲۱ است که می‌توان آن را همان‌طور که در زیر نشان داده شده است استفاده کرد:
throw new PamException("Problème d'accès aux données",5);
  • (ادامه)
    • یا مورد خطوط ۲۳–۲۶، که برای انتشار استثنایی که قبلاً رخ داده است با محصور کردن آن در یک استثنای از نوع [PamException] طراحی شده است:
try{
....
}catch (IOException ex){
     // استثناء پیچیده شده است
    throw new PamException("Problème d'accès aux données",ex,10);
}

این روش دوم این مزیت را دارد که هیچ اطلاعاتی را که ممکن است در استثنای اول وجود داشته باشد، از دست نمی‌دهد.

7.2.4. جدول نگاشت <--> فایل‌های کلاس برای NHibernate

بیایید به معماری برنامه بازگردیم:

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

   
  • فایل [Cotisations.hbm.xml]، که در بخش 6.3.2.1 شرح داده شده است، تطابق بین جدول [COTISATIONS] و کلاس [Cotisations] را برقرار می‌کند.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Cotisations" table="COTISATIONS">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="CsgRds" column="CSGRDS" not-null="true"/>
        <property name="Csgd" column="CSGD" not-null="true"/>
        <property name="Retraite" column="RETRAITE" not-null="true"/>
        <property name="Secu" column="SECU" not-null="true"/>
    </class>
</hibernate-mapping>
  • فایل [Employe.hbm.xml]، همان‌طور که در بند 6.3.2.3 نشان داده شده است، تطابق بین جدول [EMPLOYES] و کلاس [Employe] را برقرار می‌کند

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS" length="15" not-null="true" unique="true"/>
        <property name="Nom" column="NOM" length="30" not-null="true"/>
        <property name="Prenom" column="PRENOM" length="20" not-null="true"/>
        <property name="Adresse" column="ADRESSE" length="50" not-null="true" />
        <property name="Ville" column="VILLE" length="30" not-null="true"/>
        <property name="CodePostal" column="CP" length="5" not-null="true"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>
  • فایل [Indemnites.hbm.xml]، که در بند 6.3.2.2 توصیف شده است، تطابق بین جدول [INDEMNITES] و کلاس [Indemnites] را برقرار می‌کند

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Indemnites" table="INDEMNITES">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="Indice" column="INDICE" not-null="true" unique="true"/>
        <property name="BaseHeure" column="BASE_HEURE" not-null="true"/>
        <property name="EntretienJour" column="ENTRETIEN_JOUR" not-null="true"/>
        <property name="RepasJour" column="REPAS_JOUR" not-null="true" />
        <property name="IndemnitesCp" column="INDEMNITES_CP" not-null="true"/>
    </class>
</hibernate-mapping>

توجه داشته باشید که در تگ <hibernate-mapping> در این فایل‌ها (خط ۲)، ویژگی‌های زیر وجود دارند:

  • namespace : Pam.Dao.Entites. کلاس‌های [Cotisations]، [Employe] و [Indemnites] باید در این فضای نام قرار گیرند.
  • assembly: pam-dao-nhibernate. فایل‌های نگاشت [*.hbm.xml] باید در داخل یک DLL به نام [pam-dao-nhibernate] قرار گیرند. برای این کار، پروژهٔ C# به صورت زیر پیکربندی می‌شود:
  • در [1]، اسمبلی پروژه با نام [pam-dao-nhibernate] نام‌گذاری شده است
  • در [2]، فایل‌های نگاشت [*.hbm.xml] به صورت [3] در مونتاژ پروژه ادغام شده‌اند

7.2.5. رابط [IPamDao] از لایه [dao]

بیایید به معماری برنامهٔ خود بازگردیم:

در موارد ساده، می‌توانیم از لایه [metier] شروع کنیم تا رابط‌های برنامه را کشف کنیم. برای کارکرد، به داده‌ها نیاز دارد:

  • که از قبل در فایل‌ها، پایگاه‌های داده یا از طریق شبکه در دسترس است. این داده‌ها توسط لایه [dao] فراهم می‌شوند.
  • هنوز در دسترس نیست. در این صورت، این لایه توسط [ui] فراهم می‌شود که آن را از کاربر برنامه دریافت می‌کند.

چه رابطی باید لایه [dao] در اختیار لایه [metier] قرار دهد؟ چه تعاملاتی بین این دو لایه ممکن است؟ لایه [dao] باید داده‌های زیر را در اختیار لایه [metier] قرار دهد:

  • فهرستی از مراقبان کودک تا کاربر بتواند یکی را به طور مشخص انتخاب کند
  • جزئیات کامل مراقب منتخب (آدرس، شاخص و غیره)
  • مزایای مرتبط با شاخص فرد
  • نرخ‌های مربوط به انواع حق بیمه‌های تأمین اجتماعی

در واقع این اطلاعات پیش از محاسبه حقوق و دستمزد در دسترس است و بنابراین می‌توان آن را ذخیره کرد. در جهت [metier] → [dao]، لایه [metier] می‌تواند از لایه [dao] بخواهد که نتیجه محاسبه حقوق و دستمزد را ذخیره کند. ما در اینجا این کار را انجام نخواهیم داد.

با این اطلاعات، می‌توانیم یک تعریف اولیه از رابط برای لایه [dao] ارائه دهیم:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // فهرست شناسهٔ تمام کارمندان 
        Employe[] GetAllIdentitesEmployes();
        // یک کارمند خاص و مزایای او 
        Employe GetEmploye(string ss);
        // فهرست تمام مشارکت‌ها 
        Cotisations GetCotisations();
    }
}
  • خط ۱: ما فضای نام انتیته‌ها را از لایه [dao] وارد می‌کنیم.
  • خط ۳: لایه [dao] در فضای نام [Pam.Dao.Service] قرار دارد. عناصر در فضای نام [Pam.Dao.Entites] می‌توانند در چندین نمونه ایجاد شوند. عناصر در فضای نام [Pam.Dao.Service] به صورت یک نمونه واحد (singleton) ایجاد می‌شوند. به همین دلیل این نام‌های فضای نام انتخاب شده‌اند.
  • خط ۴: رابط [IPamDao] نامیده می‌شود. این رابط سه متد را تعریف می‌کند:
    • خط ۶: [GetAllIdentitesEmployes] یک آرایه از اشیاء از نوع [Employe] را بازمی‌گرداند که نمایانگر فهرست پرستاران کودک به شکلی ساده‌شده (نام خانوادگی، نام، SS) است.
    • خط ۸، [GetEmploye] یک شیء [Employe] را بازمی‌گرداند: کارمندی با شماره تأمین اجتماعی که به‌عنوان پارامتر به متد ارسال شده است، همراه با مزایای مرتبط با شاخص او.
    • خط ۱۰: [GetCotisations] شیء [Cotisations] را بازمی‌گرداند که شامل نرخ‌های انواع کسورات تأمین اجتماعی است که از حقوق ناخالص کسر می‌شوند.

7.3. پیاده‌سازی و تست لایه [dao]

7.3.1. پروژه ویژوال استودیو

پروژه ویژوال استودیو قبلاً ارائه شده است. برای مرور:

  • در [1]، پروژه به طور کلی
  • در [2]، کلاس‌های مختلف پروژه. پوشه [entites] شامل اجزایی است که توسط لایه [dao] مدیریت می‌شوند و همچنین فایل‌های نگاشت NHibernate. پوشه [service] شامل رابط [IPamDao] و پیاده‌سازی آن، [PamDaoNHibernate] است. پوشه [tests] شامل یک تست کنسولی [Main.cs] و یک تست واحد [NUnit.cs] است.
  • در [3]، پروژه ارجاع می‌دهد.

7.3.2. برنامهٔ تست کنسول [Main.cs]

برنامه آزمون [Main.cs] در معماری زیر اجرا می‌شود:

مسئول تست روش‌های رابط [IPamDao] است. یک مثال پایه ممکن است به شرح زیر باشد:


using System;
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Spring.Context.Support;

namespace Pam.Dao.Tests {
    public class MainPamDaoTests {
        public static void Main() {
            try {
                // مثال‌سازی لایه [dao]
                IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
                //فهرست سوابق کارکنان 
                foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
                    Console.WriteLine(Employe.ToString());
                }
                // یک کارمند به همراه مزایای او 
                Console.WriteLine("------------------------------------");
                Console.WriteLine(pamDao.GetEmploye("254104940426058"));
                Console.WriteLine("------------------------------------");
                // فهرست مشارکت‌ها 
                Cotisations cotisations = pamDao.GetCotisations();
                Console.WriteLine(cotisations.ToString());
            } catch (Exception ex) {
                //نمایش استثنا 
                Console.WriteLine(ex.ToString());
            }
            //پایان 
            Console.ReadLine();
        }
    }
}
  • خط ۱۱: از Spring خواسته می‌شود تا مرجعی به لایه [dao] ارائه دهد.
  • خطوط ۱۳–۱۵: تست روش [GetAllIdentitesEmployes] از رابط [IPamDao]
  • خط ۱۸: تست متد [GetEmploye] از رابط [IPamDao]
  • خط ۲۱: آزمون روش [GetCotisations] از رابط [IPamDao]

Spring، NHibernate و log4net توسط فایل زیر پیکربندی شده‌اند: :


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!--بخش‌های پیکربندی -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <sectionGroup name="spring">
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
        </sectionGroup>
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!--پیکربندی بهار -->
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
        </objects>
    </spring>

    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-dao-nhibernate"/>
        </session-factory>
    </hibernate-configuration>

    <!-- این بخش حاوی تنظیمات پیکربندی log4net است -->
    <!-- NOTE IMPORTANTE: لاگ‌ها به‌طور پیش‌فرض فعال نیستند. باید از طریق برنامه فعال شوند
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
    ...
    </log4net>

</configuration>

پیکربندی NHibernate (خط ۱۰، خطوط ۲۵–۳۶) در بخش ۶.۳.۱ توضیح داده شده است. به خط ۳۴ توجه کنید که مشخص می‌کند فایل‌های نگاشت در اسمبلی [pam-dao-nhibernate] قرار دارند. این اسمبلی پروژه است.

پیکربندی Spring در خطوط 6–9 و 15–22 ارائه شده است. خط 20 شیء [pamdao] را که توسط برنامه کنسولی [Main.cs] استفاده می‌شود، تعریف می‌کند. تگ <object> در اینجا دارای ویژگی‌های زیر است:

  • type: کلاسی را که باید نمونه‌سازی شود مشخص می‌کند. این کلاس [PamDaoNHibernate] است که رابط [IPamDao] را پیاده‌سازی می‌کند. این کلاس را می‌توان در DLL [pam-dao-nhibernate] پروژه یافت.
  • init-method: متد کلاس [PamDaoNHibernate] که پس از ایجاد نمونه از کلاس اجرا می‌شود
  • destroy-method: متد کلاس [PamDaoNHibernate] که باید هنگام تخریب کانtejner Spring در پایان اجرای پروژه اجرا شود.

اجرای برنامه با پایگاه داده توصیف‌شده در بخش 6.2، خروجی کنسول زیر را تولید می‌کند:

1
2
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • خطوط ۱–۲: دو کارمند از نوع [Employe]، با اطلاعات فقط برای [SS, Nom, Prenom]
  • خط ۴: کارمند با نوع [Employe] و شماره تأمین اجتماعی [254104940426058]
  • خط ۵: نرخ‌های مشارکت

7.3.3. اعلامیه برای کلاس [PamDaoNHibernate]

رابط [IPamDao] که توسط لایه [dao] پیاده‌سازی شده است به شرح زیر است:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // فهرست تمام هویت‌های کارکنان 
        Employe[] GetAllIdentitesEmployes();
        // یک کارمند خاص و مزایای او 
        Employe GetEmploye(string ss);
        // فهرست تمام مشارکت‌ها 
        Cotisations GetCotisations();
    }
}

سؤال: کد کلاس [PamDaoNHibernate] را بنویسید که رابط [IPamDao] فوق را با استفاده از فریم‌ورک NHibernate که همانند توضیحات قبلی پیکربندی شده است، پیاده‌سازی می‌کند. ما همچنین متدهای init و destroy را که توسط Spring اجرا می‌شوند، پیاده‌سازی خواهیم کرد. متد init نمونه SessionFactory را ایجاد خواهد کرد، که از آن اشیاء Session را به دست خواهیم آورد. متد destroy این SessionFactory را می‌بندد. ما به مثال‌های بخش 6.5 مراجعه خواهیم کرد.


محدودیت‌ها:

فرض می‌کنیم که برخی از داده‌های درخواست‌شده از لایه [dao] را می‌توان به‌طور کامل در حافظه نگهداری کرد. بنابراین، برای بهبود عملکرد، کلاس [PamDaoNHibernate] موارد زیر را ذخیره خواهد کرد:

  • جدول [EMPLOYES] به صورت (SS, NOM, PRENOM) مورد نیاز متد [GetAllIdentitesEmployes] به صورت آرایه‌ای از اشیاء از نوع [Employe]
  • جدول [COTISATIONS] به صورت یک شیء واحد از نوع [Cotisations]

این کار در متد [init] کلاس انجام خواهد شد. اسکلت کلاس [PamDaoNHibernate] می‌تواند به شکل زیر باشد:


using System;
...

namespace Pam.Dao.Service {
    class PamDaoNHibernate : IPamDao {
        // میدان‌های خصوصی 
        private Cotisations cotisations;
        private Employe[] employes;
        private ISessionFactory sessionFactory = null;

        // ابتدایی 
        public void init() {
            try {
                // ابتکاری‌سازی کارخانه
                sessionFactory = new Configuration().Configure().BuildSessionFactory();
                //بازیابی نرخ‌های مشارکت و کارمندان برای کش کردن آنها 
.......................
        }

        //بستن SessionFactory
        public void destroy() {
            if (sessionFactory != null) {
                sessionFactory.Close();
            }
        }

        // فهرست تمام شناسه کارمندان 
        public Employe[] GetAllIdentitesEmployes() {
            return employes;
        }

        //یک کارمند مشخص و مزایای او 
        public Employe GetEmploye(string ss) {
................................
        }

        //فهرست مشارکت‌ها 
        public Cotisations GetCotisations() {
            return cotisations;
        }
    }
}

7.3.4. تست‌های واحد با NUnit


مطالعهٔ پیشنهادی: «C# 2008، فصل ۴: معماری‌های سه‌لایه، تست NUnit، فریم‌ورک Spring».


آزمون قبلی بصری بود: ما در روی صفحه بررسی کردیم که واقعاً نتایج مورد انتظار را دریافت می‌کنیم. این روش در یک محیط حرفه‌ای ناکافی است. آزمون‌ها باید تا حد امکان خودکار شوند و هدف این باشد که به هیچ مداخله انسانی نیازی نداشته باشند. انسان‌ها، در نهایت، مستعد خستگی هستند و توانایی آن‌ها برای تأیید آزمون‌ها با گذشت روز کاهش می‌یابد. ابزار [NUnit] به دستیابی به این اتوماسیون کمک می‌کند. این ابزار در URL [http://www.nunit.org/] در دسترس است.

پروژه ویژوال استودیو برای لایه [dao] به شرح زیر تکامل خواهد یافت:

  • به [1]؛ برنامه آزمایشی [NUnit.cs]
  • به [2,3]؛ پروژه یک DLL به نام [pam-dao-nhibernate.dll] تولید خواهد کرد
  • در [4]، ارجاع به DLL در چارچوب NUnit: [nunit.framework.dll]
  • به [5]، کلاس [Main.cs] در DLL [pam-dao-nhibernate] گنجانده نخواهد شد
  • به [6]، کلاس [NUnit.cs] در DLL گنجانده خواهد شد [pam-dao-nhibernate]

کلاس تست NUnit به شرح زیر است:


using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;

namespace Pam.Dao.Tests {

    [TestFixture]
    public class NunitPamDao : AssertionHelper {
        //لایه [dao] برای آزمایش 
        private IPamDao pamDao = null;

        // تولیدکننده 
        public NunitPamDao() {
            // نمونه لایه [dao]
            pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        }

        // ابتدا 
        [SetUp]
        public void Init() {

        }

        [Test]
        public void GetAllIdentitesEmployes() {
            //بررسی تعداد کارمندان 
            Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetCotisations() {
            // بررسی نرخ مشارکت 
            Cotisations cotisations = pamDao.GetCotisations();
            Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
            Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
            Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
            Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites() {
            // تأیید فرد 
            Employe employe1 = pamDao.GetEmploye("254104940426058");
            Employe employe2 = pamDao.GetEmploye("260124402111742");
            Expect("Jouveinal", EqualTo(employe1.Nom));
            Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
            Expect("Laverti", EqualTo(employe2.Nom));
            Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites2() {
            //بررسی فرد وجود ندارد 
            bool erreur = false;
            try {
                Employe employe1 = pamDao.GetEmploye("xx");
            } catch {
                erreur = true;
            }
            Expect(erreur, True);
        }
    }
}
  • خط ۱۱: کلاس دارای ویژگی [TestFixture] است که آن را به یک کلاس تست [NUnit] تبدیل می‌کند.
  • خط ۱۲: این کلاس از کلاس ابزار AssertionHelper در چارچوب NUnit (از نسخه ۲.۴.۶ به بعد) ارث می‌برد.
  • خط ۱۴: فیلد خصوصی [pamDao] نمونه‌ای از رابط دسترسی لایه [dao] است. توجه داشته باشید که نوع این فیلد یک اینترفیس است و نه یک کلاس. این بدان معناست که ایننس [pamDao] تنها متدها را در دسترس قرار می‌دهد، یعنی متدهای اینترفیس [IPamDao].
  • روش‌های آزمایش‌شده در کلاس، آن‌هایی هستند که دارای ویژگی [Test] می‌باشند. برای همه این روش‌ها، فرآیند تست به شرح زیر است:
    • ابتدا متدی که دارای ویژگی [SetUp] است اجرا می‌شود. این متد برای آماده‌سازی منابع مورد نیاز آزمون (اتصالات شبکه، اتصالات پایگاه داده و غیره) استفاده می‌شود.
    • سپس متد مورد آزمایش اجرا می‌شود
    • و در نهایت، متدی که دارای ویژگی [TearDown] است اجرا می‌شود. این متد عموماً برای آزادسازی منابعی استفاده می‌شود که توسط متد دارای ویژگی [SetUp] تخصیص داده شده‌اند.
  • در تست ما، هیچ منبعی برای تخصیص دادن قبل از هر تست و سپس آزاد کردن آن پس از آن وجود ندارد. بنابراین، ما به متدهایی با ویژگی‌های [SetUp] و [TearDown] نیازی نداریم. برای این مثال، ما در خطوط ۲۳–۲۶، متدی با ویژگی [SetUp] را نشان داده‌ایم.
  • خطوط ۱۷–۲۰: سازنده کلاس فیلد خصوصی [pamDao] را با استفاده از Spring و [App.config] مقداردهی اولیه می‌کند.
  • خطوط ۲۹–۳۲: تست متد [GetAllIdentitesEmployes]
  • خطوط ۳۵–۴۲: تست متد [GetCotisations]
  • خطوط ۴۵–۵۳: تست متد [GetEmploye]
  • خطوط ۵۶–۶۵: تست متد [GetEmploye] هنگام رخ دادن یک استثنا.

تولید پروژه، فایل‌های DLL و [pam-dao-nhibernate.dll] را در پوشه [bin/Release] ایجاد می‌کند.

پوشه [bin/Release] همچنین شامل موارد زیر است:

  • فایل‌های DLL که بخشی از مراجع پروژه را تشکیل می‌دهند و ویژگی [Copie locale] برای آن‌ها روی true تنظیم شده است: [Spring.Core, MySql.data, NHibernate, log4net]. این فایل‌های DLL همراه با نسخه‌هایی از فایل‌های DLL که خودشان استفاده می‌کنند، ارائه شده‌اند:
    • [CastleDynamicProxy, Iesi.Collections] برای ابزار NHibernate
    • [antlr.runtime, Common.Logging] برای ابزار Spring
  • فایل [pam-dao-nhibernate.dll.config] کپی‌ای از فایل پیکربندی [App.config] است. این VS است که این تکثیر را انجام می‌دهد. در زمان اجرا، از فایل [pam-dao-nhibernate.dll.config] استفاده می‌شود، نه [App.config].

ما با استفاده از ابزار [NUnit-Gui]، نسخه 2.4.6، فایل‌های DLL و [pam-dao-nhibernate.dll] را بارگذاری کرده و تست‌ها را اجرا می‌کنیم:

Image

تست‌های بالا با موفقیت انجام شدند.

تمرین عملی:


  • تست‌ها را برای کلاس [PamDaoNHibernate] روی ماشین پیاده‌سازی کنید.
  • از فایل‌های پیکربندی مختلف [App.config] استفاده کنید تا از پایگاه‌داده‌های مختلف SGBD (Firebird، MySQL، Postgres، SQL Server) استفاده کنید.

7.3.5. تولید of from لایه [dao]

پس از نوشتن و آزمایش کلاس [PamDaoNHibernate]، کلاس DLL از لایه [dao] به شرح زیر تولید خواهد شد:

  • [1]؛ برنامه‌های تست از ساخت پروژه مستثنی شده‌اند
  • [2,3]، پیکربندی پروژه
  • [4]، تولید پروژه
  • DLL در پوشه [bin/Release] [5] تولید می‌شود. ما آن را به فایل‌های DLL که از قبل در پوشه [lib] [6] موجود هستند اضافه می‌کنیم:

7.4. لایه کسب‌وکار

بیایید نگاهی دیگر به معماری کلی برنامه [SimuPaie] بیندازیم:

اکنون فرض می‌کنیم که لایه [dao] کامل شده و در داخل DLL و [pam-dao-nhibernate.dll] قرار گرفته است. اکنون بر لایه [metier] تمرکز می‌کنیم. این لایه قوانین کسب‌وکار را پیاده‌سازی می‌کند، در این مورد قوانین محاسبه حقوق.

7.4.1. پروژهٔ « » در ویژوال استودیو برای لایهٔ [metier]

پروژه ویژوال استودیو برای لایه کسب‌وکار ممکن است چیزی شبیه به این باشد:

  • در [1]، کل پروژه توسط فایل [App.config] پیکربندی شده است
  • در [2]، لایه [metier] از دو پوشه [entites, service] تشکیل شده است. پوشه [tests] شامل یک برنامه تست کنسول (Main.cs) و یک برنامه تست NUnit (NUnit.cs) است.
  • [3] فهرست مراجعی را که پروژه از آن‌ها استفاده می‌کند، نمایش می‌دهد. توجه داشته باشید DLL و [pam-dao-nhibernate] از لایه [dao] که قبلاً مورد بحث قرار گرفت.

7.4.2. رابط [IPamMetier] از لایه [metier]

بیایید به معماری کلی برنامه بازگردیم:

چه رابطی باید لایه [metier] در اختیار لایه [ui] قرار دهد؟ چه تعاملاتی بین این دو لایه ممکن است؟ بیایید رابط وبی را که به کاربر ارائه خواهد شد، به یاد آوریم:

  1. هنگامی که فرم برای اولین بار نمایش داده می‌شود، لیست کارمندان باید در [1] در دسترس باشد. یک لیست ساده‌شده کافی است (نام خانوادگی، نام، SS). برای دسترسی به اطلاعات اضافی در مورد کارمند انتخاب‌شده (fields 6 تا 11)، به مرجع SS نیاز است.
  2. موارد ۱۲ تا ۱۵ نرخ‌های مختلف مشارکت را نشان می‌دهند.
  3. بندهای ۱۶ تا ۱۹ مزایای مرتبط با شاخص کارمند هستند
  4. بندهای ۲۰ تا ۲۴ اجزای حقوق هستند که بر اساس ورودی‌های ۱ تا ۳ انجام‌شده توسط کاربر محاسبه می‌شوند.

رابط [IPamMetier] که توسط لایه [metier] به لایه [ui] ارائه می‌شود، باید الزامات فوق را برآورده سازد. رابط‌های ممکن زیادی وجود دارد. ما موارد زیر را پیشنهاد می‌کنیم:


using Pam.Dao.Entites;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {
    public interface IPamMetier {
        // فهرست تمام شناسه کارمندان 
        Employe[] GetAllIdentitesEmployes();

        // ------- محاسبه حقوق 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
    }
}
  • خط ۷: متدی که کادر ترکیبی [1] را پر می‌کند
  • خط ۱۰: روشی که برای بازیابی اطلاعات ۶ تا ۲۴ استفاده می‌شود. این اطلاعات در یک شیء از نوع [FeuilleSalaire] گروه‌بندی شده‌اند.

7.4.3. اشیاء در لایه [metier]

پوشه [entites] در پروژه Visual Studio شامل اشیایی است که توسط کلاس کسب‌وکار مدیریت می‌شوند: [FeuilleSalaire] و [ElementsSalaire].

کلاس [FeuilleSalaire] اطلاعات ۶ تا ۲۴ را از فرم قبلی در بر می‌گیرد:


using Pam.Dao.Entites;

namespace Pam.Metier.Entites {

    public class FeuilleSalaire {

        // ویژگی‌های خودکار 
        public Employe Employe { get; set; }
        public Cotisations Cotisations { get; set; }
        public ElementsSalaire ElementsSalaire { get; set; }

        // ToString 
        public override string ToString() {
            return string.Format("[{0},{1},{2}", Employe, Cotisations, ElementsSalaire);
        }
    }
}
  • خط ۸: جزئیات ۶ تا ۱۱ مربوط به کارمندی که حقوق او محاسبه می‌شود، و جزئیات ۱۶ تا ۱۹ مربوط به مزایای او. مهم است که در اینجا توجه داشته باشیم که یک شیء [Employe] یک شیء [Indemnites] را که نمایانگر مزایای کارمند است، در بر می‌گیرد.
  • خط ۹: جزئیات ۱۲ تا ۱۵
  • خط ۱۰: اطلاعات ۲۰ تا ۲۴
  • خطوط ۱۳–۱۵: متد [ToString]

کلاس [ElementsSalaire] اطلاعات ۲۰ تا ۲۴ را از فرم در بر می‌گیرد:


namespace Pam.Metier.Entites {
    public class ElementsSalaire {
        // ویژگی‌های خودکار 
        public double SalaireBase { get; set; }
        public double CotisationsSociales { get; set; }
        public double IndemnitesEntretien { get; set; }
        public double IndemnitesRepas { get; set; }
        public double SalaireNet { get; set; }


        // ToString 
        public override string ToString() {
            return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
        }
    }
}
  • خطوط ۴–۸: اجزای حقوق طبق توضیحات قوانین کسب‌وکار در بند ۳.۲.
  • خط ۴: حقوق پایه کارمند، بر اساس تعداد ساعات کاری
  • خط ۵: مشارکت‌های کسرشده از این حقوق پایه
  • خطوط ۶ و ۷: مزایای قابل افزودن به حقوق پایه، بسته به شاخص کارمند و تعداد روزهای کاری
  • خط ۸: حقوق خالص قابل پرداخت
  • خطوط ۱۲–۱۵: متد کلاس [ToString].

7.4.4. پیاده‌سازی لایه [metier]

ما رابط [IPamMetier] را با استفاده از دو کلاس پیاده‌سازی خواهیم کرد:

  • [AbstractBasePamMetier]، که یک کلاس انتزاعی است و در آن دسترسی به داده‌ها برای رابط [IPamMetier] را پیاده‌سازی خواهیم کرد. این کلاس به لایه [dao] ارجاع خواهد داد.
  • [PamMetier] کلاسی مشتق‌شده از [AbstractBasePamMetier] است که به نوبه خود قواعد کسب‌وکار رابط [IPamMetier] را پیاده‌سازی می‌کند. این کلاس از لایه [dao] بی‌خبر خواهد بود.

کلاس [AbstractBasePamMetier] به شرح زیر خواهد بود:


using Pam.Dao.Entites;
using Pam.Dao.Service;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {
    public abstract class AbstractBasePamMetier : IPamMetier {

        // ابژه دسترسی به داده‌ها 
        public IPamDao PamDao { get; set; }

        // فهرست تمام شناسه کارمندان 
        public Employe[] GetAllIdentitesEmployes() {
            return PamDao.GetAllIdentitesEmployes();
        }

        // یک کارمند مشخص و مزایای او 
        protected Employe GetEmploye(string ss) {
            return PamDao.GetEmploye(ss);
        }

        // سهم‌ها 
        protected Cotisations GetCotisations() {
            return PamDao.GetCotisations();
        }

        // محاسبه حقوق 
        public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
    }
}
  • خط ۵: این کلاس به فضای نام [Pam.Metier.Service] تعلق دارد، همانند تمام کلاس‌ها و رابط‌ها در لایه [metier].
  • خط ۶: کلاس انتزاعی است (ویژگی abstract) و رابط [IPamMetier] را پیاده‌سازی می‌کند
  • خط ۹: کلاس یک مرجع به لایه [dao] را در قالب یک ویژگی عمومی نگه می‌دارد
  • خطوط ۱۲–۱۴: پیاده‌سازی متد [GetAllIdentitesEmployes] از رابط [IPamMetier] – از متد هم‌نام در لایه [dao] استفاده می‌کند
  • خطوط 17–19: متد داخلی (محافظت‌شده) [GetEmploye]، که متد هم‌نام را در لایه [dao] فراخوانی می‌کند – به عنوان protected اعلام شده است تا کلاس‌های مشتق‌شده بتوانند بدون اینکه عمومی باشد به آن دسترسی داشته باشند.
  • خطوط ۲۲–۲۴: متد داخلی (محافظت‌شده) [GetCotisations]، که متد هم‌نام را در لایه [dao] فراخوانی می‌کند
  • خط ۲۷: پیاده‌سازی انتزاعی (ویژگی abstract) متد [GetSalaire] از رابط [IPamMetier].

محاسبه حقوق توسط کلاس زیر [PamMetier] پیاده‌سازی شده است:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {

    public class PamMetier : AbstractBasePamMetier {

        // محاسبه حقوق 
        public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
            //SS: شماره SS کارمند 
            //HeuresTravaillées: تعداد ساعات کاری 
            // روزهای کاری: تعداد روزهای کاری 
            // اطلاعات کارمند و مزایای او را بازیابی می‌کند 
            ...
            //بازیابی نرخ‌های مختلف مشارکت 
            ...
            // موارد تشکیل‌دهنده حقوق را محاسبه می‌کند 
            ...
            // تولید فیش حقوقی 
            return ...;
        }
    }
}
  • خط ۷: این کلاس از [AbstractBasePamMetier] ارث می‌برد و بنابراین رابط [IPamMetier] را پیاده‌سازی می‌کند
  • خط ۱۰: متد [GetSalaire] باید پیاده‌سازی شود

سؤال: کد متد [GetSalaire] را بنویسید.


7.4.5. آزمون کنسول برای لایه [metier]

بیایید پروژهٔ Visual Studio برای لایهٔ [metier] را به یاد بیاوریم:

برنامه تست [Main] بالا، متدهای رابط [IPamMetier] را تست می‌کند. یک مثال پایه ممکن است به این صورت باشد:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace Pam.Metier.Tests {
    class MainPamMetierTests {
        public static void Main() {
            try {
                // ایجاد لایه [metier]
                IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
                //محاسبات فیش حقوقی 
                Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
                Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
                try {
                    Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
                } catch (PamException ex) {
                    Console.WriteLine(string.Format("PamException : {0}", ex.Message));
                }
            } catch (Exception ex) {
                Console.WriteLine(string.Format("Exception : {0}", ex.ToString()));
            }
            //مکث 
            Console.ReadLine();
        }
    }
}
  • خط ۱۱: نمونه‌سازی Spring لایه [metier].
  • خطوط ۱۳–۱۴: تست متد [GetSalaire] از رابط [IPamMetier]
  • خطوط ۱۵–۲۲: تست متد [GetSalaire] هنگام رخ دادن استثنا

برنامهٔ آزمون از فایل پیکربندی زیر استفاده می‌کند [App.config] :


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!--بخش‌های پیکربندی -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <sectionGroup name="spring">
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
        </sectionGroup>
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!-- پیکربندی Spring -->
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
            <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
                <property name="PamDao" ref="pamdao"/>
            </object>
        </objects>
    </spring>

    <!-- پیکربندی NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
    </hibernate-configuration>

    <!-- این بخش شامل تنظیمات پیکربندی log4net است -->
    <!--NOTE IMPORTANTE: لاگ‌ها به‌صورت پیش‌فرض فعال نیستند. باید به‌صورت برنامه‌ای فعال شوند
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
...
    </log4net>

</configuration>

این فایل با فایل [App.config] مورد استفاده برای پروژه لایه [dao] (به بخش 7.3.2 مراجعه کنید) یکسان است، به جز جزئیات زیر:

  • خط ۲۰: شیء با شناسه "pamdao" از نوع [Pam.Dao.Service.PamDaoNHibernate] است و در مجموعه [pam-dao-nhibernate] یافت می‌شود. لایه [dao] همان لایه‌ای است که قبلاً بررسی شده است.
  • خطوط 21–23: شیء با شناسه «pammetier» از نوع [Pam.Metier.Service.PamMetier] است و در مونتاژ [pam-metier-dao-nhibernate] یافت می‌شود. پروژه باید به صورت زیر پیکربندی شود:
 
  • خط ۲۲: شیء [PamMetier] که توسط Spring نمونه سازی شده است، دارای یک ویژگی عمومی [PamDao] است که ارجاعی به لایه [dao] می‌باشد. این خاصیت با ارجاع به لایه [dao] که در خط ۲۰ ایجاد شده است، مقداردهی اولیه می‌شود.

اجرای کد بر روی پایگاه داده توصیف‌شده در بخش 6.2، خروجی کنسول زیر را تولید می‌کند:

1
2
3
[[260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014,[1, 1,93, 2, 3, 12]],[3,49,6,15,9,39,7,88],[1, 1,93, 2, 3, 12],[64,85 : 17,45 : 10 : 15 : 72,4 ]
[[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]],[3,49,6,15,9,39,7,88],[2, 2,1, 2,1, 3,1, 15],[362,25 : 97,48 : 42: 62 : 368,77 ]
PamException : L'employé de n° ss [xx] n'existe pas
  • خطوط ۱–۲: دو فیش حقوقی درخواست‌شده
  • خط ۳: استثنای نوع [PamException] ناشی از یک کارمند وجود ندارد.

7.4.6. تست‌های واحد برای لایه کسب‌وکار

آزمون قبلی بصری بود: ما روی صفحه بررسی کردیم که واقعاً نتایج مورد انتظار را دریافت می‌کنیم. اکنون به آزمون‌های غیربصری NUnit می‌رویم.

بیایید به پروژه Visual Studio برای [metier] بازگردیم:

  • در [1]، برنامه آزمون NUnit
  • به [2]، مرجع در DLL [nunit.framework]
  • به [3,4]، ساخت پروژه، DLL و [pam-metier-dao-nhibernate.dll] را تولید خواهد کرد.
  • در [5]، فایل [NUnit.cs] در اسمبلی [pam-metier-dao-nhibernate.dll] گنجانده می‌شود اما در [Main.cs] یا [6] گنجانده نمی‌شود

کلاس تست NUnit به شرح زیر است:


using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace Pam.Metier.Tests {

    [TestFixture()]
    public class NunitTestPamMetier : AssertionHelper {

        //لایه [metier] برای آزمایش 
        private IPamMetier pamMetier;

        // سازنده
        public NunitTestPamMetier() {
            // مثال‌سازی لایه [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }


        [Test]
        public void GetAllIdentitesEmployes() {
            // بررسی تعداد کارکنان 
            Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetSalaire1() {
            //محاسبه حقوق و دستمزد 
            FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
            // چک‌ها 
            Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
            // برگه پرداخت برای کارمند وجود ندارد 
            bool erreur = false;
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }

    }
}
  • خط ۱۳: فیلد خصوصی [pamMetier] یک نمونه از رابط دسترسی به لایه [metier] است. توجه داشته باشید که نوع این فیلد یک اینترفیس است، نه یک کلاس. این بدان معناست که نمونه [PamMetier] تنها متدها را قابل دسترسی می‌سازد، یعنی متدهای اینترفیس [IPamMetier].
  • خطوط ۱۶–۱۹: سازنده کلاس فیلد خصوصی [pamMetier] را با استفاده از Spring و فایل پیکربندی [App.config] مقداردهی اولیه می‌کند.
  • خطوط ۲۳–۲۶: تست متد [GetAllIdentitesEmployes]
  • خطوط ۲۹–۴۲: تست متد [GetSalaire]

پروژه فوق، کلاس‌های DLL و [pam-metier.dll] را در پوشه [bin/Release] تولید می‌کند.

پوشه [bin/Release] همچنین شامل موارد زیر است:

  • فایل‌های DLL که بخشی از ارجاعات پروژه را تشکیل می‌دهند و ویژگی [Copie locale] برای آن‌ها روی true تنظیم شده است: [Spring.Core, MySql.data, NHibernate, log4net, pam-dao-nhibernate]. این فایل‌های DLL همراه با نسخه‌هایی از فایل‌های DLL که خودشان استفاده می‌کنند، ارائه شده‌اند:
    • [CastleDynamicProxy, Iesi.Collections] برای ابزار NHibernate
    • [antlr.runtime, Common.Logging] برای ابزار Spring
  • فایل [pam-metier-dao-nhibernate.dll.config] کپی‌ای از فایل پیکربندی [App.config] است.

ما با استفاده از ابزار [NUnit-Gui, version 2.4.6]، فایل‌های DLL و [pam-metier-dao-nhibernate.dll] را بارگذاری کرده و تست‌ها را اجرا می‌کنیم:

Image

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

تمرین عملی:


  • تست‌ها را برای کلاس [PamMetier] روی ماشین پیاده‌سازی کنید.
  • از فایل‌های پیکربندی مختلف App.config استفاده کنید تا از پایگاه‌های داده مختلف SGBD (Firebird، MySQL، Postgres، SQL Server) استفاده کنید.

7.4.7. تولید DLL از لایه [metier]

پس از نوشتن و آزمایش کلاس [PamMetier]، کلاس‌های DLL و [pam-metier-dao-nhibernate.dll] از لایه [metier]، مطابق روشی که در بخش 7.3 توضیح داده شده است، تولید خواهند شد باید مراقب بود که برنامه‌های آزمون [Main.cs] و [NUnit.cs] در DLL گنجانده نشوند. سپس باید در پوشه [lib] درون پوشه‌های DLL و [1] قرار داده شود.

7.5. لایه [web]

بیایید نگاهی دیگر به معماری کلی برنامه [SimuPaie] بیندازیم:

ما فرض می‌کنیم که لایه‌های [dao] و [métier] از قبل در جای خود قرار دارند و در لایه‌های DLL و [pam-dao-nhibernate, pam-metier-dao-nhibernate.dll] محصور شده‌اند. اکنون لایه وب را توصیف خواهیم کرد.

7.5.1. پروژه Visual Web Developer برای لایه [web]

  • در [1]، پروژه به طور کلی:
    • [Global.asax]: کلاسی که هنگام راه‌اندازی برنامه وب ایجاد می‌شود و مسئول راه‌اندازی اولیه برنامه است
    • [Default.aspx]: صفحه فرم وب
  • در [2]، فایل‌های DLL مورد نیاز وب‌اپلیکیشن. به فایل‌های DLL برای لایه‌های [dao] و [metier] که قبلاً ایجاد شده‌اند توجه کنید.

7.5.2. پیکربندی برنامه

فایل [Web.config] که برنامه را پیکربندی می‌کند، داده‌های یکسانی را با فایل [App.config] که لایه [metier] را که قبلاً بررسی شده بود پیکربندی می‌کند، تعریف می‌کند. این‌ها باید در کد از پیش تولید شده فایل [Web.config] درج شوند:


<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <configSections>
    <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
..........
    </sectionGroup>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
    <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>

  <!-- پیکربندی بهار -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
        <property name="PamDao" ref="pamdao"/>
      </object>
    </objects>
  </spring>

  <!-- NHibernate پیکربندی -->
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
      <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
      <property name="connection.connection_string">
        Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
      </property>
      <property name="show_sql">false</property>
      <mapping assembly="pam-dao-nhibernate"/>
    </session-factory>
  </hibernate-configuration>

  <!-- این بخش شامل تنظیمات پیکربندی log4net است -->
  <!--NOTE IMPORTANTE: لاگ‌ها به‌طور پیش‌فرض فعال نیستند. باید از طریق برنامه فعال شوند
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
  <log4net>
....
  </log4net>

  <appSettings/>
  <connectionStrings/>

  <system.web>
....
....

</configuration>

خطوط ۹–۱۲، ۱۸–۲۸ و ۳۱–۴۴ حاوی پیکربندی Spring و NHibernate هستند که در فایل [App.config] برای لایه [metier] توصیف شده است (به بخش ۷.۴.۵ مراجعه کنید).

Global.asax.cs


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace pam_v3
{
  public class Global : System.Web.HttpApplication
  {
    // --- داده‌های ثابت برنامه ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;
    public static string Msg;
    public static bool Erreur = false;

    // راه‌اندازی برنامه
    public void Application_Start(object sender, EventArgs e)
    {
      // پردازش فایل پیکربندی
      try
      {
        // مثال‌سازی لایه [metier]
        PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // فهرست ساده‌شده کارمندان
        Employes = PamMetier.GetAllIdentitesEmployes();
        // موفقیت
        Msg = "Base chargée...";
      }
      catch (Exception ex)
      {
        // خطا ثبت شد
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}

شایان ذکر است که:

  • کلاس [Global.asax.cs] هنگام راه‌اندازی برنامه ایجاد می‌شود و این نمونه برای تمام درخواست‌های همه کاربران در دسترس است. بنابراین فیلدهای ایستا در خطوط ۱۱ تا ۱۴ بین همه کاربران مشترک هستند.
  • متد [Application_Start] تنها یک بار پس از ایجاد کلاس اجرا می‌شود. این متدی است که در آن برنامه به طور کلی اولیه می‌شود.

داده‌های مشترک بین همه کاربران به شرح زیر است:

  • خط ۱۱: آرایهٔ اشیاء از نوع [Employe] که لیست ساده‌شده (SS, NOM, PRENOM) تمام کارمندان را ذخیره خواهد کرد
  • خط ۱۲: مرجعی به لایه [metier] که در DLL و [pam-metier-dao-nhibernate.dll] نهفته است
  • خط ۱۳: پیامی که نتیجهٔ راه‌اندازی را (موفق یا با خطا) نشان می‌دهد
  • خط ۱۴: یک مقدار بولی که نشان می‌دهد آیا inizialization با خطا پایان یافته است یا خیر.

در [Application_Start]:

  • خط ۲۳: Spring لایه‌های [metier] و [dao] را ایجاد می‌کند و مرجعی به لایه [metier] برمی‌گرداند. این مقدار در فیلد ثابت [PamMetier] در خط ۱۲ ذخیره می‌شود.
  • خط ۲۵: جدول کارمندان از لایه [metier] درخواست می‌شود
  • خط ۲۷: پیام موفقیت
  • خط ۳۲: پیام خطا

7.5.3. فرم [Default.aspx]

این فرم از نسخهٔ ۲ است.

Image


سؤال: با استفاده از کد C# صفحه [Default.aspx.cs] در نسخه ۲ به‌عنوان راهنما، کد [Default.aspx.cs] را برای نسخه ۳ بنویسید. تنها تفاوت در محاسبه حقوق است. در حالی که در نسخهٔ ۲ از متدهای API و ADO.NET برای بازیابی اطلاعات از پایگاه داده استفاده می‌شد، در اینجا از متد GetSalaire در لایهٔ [metier] استفاده خواهیم کرد.


تمرین عملی:


  • انتشار اپلیکیشن وب قبلی روی یک ماشین
  • از فایل‌های پیکربندی مختلف [Web.config] برای استفاده از پایگاه‌داده‌های مختلف SGBD (Firebird، MySQL، Postgres، SQL Server) استفاده کنید