Skip to content

7. حل

7.1. مشروع Visual Studio

تعليقات:

  • تم وضع الملفات اللازمة لـ Spring في المجلد [bin]: Spring.Core.dll، log4net.dll، Spring.Core.xml
  • كما تم وضع ملفات تكوين Spring [spring-config-3tier-*.xml] في المجلد [bin]
  • تحت المجلد الجذري [istia]، توجد الفئات المختلفة للتطبيق

تم تكوين المشروع لإنشاء ملفات DLL و [spring3tier.dll] في المجلد [bin]:

Image

7.2. الحزمة [istia.st.spring3tier.dao]

واجهة IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' القيام بشيء ما في الطبقة [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

نموذج أول للتنفيذ:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl1
        Implements istia.st.spring3tier.dao.IDao

         ' القيام بشيء ما في الطبقة [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a + b
        End Function
    End Class

End Namespace
  • لا تحتوي الفئة على حقل خاص
  • تُرجع الطريقة [doSomethingInDaoLayer] مجموع معلماتها كما هو مطلوب.

فئة تنفيذ ثانية:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl2
        Implements istia.st.spring3tier.dao.IDao

         ' القيام بشيء ما في الطبقة [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a - b
        End Function
    End Class

End Namespace
  • لا تحتوي الفئة على حقل خاص
  • تُرجع الطريقة [doSomethingInDaoLayer] الفرق بين معلماتها كما هو مطلوب.

7.3. الحزمة [istia.st.spring3tier.domain]

الواجهة IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' القيام بشيء ما في الطبقة [domain]
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

فئة أولى من عمليات التنفيذ IDomainImpl1:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl1
        Implements istia.st.spring3tier.domain.IDomain

         ' الحقول الخاصة
        Private _dao As IDao

         ' الخاصية المرتبطة
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' منشئ افتراضي
        Public Sub New()
        End Sub

         ' القيام بشيء ما في الطبقة [domain]
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a += 1
            b += 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • تحتوي الفئة على حقل خاص يمثل مرجعًا إلى عنصر فريد من نوع [IDao] يتيح الوصول إلى الطبقة [Dao]. سيتم تهيئة هذا الحقل بواسطة Spring (حقن التبعية) عند إنشاء الكائن.
  • تقوم الطريقة [doSomethingInDomainLayer] بزيادة قيم معلماتها ثم تمررها إلى الطريقة [doSomethingInDaoLayer] الخاصة بالكائن الفردي [dao]

فئة تنفيذ ثانية IDomainImpl2:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl2
        Implements istia.st.spring3tier.domain.IDomain

         ' الحقول الخاصة
        Private _dao As IDao

         ' خاصية مرتبطة
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' منشئ افتراضي
        Public Sub New()
        End Sub

         ' القيام بشيء ما في الطبقة [domain]
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a -= 1
            b -= 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • تحتوي الفئة على حقل خاص يمثل مرجعًا إلى العنصر الفردي من النوع [IDao] الذي يتيح الوصول إلى الطبقة [Dao]. سيتم تهيئة هذا الحقل بواسطة Spring (حقن التبعية) عند إنشاء الكائن.
  • تقوم الطريقة [doSomethingInDomainLayer] بخصم قيم معلماتها ثم تمريرها إلى الطريقة [doSomethingInDaoLayer] الخاصة بالكائن الفردي [dao]

7.4. الحزمة [istia.st.spring3tier.control]

الواجهة IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' القيام بشيء ما في الطبقة [control]
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

فئة أولى من عمليات التنفيذ ControlImpl1:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl1
        Implements istia.st.spring3tier.control.IControl

         ' الحقول الخاصة
        Private _domain As IDomain

         ' الخاصية المرتبطة
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' المنشئ الافتراضي
        Public Sub New()
        End Sub

         ' القيام بشيء ما في الطبقة [control]
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a += 1
            b += 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • تحتوي الفئة على حقل خاص يمثل مرجعًا إلى العنصر الفردي من النوع [IDomain] الذي يتيح الوصول إلى الطبقة [Domain]. سيتم تهيئة هذا الحقل بواسطة Spring (حقن التبعية) عند إنشاء الكائن.
  • تقوم الطريقة [doSomethingInControlLayer] بزيادة قيم معلماتها ثم تمررها إلى الطريقة [doSomethingInDomainLayer] الخاصة بالكائن الفردي [domain]

