الكتابة: جعل الدول غير صالحة للتعبير عنها

أقدم لكم ترجمة مقالة سكوت فلاشين "التصميم بأنواع: جعل الدول غير القانونية غير قابلة للتمثيل " .


في هذه المقالة ، سننظر في الميزة الرئيسية لـ F # - القدرة على "جعل الحالات غير الصحيحة غير قابلة للتعبير" باستخدام نظام النوع (تم استعارة العبارة من Yaron Minsky ).


ضع في اعتبارك نوع Contact . نتيجة لإعادة الهيكلة ، قام بتبسيط كبير:


 type Contact = { Name: Name; EmailContactInfo: EmailContactInfo; PostalContactInfo: PostalContactInfo; } 

افترض الآن أن هناك قاعدة عمل بسيطة: "يجب أن تحتوي جهة الاتصال على عنوان بريد إلكتروني أو عنوان بريدي." هل يتوافق نوعنا مع هذه القاعدة؟


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


يبدو أن الإجابة واضحة - اجعل العناوين اختيارية ، على سبيل المثال ، مثل:


 type Contact = { Name: PersonalName; EmailContactInfo: EmailContactInfo option; PostalContactInfo: PostalContactInfo option; } 

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


كيف تحل هذه المشكلة؟


كيفية جعل حالات غير صحيحة لا يمكن التعبير عنه


بعد النظر في قاعدة منطق العمل ، يمكننا أن نستنتج أن ثلاث حالات ممكنة:


  • يتم توفير عنوان البريد الإلكتروني فقط ؛
  • يشار فقط إلى العنوان البريدي ؛
  • يتم توفير كل من البريد الإلكتروني والعناوين البريدية.

في مثل هذه الصيغة ، يصبح الحل واضحًا - لعمل مجموع نوع مع منشئ لكل حالة ممكنة.


 type ContactInfo = | EmailOnly of EmailContactInfo | PostOnly of PostalContactInfo | EmailAndPost of EmailContactInfo * PostalContactInfo type Contact = { Name: Name; ContactInfo: ContactInfo; } 

هذا التطبيق متوافق تمامًا. يتم التعبير عن الحالات الثلاث صراحة ، في حين أن الحالة الرابعة (بدون أي عنوان) غير مسموح بها.


انتبه إلى حالة "عنوان البريد الإلكتروني والعنوان البريدي". حتى الآن استخدمت للتو tuple. في هذه الحالة ، هذا يكفي.


إنشاء ContactInfo


الآن دعنا نرى كيفية استخدام هذا التطبيق كمثال. أولاً ، أنشئ جهة اتصال جديدة:


 let contactFromEmail name emailStr = let emailOpt = EmailAddress.create emailStr //          match emailOpt with | Some email -> let emailContactInfo = {EmailAddress=email; IsEmailVerified=false} let contactInfo = EmailOnly emailContactInfo Some {Name=name; ContactInfo=contactInfo} | None -> None let name = {FirstName = "A"; MiddleInitial=None; LastName="Smith"} let contactOpt = contactFromEmail name "abc@example.com" 

في هذا المثال ، نقوم بإنشاء جهة اتصال وظيفة مساعدة بسيطة من contactFromEmail لإنشاء جهة اتصال جديدة عن طريق تمرير الاسم وعنوان البريد الإلكتروني. ومع ذلك ، قد يكون العنوان غير صحيح ، ويجب أن تعالج الوظيفة كلتا الحالتين. لا يمكن للوظيفة إنشاء جهة اتصال بعنوان غير صالح ، لذا فإنها تُرجع قيمة من نوع Contact option ، وليس جهة اتصال.


تغيير ContactInfo


إذا كنت بحاجة إلى إضافة عنوان بريدي إلى ContactInfo موجودة ، فعليك معالجة ثلاث حالات محتملة:


  • إذا كان لجهة الاتصال عنوان بريد إلكتروني فقط ، فعندئذٍ يحتوي الآن على كلا العنوانين ، لذلك تحتاج إلى إعادة جهة الاتصال مع المنشئ EmailAndPost ؛
  • إذا كان لجهة الاتصال عنوان بريدي فقط ، فيجب إعادة جهة الاتصال مع مُنشئ PostOnly ، واستبدال العنوان البريدي بعنوان جديد ؛
  • إذا كان لجهة الاتصال كلا العنوانين ، فأنت بحاجة إلى إعادة جهة الاتصال مع منشئ البريد الإلكتروني EmailAndPost ، واستبدال العنوان البريدي بعنوان جديد.

الوظيفة المساعدة لتحديث العنوان البريدي هي كما يلي. لاحظ المعالجة الصريحة لكل حالة.


 let updatePostalAddress contact newPostalAddress = let {Name=name; ContactInfo=contactInfo} = contact let newContactInfo = match contactInfo with | EmailOnly email -> EmailAndPost (email,newPostalAddress) | PostOnly _ -> //     PostOnly newPostalAddress | EmailAndPost (email,_) -> //     EmailAndPost (email,newPostalAddress) //    {Name=name; ContactInfo=newContactInfo} 

وهنا استخدام هذا الرمز:


 let contact = contactOpt.Value //      option.Value  let newPostalAddress = let state = StateCode.create "CA" let zip = ZipCode.create "97210" { Address = { Address1= "123 Main"; Address2=""; City="Beverly Hills"; State=state.Value; //      option.Value  Zip=zip.Value; //      option.Value  }; IsAddressValid=false } let newContact = updatePostalAddress contact newPostalAddress 

تحذير: في هذا المثال ، استخدمت الخيار. القيمة للحصول على محتويات الخيار. هذا مقبول عندما تقوم بالتجربة في وحدة تحكم تفاعلية ، ولكنه حل رهيب لرمز العمل! يجب عليك دائمًا استخدام مطابقة الأنماط والتعامل مع كل من مُنشئ option .


لماذا تهتم بهذه الأنواع المعقدة؟


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


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


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


 type ContactInfo = | EmailOnly of EmailContactInfo | PostOnly of PostalContactInfo | EmailAndPost of EmailContactInfo * PostalContactInfo 

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


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

Source: https://habr.com/ru/post/ar424895/


All Articles