سؤال

لنفترض أن لديك تطبيقًا مقسمًا إلى 3 مستويات:واجهة المستخدم الرسومية ومنطق الأعمال والوصول إلى البيانات.في طبقة منطق عملك، قمت بوصف كائنات عملك:getters، setters، accessors، وما إلى ذلك ...انت وجدت الفكرة.تضمن واجهة طبقة منطق الأعمال الاستخدام الآمن لمنطق الأعمال، وبالتالي فإن جميع الأساليب والملحقات التي تستدعيها ستتحقق من صحة الإدخال.

يعد هذا أمرًا رائعًا عند كتابة رمز واجهة المستخدم لأول مرة، لأن لديك واجهة محددة بدقة يمكنك الوثوق بها.

ولكن هنا يأتي الجزء الصعب، عندما تبدأ في كتابة طبقة الوصول إلى البيانات، فإن واجهة منطق الأعمال لا تلبي احتياجاتك.يجب أن يكون لديك المزيد من أدوات الوصول والحروف لتعيين الحقول التي/كانت مخفية.أنت الآن مجبر على تآكل واجهة منطق عملك؛أصبح من الممكن الآن تعيين الحقول من طبقة واجهة المستخدم، والتي لا تحتوي طبقة واجهة المستخدم على إعداد عمل.

بسبب التغييرات المطلوبة لطبقة الوصول إلى البيانات، تآكلت واجهة منطق الأعمال إلى درجة أنه من الممكن حتى تعيين منطق الأعمال باستخدام بيانات غير صالحة.وبالتالي، فإن الواجهة لم تعد تضمن الاستخدام الآمن بعد الآن.

آمل أن أكون قد شرحت المشكلة بشكل واضح بما فيه الكفاية.كيف يمكنك منع تآكل الواجهة، والحفاظ على إخفاء المعلومات وتغليفها، مع الاستمرار في تلبية احتياجات الواجهة المختلفة بين الطبقات المختلفة؟

هل كانت مفيدة؟

المحلول

إذا فهمت السؤال بشكل صحيح، فقد قمت بإنشاء نموذج مجال وترغب في كتابة مخطط ارتباطي للكائنات للتخطيط بين السجلات في قاعدة البيانات الخاصة بك وكائنات المجال الخاصة بك.ومع ذلك، فأنت قلق بشأن تلويث نموذج المجال الخاص بك بكود "السباكة" الذي قد يكون ضروريًا للقراءة والكتابة في حقول الكائن الخاص بك.

بالعودة خطوة إلى الوراء، لديك خياران أساسيان لمكان وضع رمز تعيين البيانات الخاص بك - داخل فئة المجال نفسها أو في فئة تعيين خارجية.غالبًا ما يُطلق على الخيار الأول اسم نمط السجل النشط ويتمتع بميزة أن كل كائن يعرف كيفية الاستمرار في نفسه ولديه وصول كافٍ إلى بنيته الداخلية للسماح له بإجراء التعيين دون الحاجة إلى الكشف عن الحقول غير المتعلقة بالعمل.

على سبيل المثال

public class User
{
    private string name;
    private AccountStatus status;