فئة تنفيذ ثانية IControlImpl2:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl2
        Implements istia.st.spring3tier.control.IControl

         ' الحقول الخاصة
        Private _domain As IDomain

         ' خاصية مرتبطة
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' منشئ افتراضي
        Public Sub New()
        End Sub

         ' القيام بشيء ما في الطبقة [control]
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a -= 1
            b -= 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • تحتوي الفئة على حقل خاص يمثل مرجعًا إلى العنصر الفردي من النوع [IDomain] الذي يتيح الوصول إلى الطبقة [Domain]. سيتم تهيئة هذا الحقل بواسطة Spring (حقن التبعية) عند إنشاء الكائن.
  • تقوم الطريقة [doSomethingInControlLayer] بخصم قيم معلماتها ثم تمريرها إلى الطريقة [doSomethingInDomainLayer] الخاصة بالكائن الفريد [domain].

7.5. ملفات التكوين [Spring]

يستخدم الملف [spring-config-3tier-1.xml] الإصدارات 1 من عمليات التنفيذ التالية:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- فئة DAO -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- فئة domain -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- فئة التحكم -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

يستخدم الملف [spring-config-3tier-2.xml] الإصدارات 2 من عمليات التنفيذ التالية:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- فئة dao -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- فئة domain -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- فئة التحكم -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. حزمة الاختبارات [istia.st.spring3tier.tests]

اختبار Nunit [NunitTestSpring3tier.vb]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpring3tier
         ' مصانع السينجلتون
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' يتم إنشاء مصانع السينجلتون
            factory1 = New XmlObjectFactory(New FileStream("spring-config-3tier-1.xml", FileMode.Open))
            factory2 = New XmlObjectFactory(New FileStream("spring-config-3tier-2.xml", FileMode.Open))
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' إزالة الكائنات الفردية
            factory1.Dispose()
            factory2.Dispose()
             ' تحرير مصانع الكائنات الفردية
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             'استرداد تنفيذ للواجهة IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' استخدام الفئة
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' نسترد تنفيذًا آخر للواجهة IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' نستخدم الفئة
            Dim a2 As Integer = 10, b2 As Integer = 20
            Dim res2 As Integer = control2.doSomethingInControlLayer(a2, b2)
            Assert.AreEqual(-10, res2)
        End Sub

    End Class
End Namespace

يؤدي تنفيذ هذا الاختبار إلى النتائج التالية:

Image

سيلاحظ القارئ الذي لديه نسخة ملونة من هذا المستند أن النتائج تظهر باللون "الأخضر"، مما يشير إلى نجاح الاختبار.

7.7. نوع آخر من ملفات تكوين Spring

يمكن تكوين تطبيق VB.net باستخدام ملف يُسمى [App.config]. يتم وضع هذا الملف في جذر مشروع Visual Studio:

Image

يمكن لـ Spring الاستفادة من ملف التكوين هذا. لننظر إلى الملف [App.config] التالي، المستوحى من أمثلة موجودة في وثائق [Spring.net]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
             <!-- تكوين أولي -->
             <!-- فئة dao -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- الفئة domain -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- فئة control -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- تكوين ثانٍ -->
             <!-- فئة DAO -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- فئة domain -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- فئة التحكم -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

ملاحظة: المعلومات الواردة أدناه غير مؤكدة. لست متأكدًا من أنني فهمت بشكل صحيح معنى جميع عناصر ملف التكوين أعلاه.


تتطلب صيغة XML لملف [App.config] أن يتوافق مع الصيغة التالية:

<configuration>
....
</configuration>

يمكن تفويض إدارة الأقسام المختلفة لـ [App.config] إلى برامج خارجية. وهذا ما يتم هنا في القسم [ConfigSections]:

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

يشير الرمز أعلاه إلى أن القسم المسمى [spring/context] يجب أن تداره الفئة [Spring.Context.Support.ContextHandler] التيتوجد في المجموعة [Spring.Core.dll]، وأن القسم المسمى [spring/objects] يجب أن تداره الفئة [Spring.Context.Support.DefaultSectionHandler] التي توجد هي الأخرى في المجموعة [Spring.Core.dll].

القسم [spring/context] هو كما يلي:

    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
...
    </spring>

