كتاب الطبخ للمطورين: وصفات DDD (الجزء 4 ، الهياكل)

مقدمة


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


Blacjack & المتجولون


وقد اخترنا بالفعل اسمًا مناسبًا - LunaPark .
التطورات الحالية نشرت على جيثب .
بعد فحص جميع القوالب ، سنقوم بتجميع خدمة مايكروسيف كاملة واحدة.


تاريخيا


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


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

اريك ايفانز 2006

لسوء الحظ ، لم يتغير شيء كثيرًا على مدار السنوات الـ 13 الماضية. على الإنترنت ، يمكنك العثور على محاولات لتكييف Rails لهذا ، لكنها تبدو كلها فظيعة. إطار Rails ثقيل وبطيء وليس صلبًا. من الصعب للغاية مراقبة دون أي دموع كيف يحاول شخص ما أن يصور تطبيق نمط السجل على أساس ActiveRecord . قررنا اعتماد إطار عمل مصغر وتعديله حسب احتياجاتنا. لقد جربنا Grape ، بدا أن فكرة التوثيق التلقائي كانت ناجحة ، لكن بخلاف ذلك تم التخلي عنها وسرعان ما تخلينا عن فكرة استخدامها. وعلى الفور تقريبا بدأوا في استخدام حل آخر - سيناترا . ما زلنا نواصل استخدامه في REST Controllers و Endpoints .


الراحة؟

إذا قمت بتطوير تطبيقات الويب ، فأنت لديك بالفعل فكرة عن التكنولوجيا. لها إيجابيات وسلبيات ، قائمة كاملة منها خارج نطاق هذه المقالة. ولكن بالنسبة لنا ، كمطورين لتطبيقات المؤسسات ، فإن العيب الأكثر أهمية هو أن REST (هذا واضح حتى من الاسم) لا يعكس العملية ، ولكن حالتها. والميزة هي قابليتها للفهم - التكنولوجيا واضحة لكل من المطورين الخلفيين ومطوري الواجهة الأمامية.
ولكن بعد ذلك ربما لا تركز على REST ، ولكن قم بتنفيذ حل http + json؟ حتى إذا تمكنت من تطوير API لخدمتك ، فحينئذٍ تقدم وصفها للأطراف الثالثة ، ستتلقى العديد من الأسئلة. أكثر بكثير مما لو كنت توفير REST مألوفة.
سوف ننظر في استخدام REST حلا وسطا. نحن نستخدم JSON للإيجاز ومعيار jsonapi حتى لا نضيع وقت المطورين في الحروب المقدسة فيما يتعلق بتنسيق الطلب.
في المستقبل ، عندما نحلل Endpoint ، سنرى أنه من أجل التخلص من الراحة ، يكفي إعادة كتابة فصل واحد فقط. لذلك لا ينبغي أن تهتم REST على الإطلاق إذا كانت هناك شكوك حول هذا الموضوع.


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


هنا نشأت الصعوبات الرئيسية. الموظفون الجدد الذين لم يتعاملوا مع ممارسات DDD والبنية النظيفة لم يتمكنوا من فهم الكود والغرض منه. إذا رأيت بنفسي هذه الشفرة لأول مرة قبل قراءة إيفانز ، فسأعتبرها مثل الهندسة القديمة.


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


فلسفة


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


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

يمكن تتبع فلسفة مماثلة ، على سبيل المثال ، في ArchLinux OS - The Arch Way . على جهاز الكمبيوتر المحمول الخاص بي ، لم يعمل Linux على الوصول إلى الجذر لفترة طويلة ، عاجلاً أو آجلاً ، لقد تعطلت إعادة تثبيته. تسبب هذا في عدد من المشاكل ، أحيانًا مشاكل خطيرة مثل تعطيل الموعد النهائي للعمل. لكن بعد قضاء 2-3 أيام بمجرد تثبيت Arch ، اكتشفت كيف يعمل نظام التشغيل الخاص بي. بعد ذلك ، بدأت العمل أكثر استقرارًا ، دون إخفاقات. ساعدتني ملاحظاتي في تثبيته على أجهزة كمبيوتر جديدة في بضع ساعات. لقد ساعدتني الوثائق الوفيرة في حل المشكلات الجديدة.


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


الإطار


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


module LunaPark #  module Forms #  class Single # / end end end 

إذا كنت ترغب في تطبيق نموذج ينشئ عنصرًا واحدًا ، فإنك ترث من هذه الفئة:


 module Forms class Create < LunaPark::Forms::Single 

إذا كان هناك العديد من العناصر ، فسنستخدم تطبيقًا آخر.


 module Forms class Create < LunaPark::Forms::Multiple 

في الوقت الحالي ، لم يتم وضع جميع التطورات في وضع مثالي والجوهرة في حالة ألفا. سوف نستشهد به على مراحل ، وفقًا لنشر المقالات. أي إذا رأيت مقالًا حول ValueObject و Entity ، فسيتم بالفعل تنفيذ هذين ValueObject . بحلول نهاية الدورة ، ستكون جميعها مناسبة للاستخدام في المشروع. نظرًا لأن إطار العمل نفسه ليس له فائدة تذكر دون ارتباط بـ sinatra \ roda ، فسيتم إنشاء مستودع منفصل يوضح كيفية "ربط كل شيء" لبدء مشروعك بسرعة.