    private User()
    {
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public AccountStatus Status
    {
        get { return status; }
    }

    public void Activate()
    {
        status = AccountStatus.Active;
    }

    public void Suspend()
    {
        status = AccountStatus.Suspended;
    }

    public static User GetById(int id)
    {
        User fetchedUser = new User();

        // Lots of database and error-checking code
        // omitted for clarity
        // ...

        fetchedUser.name = (string) reader["Name"];
        fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;

        return fetchedUser;
    }

    public static void Save(User user)
    {
        // Code to save User's internal structure to database
        // ...
    }
}

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

الخيار الثاني هو أن يكون لديك فئة ثانية مسؤولة عن التعيين.يتميز هذا بفصل الاهتمامات المختلفة لمنطق العمل والمثابرة مما قد يسمح لتصميمك بأن يكون أكثر قابلية للاختبار ومرونة.يتمثل التحدي في هذه الطريقة في كيفية كشف حقول الاسم والحالة للفئة الخارجية.بعض الخيارات هي:1.استخدم الانعكاس (الذي ليس له أي مخاوف حول الحفر بعمق في الأجزاء الخاصة لكائنك) 2.قم بتوفير محددات عامة ذات أسماء خاصة (على سبيل المثال.بادئةهم بكلمة "خاصة") وآمل ألا يستخدمها أحد عن طريق الخطأ 3.إذا كانت لغتك تدعمها، فاجعل أدوات الضبط داخلية ولكن امنح حق الوصول إلى وحدة مخطط البيانات الخاصة بك.على سبيل المثالاستخدم InternalsVisibleToAttribute في .NET 2.0 وما بعده أو وظائف الأصدقاء في C++

لمزيد من المعلومات، أنصح بقراءة كتاب مارتن فاولر الكلاسيكي "أنماط هندسة المؤسسات"

ومع ذلك، ككلمة تحذير، قبل المضي في مسار كتابة مصممي الخرائط الخاصين بك، أوصي بشدة بالنظر في استخدام أداة رسم الخرائط العلائقية (ORM) التابعة لجهة خارجية مثل nHibernate أو Microsoft's Entity Framework.لقد عملت في أربعة مشاريع مختلفة حيث، لأسباب مختلفة، قمنا بكتابة مصمم الخرائط الخاص بنا ومن السهل جدًا إضاعة الكثير من الوقت في صيانة مصمم الخرائط وتوسيعه بدلاً من كتابة التعليمات البرمجية التي توفر قيمة للمستخدم النهائي.لقد استخدمت nHibernate في مشروع واحد حتى الآن، وعلى الرغم من أنه يحتوي على منحنى تعليمي حاد في البداية، إلا أن الاستثمار الذي قمت به في وقت مبكر يؤتي ثماره بشكل كبير.

نصائح أخرى

هذه مشكلة كلاسيكية - فصل نموذج المجال الخاص بك عن نموذج قاعدة البيانات الخاصة بك.هناك عدة طرق لمهاجمته، ويعتمد الأمر حقًا على حجم مشروعك في رأيي.يمكنك استخدام نمط المستودع كما قال الآخرون.إذا كنت تستخدم .net أو Java فيمكنك استخدامه السبات أو بيات شتوى.

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

ملاحظة: عنوان السؤال مضلل بعض الشيء

@الثلج^^الحرارة:

ماذا تقصد بأن طبقة البيانات لا ينبغي أن تكون على علم بطبقة منطق الأعمال؟كيف يمكنك ملء كائن الأعمال بالبيانات؟

تطلب واجهة المستخدم من ServiceClass في طبقة الأعمال الحصول على خدمة، أي الحصول على قائمة بالكائنات التي تمت تصفيتها بواسطة كائن مع بيانات المعلمة المطلوبة.
ثم يقوم ServiceClass بإنشاء مثيل لإحدى فئات المستودع في طبقة البيانات، ويستدعي GetList (مرشحات ParameterType).
ثم تصل طبقة البيانات إلى قاعدة البيانات، وتسحب البيانات، وتعيينها إلى التنسيق الشائع المحدد في تجميع "المجال".
لم يعد لدى BL أي عمل للقيام به مع هذه البيانات، لذلك يقوم بإخراجها إلى واجهة المستخدم.

ثم تريد واجهة المستخدم تحرير العنصر X.يقوم بإرسال العنصر (أو كائن الأعمال) إلى الخدمة في طبقة الأعمال.تقوم طبقة الأعمال بالتحقق من صحة الكائن، وإذا كان على ما يرام، فإنه يرسله إلى طبقة البيانات للتخزين.

تعرف واجهة المستخدم الخدمة الموجودة في طبقة الأعمال والتي تعرف مرة أخرى عن طبقة البيانات.

تكون واجهة المستخدم مسؤولة عن تعيين إدخال بيانات المستخدمين من وإلى الكائنات، وتكون طبقة البيانات مسؤولة عن تعيين البيانات في قاعدة البيانات من وإلى الكائنات.تظل فئة الأعمال تجارية بحتة.:)

يمكن أن يكون حلاً، لأنه لن يؤدي إلى تآكل الواجهة.أعتقد أنه يمكن أن يكون لديك فصل مثل هذا:

public class BusinessObjectRecord : BusinessObject
{
}

أقوم دائمًا بإنشاء تجميع منفصل يحتوي على:

  • الكثير من الواجهات الصغيرة (فكر في ICreateRepository وIReadRepository وIReadListRepsitory..والقائمة تطول ويعتمد معظمها بشكل كبير على الأدوية العامة)
  • هناك الكثير من الواجهات الملموسة، مثل IPersonRepository، التي ترث من IReadRepository، لقد فهمت النقطة..
    أي شيء لا يمكنك وصفه باستخدام الواجهات الأصغر حجمًا فقط، يمكنك وضعه في الواجهة الملموسة.
    طالما أنك تستخدم IPersonRepository للإعلان عن الكائن الخاص بك، فستحصل على واجهة نظيفة ومتسقة للعمل معها.لكن الأمر المهم هو أنه يمكنك أيضًا إنشاء فصل دراسي يأخذ f.x.ICreateRepository في المُنشئ الخاص به، لذلك سيكون من السهل جدًا تنفيذ بعض الأشياء غير التقليدية باستخدام الكود.توجد أيضًا واجهات للخدمات في مستوى الأعمال هنا.
  • أخيرًا، قمت بلصق جميع كائنات المجال في التجميع الإضافي، فقط لجعل قاعدة التعليمات البرمجية نفسها أكثر نظافة قليلاً وأكثر ارتباطًا بشكل فضفاض.لا تحتوي هذه الكائنات على أي منطق، فهي مجرد طريقة شائعة لوصف البيانات لجميع الطبقات الثلاث فما فوق.