يبدو أن المقصود هنا هو أن القسم [spring/objects] من ملف التكوين يجب أن تديره الفئة [Spring.Context.Support.XmlApplicationHandler] الموجودة في التجميع [Spring.Core.dll]. يجب أن تستخدم هذه الفئة موردًا XML الذي يقع في [ config://spring/objectsc.a.d، القسم [spring/objects] من ملف التكوين الحالي.

نجد في القسم [spring/objects] صيغة Spring التي اعتدنا عليها الآن.

وقد حددنا في القسم <objects>... </objects> من الملف [App.config] تكوينين محتملين لتطبيقنا ثلاثي الطبقات:

  • أحدهما يستخدم الإصدار 1 من تطبيقات الواجهات
  • والآخر يستخدم الإصدار 2 من نفس هذه التنفيذات

الآن، كيف يمكننا استخدام الملف [App.config

يُظهر الكود التالي تطبيقًا يعمل على وحدة التحكم ويستفيد من الملف [App.config] السابق:

Imports System
Imports Spring.Context
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain
Imports System.Configuration

Namespace istia.st.springioc.tests

    Module MainTestSpring3tier

        Public Sub main()
             ' سياق Spring الذي سيسمح لنا باسترداد العناصر الفردية
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' نسترد أول تطبيق للواجهة IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' نستخدم الفئة
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' نسترد تنفيذًا آخر للواجهة IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' يتم استخدام الفئة
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
             ' توقف مؤقت
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

تعليقات:

  • في الأمثلة NUnit التي استخدمناها حتى الآن، كنا نستخدم كائنًا من النوع [XmlObjectFactory] للحصول على العناصر الفردية التي كنا بحاجة إليها. أما هنا، فإن الكائن المستخدم هو من النوع [IApplicationContext]، وهي واجهة من Spring. ويتم الحصول عليه من [App.config] بفضل الفئة [ConfigurationSettings]، وهي فئة تُستخدم عادةً في .NET لاستغلال ملفات التكوين.
  • يتم طلب مدير القسم [spring/context]. وإذا رجعنا إلى الملف [App.config]، نجد أنه من النوع [XmlApplicationContext] وأنه مكلف بإدارة القسم [spring/objects] التابع لـ [App.config].
  • بمجرد استرداد الكائن من النوع [IApplicationContext]، يتم استخدامه مثل الكائن [XmlObjectFactory] الذي كنا نستخدمه حتى الآن.

يُسمى البرنامج السابق [MainTestSpring3tier.vb] ويوجد في الحزمة [tests]:

Image

تم تكوين المشروع [spring3tier] بحيث يقوم [MainTestSpring3tier] بما يلي:

Image

يؤدي تنفيذ المشروع إلى النتائج التالية:

res1(10,20)=34
res2(10,20)=-10
Tapez [entrée] pour continuer...

7.8. Conclusion

يتيح إطار عمل Spring مرونة حقيقية سواء في بنية التطبيقات أو في تكوينها. وقد استخدمنا مفهوم IoC، وهو أحد ركيزتي Spring. أما الركيزة الأخرى فهي AOP (البرمجة الموجهة نحو الجوانب) التي لم نتطرق إليها. وهي تتيح إضافة «سلوك» إلى دالة فئة ما عن طريق التهيئة دون تعديل كودها. وبشكل مبسط، تتيح AOP تصفية استدعاءات دالات معينة:

  • يمكن تنفيذ المرشح قبل أو بعد الطريقة M المستهدفة، أو كليهما.
  • تتجاهل الطريقة M وجود هذه المرشحات. يتم تعريف هذه المرشحات في ملف تكوين Spring.
  • لا يتم تعديل كود الطريقة M. المرشحات عبارة عن فئات Java يجب إنشاؤها. يوفر Spring مرشحات محددة مسبقًا، لا سيما لإدارة معاملات SGBD.
  • تُعد المرشحات «بينات» (beans)، وبصفتها هذه تُعرَّف في ملف تكوين Spring كـ«بينات».

ومن المرشحات الشائعة المرشح المعاملاتي. لنأخذ طريقة M من طبقة الأعمال التي تنفذ عمليتين لا يمكن فصلهما عن بعضهما على البيانات (وحدة العمل). وهي تستدعي طريقتين M1 و M2 من طبقة DAO لتنفيذ هاتين العمليتين.

ونظرًا لوجودها في الطبقة الوظيفية، فإن الطريقة M تتجاهل وسيط هذه البيانات. فهي لا تحتاج، على سبيل المثال، أن تفترض أن البيانات موجودة في SGBD وأنها تحتاج إلى وضع استدعاءي الطريقتين M1 و M2 ضمن معاملة SGBD. ويقع على عاتق الطبقة DAO الاهتمام بهذه التفاصيل. ومن ثم، فإن أحد الحلول للمشكلة السابقة هو إنشاء دالة في الطبقة DAO تقوم بدورها باستدعاء الدالتين M1 و M2، وهي استدعاءات ستضمها في معاملة تابعة لـ SGBD.

يُعد حل التصفية AOP أكثر مرونة. فهو سيسمح بتعريف مرشح يبدأ معاملة قبل استدعاء M، ويقوم بعد الاستدعاء بإجراء «التثبيت» (commit) أو «التراجع» (rollback) حسب الحالة.

هناك العديد من المزايا لهذا النهج:

  • بمجرد تعريف المرشح، يمكن تطبيقه على عدة طرق، على سبيل المثال جميع الطرق التي تحتاج إلى معاملة
  • لا يلزم إعادة كتابة الطرق التي تمت تصفيتها بهذه الطريقة
  • نظرًا لأن المرشحات المستخدمة يتم تعريفها عبر التكوين، فيمكن تغييرها

لمزيد من المعلومات: http://www.springframework.net.