الإطار هو في المقام الأول تطبيق على الوثائق. لا تنظر إلى هذه المقالات كوثائق لإطار العمل.


لذلك ، دعونا ننكب على العمل.


كائن القيمة


- كم يبلغ طول صديقتك؟
- 151
- هل بدأت لقاء مع تمثال الحرية؟

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


  • المال ليس مجرد رقم ، بل هو رقم (مبلغ) + عملة.
  • يتكون التاريخ من يوم وشهر وسنة.
  • لقياس الوزن ، رقم واحد لا يكفي بالنسبة لنا ، بل يتطلب أيضًا وحدة قياس.
  • يتكون رقم جواز السفر من سلسلة ، وفي الواقع ، من الرقم.

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


ربما ديكور؟

استنادا إلى العقيدة ، لا جدال فيه - نعم. إذا اقتربنا من هذه المعضلة من جانب الفطرة السليمة ، فعندما نقرر الاتصال بهذا الرقم ، سننقل الكائن نفسه إلى الهاتف:


 phone.call Values::PhoneNumber.new(79001231212) 

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


 Values::PhoneNumber.new(79001231212).to_s 

تخيل أننا نقوم بإنشاء موقع الكازينو Three Axes على الإنترنت وبيع ألعاب الورق. سنحتاج إلى فئة لعب الورق.


 module Values class PlayingCard < Lunapark::Values::Compound attr_reader :suit, :rank end end 

لذلك ، يتميز صفنا بسمتين للقراءة فقط:


  • بدلة - بدلة بطاقة
  • رتبة بطاقة الكرامة

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


 module Values RSpec.describe PlayingCard do let(:card) { described_class.new suit: :clubs, rank: 10 } let(:other) { described_class.new suit: :clubs, rank: 10 } it 'should be eql' do expect(card).to eq other end end end 

سيفشل هذا الاختبار ، حيث سيتم مقارنته في العنوان. لكي ينجح الاختبار ، يجب أن نقارن قيمة Ob Obces بالقيمة ، لذلك سنضيف طريقة مقارنة:


 def ==(other) suit == other.suit && rank == other.rank end 

الآن سوف يمر اختبارنا. يمكننا أيضًا إضافة طرق مسؤولة عن المقارنة ، ولكن كيف يمكننا مقارنة 10 و K؟ كما يحتمل أنك خمنت بالفعل ، سنقدمها أيضًا في شكل كائنات قيمة . حسنًا ، لذا سيتعين علينا الآن بدء أفضل عشرة أندية مثل هذا:


 ten = Values::Rank.new('10') clubs = Values::Suits.new(:clubs) ten_clubs = Values::PlayingCards.new(rank: ten, clubs: clubs) 

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


 class PlayingCard < Lunapark::Values::Compound def self.wrap(obj) case obj.is_a? self.class #      PlayingCard obj #      case obj.is_a? Hash #    ,      new(obj) #    case obj.is_a String #    ,     new rank: obj[0..-2], suit:[-1] # ,  -  . else #       raise ArgumentError #  . end end def initialize(suit:, rank:) #     @suit = Suit.wrap(suit) #      @rank = Rank.wrap(rank) end end 

هذا النهج يعطي ميزة كبيرة:


 ten = Values::Rank.new('10') clubs = Values::Suits.new(:clubs) from_values = Values::PlayingCard.wrap rank: ten, suit: clubs from_hash = Values::PlayingCard.wrap rank: '10', suit: :clubs from_obj = Values::PlayingCard.wrap from_values from_str = Values::PlayingCard.wrap '10C' #        utf ,  ,  . 

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


توصيات


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

فريدريك تايلور 1914

يجب أن ترجع العمليات الحسابية كائنًا جديدًا

 # GOOD class Money < LunaPark::Values::Compound def +(other) other = self.class.wrap(other) raise ArgumentError unless same_currency? other self.class.new( amount: amount + other.amount, currency: currency ) end end 

يمكن أن تكون سمات كائن القيمة بدائية أو كائنات قيمة أخرى

 # GOOD class Weight < LunaPark::Values::Compound def intialize(value:, unit:) @value = value @unit = Unit.wrap(unit) end end # BAD class PlaingCard < LunaPark::Value def initialize(rank:, suit:, deck:) ... @deck = Entity::Deck.wrap(deck) #    end end 

الحفاظ على عمليات بسيطة داخل أساليب الطبقة

 # GOOD class Weight < LunaPark::Values::Compound def >(other) value > other.convert_to(unit).value end end 