بالمناسبة.لماذا تحدد الأساليب في طبقة منطق الأعمال لاستيعاب طبقة البيانات؟
يجب ألا يكون لطبقة البيانات أي سبب لمعرفة وجود طبقة أعمال.

ماذا تقصد بأن طبقة البيانات لا ينبغي أن تكون على علم بطبقة منطق الأعمال؟كيف يمكنك ملء كائن الأعمال بالبيانات؟

كثيرا ما أفعل هذا:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}

إذن المشكلة هي أن طبقة الأعمال تحتاج إلى كشف المزيد من الوظائف لطبقة البيانات، وإضافة هذه الوظيفة يعني تعريض الكثير لطبقة واجهة المستخدم؟إذا كنت أفهم مشكلتك بشكل صحيح، فيبدو أنك تحاول إرضاء الكثير من خلال واجهة واحدة، وهذا يؤدي فقط إلى ازدحامها.لماذا لا يكون لديك واجهتين في طبقة الأعمال؟سيكون أحدهما واجهة بسيطة وآمنة لطبقة واجهة المستخدم.والآخر سيكون واجهة ذات مستوى أدنى لطبقة البيانات.

يمكنك تطبيق نهج الواجهتين هذا على أي كائنات تحتاج إلى تمريرها إلى كل من واجهة المستخدم وطبقات البيانات أيضًا.

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

قد ترغب في تقسيم واجهاتك إلى نوعين، وهما:

  • واجهات العرض - وهي واجهات تحدد تفاعلاتك مع واجهة المستخدم الخاصة بك، و
  • واجهات البيانات - وهي واجهات تسمح لك بتحديد التفاعلات مع بياناتك

من الممكن وراثة وتنفيذ مجموعتي الواجهات مثل:

public class BusinessObject : IView, IData

بهذه الطريقة، في طبقة البيانات الخاصة بك، تحتاج فقط إلى رؤية تنفيذ واجهة IData، بينما في واجهة المستخدم الخاصة بك، تحتاج فقط إلى رؤية تنفيذ واجهة IView.

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

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

وهذا بدوره يسمح لكائن عملك بالبقاء جاهلاً بكل من طبقة واجهة المستخدم/العرض وطبقة البيانات.

سأستمر في عادتي المتمثلة في معارضة التيار وأقول إنه يجب عليك أن تتساءل عن سبب قيامك ببناء كل هذه الطبقات المعقدة للغاية من الكائنات.

أعتقد أن العديد من المطورين يعتبرون قاعدة البيانات بمثابة طبقة ثبات بسيطة لكائناتهم، ولا يهتمون إلا بعمليات CRUD التي تحتاجها تلك الكائنات.يتم بذل الكثير من الجهد في "عدم تطابق المعاوقة" بين النماذج الكائنية والنماذج العلائقية.وهنا فكرة:توقف عن المحاولة.

اكتب الإجراءات المخزنة لتغليف بياناتك.استخدم مجموعات النتائج، DataSet، DataTable، SqlCommand (أو Java/php/أي شيء يعادله) حسب الحاجة من التعليمات البرمجية للتفاعل مع قاعدة البيانات.لا تحتاج إلى تلك الأشياء.مثال ممتاز هو تضمين SqlDataSource في صفحة .ASPX.

لا يجب أن تحاول إخفاء بياناتك عن أي شخص.يحتاج المطورون إلى فهم كيف ومتى يتفاعلون مع مخزن البيانات الفعلي بالضبط.

مصممو الخرائط ذات العلاقة بالكائنات هم الشيطان.التوقف عن استخدامها.

غالبًا ما يكون بناء تطبيقات المؤسسات تمرينًا في إدارة التعقيد.عليك أن تبقي الأمور بسيطة قدر الإمكان، وإلا سيكون لديك نظام غير قابل للصيانة على الإطلاق.إذا كنت على استعداد للسماح ببعض الاقتران (وهو أمر متأصل في أي تطبيق على أي حال)، فيمكنك التخلص من كل من طبقة منطق عملك وطبقة الوصول إلى البيانات (استبدالهما بالإجراءات المخزنة)، ولن تحتاج إلى أي من هذه واجهات.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top