إذا كانت عملية "التحويل" كبيرة ، فربما يكون من المنطقي نقلها إلى فصل منفصل

 # UGLY class Weight < LunaPark::Values::Compound def convert_to(unit) unit = Unit.wrap(unit) case { self.unit.to_sym => unit.to_sym } when { :kg => :ft } Weight.new(value: 2.2046 * value, unit.to_sym) when # ... end end end # GOOD #./lib/values/weight/converter.rb class Weight class Converter < LunaPark::Services::Simple def initialize(weight, to:) ... end end end #./lib/values/weight.rb class Weight < LunaPark::Values::Compound def convert_to(unit) Converter.call! self, to: unit end end 

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


لا تعرف قيمة الكائن أي شيء عن منطق المجال

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


 # DEADLY BAD class Rate < LunaPark::Values::Single def top?(10) Repository::Rates.top(first: 10).include? self end end 

الكيان


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


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

من وجهة نظر مارتن ، الكيان ليس كائنًا ، لكنه طبقة. ستجمع هذه الطبقة بين كل من الكائن ومنطق العمل لتغييره.


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

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

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

الفئة العامة MyEntity {private MyDataStructure data؛}

او

الطبقة العامة MyEntity تمتد MyDataStructure {...}

وتذكر أننا جميعا قراصنة بطبيعتهم ؛ والقواعد التي أتحدث عنها هنا تشبه الإرشادات ...

بواسطة Essence ، فإننا نعني الهيكل الوحيد. في أبسط أشكاله ، ستبدو فئة الكيان كما يلي:


 module Entities class MeatBag < LunaPark::Entities::Simple attr_accessor :id, :name, :hegiht, :weight, :birthday end end 

يمكن أن يحتوي الكائن القابل للتغيير الذي يصف بنية نموذج الأعمال على أنواع وقيم بدائية.
LunaPark::Entites::Simple للغاية ، يمكنك رؤية الكود الخاص بها ، إنها تعطينا شيئًا واحدًا فقط - التهيئة السهلة.


LunaPark :: Entites :: بسيط
 module LunaPark module Entities class Simple def initialize(params) set_attributes params end private def set_attributes(hash) hash.each { |k, v| send(:"#{k}=", v) } end end end end 

يمكنك الكتابة:


 john_doe = Entity::MeatBag.new( id: 42, name: 'John Doe', height: '180cm', weight: '80kg', birthday: '01-01-1970' ) 

كما قد تكون خمنت بالفعل ، فنحن نريد أن نلف الوزن والطول وتاريخ الميلاد في كائنات القيمة .


 module Entities class MeatBag < LunaPark::Entites::Simple attr_accessor :id, :name attr_reader :heiht, :wight, :birthday def height=(height) @height = Values::Height.wrap(height) end def weight=(height) @height = Values::Weight.wrap(weight) end def birthday=(day) @birthday = Date.parse(day) end end end 

حتى لا نضيع الوقت في هذه المنشآت ، قمنا بإعداد تطبيق أكثر تعقيدًا لـ LunaPark::Entites::Nested :


 module Entities class MeatBag < LunaPark::Entities::Nested attr :id attr :name attr :heiht, Values::Height, :wrap attr :weight, Values::Weight, :wrap attr :birthday, Values::Date, :parse end end 

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


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


ريفريجاتور


 class Refregerator < LunaPark::Entites::Nested attr :id, attr :brand attr :title namespace :fridge do namespace :door do attr :upper, Shelf, :wrap attr :lower, Shelf, :wrap end attr :upper, Shelf, :wrap attr :lower, Shelf, :wrap end namespace :main do namespace :door do attr :first, Shelf, :wrap attr :second, Shelf, :wrap attr :third, Shelf, :wrap end namespace :boxes do attr :left, Box, :wrap attr :right, Box, :wrap end attr :first, Shelf, :wrap attr :second, Shelf, :wrap attr :third, Shelf, :wrap attr :fourth, Shelf, :wrap end attr :last_open_at, comparable: false end 

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


يحتوي LunaPark::Entites::Nested class على خصائص أكثر أهمية:


المقارنة:


 module Entites class User < LunaPark::Entites::Nested attr :email attr :registred_at end end u1 = Entites::User.new(email: 'john.doe@mail.com', registred_at: Time.now) u2 = Entites::User.new(email: 'john.doe@mail.com', registred_at: Time.now) u1 == u2 # => false 

المستخدمان المحددان غير متكافئين ، لأن تم إنشاؤها في أوقات مختلفة ، وبالتالي ستكون قيمة السمة registred_at مختلفة. ولكن إذا شطبنا السمة من قائمة المقارنات:


 module Entites class User < LunaPark::Entites::Nested attr :email attr :registred_at, comparable: false end end 

ثم نحصل على كائنين مماثلين.


يحتوي هذا التطبيق أيضًا على خاصية الدوران - يمكننا استخدام طريقة الفصل


 Entites::User.wrap(email: 'john.doe@mail.com', registred_at: Time.now) 

يمكنك استخدام Hash أو OpenStruct أو أي جوهرة تريدها ككيان ، والتي سوف تساعدك على إدراك هيكل كيانك.


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


تغييرات الكيان


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

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


All Articles