أحد أهم مبادئ البرمجة الشيئية هو فصل الواجهة الداخلية عن الواجهة الخارجية.
هذه ممارسة "ضرورية" في تطوير أي شيء أكثر تعقيدًا من تطبيق "hello World".
ولكي نفهم هذا، دعونا نبتعد عن التنمية ونوجه أعيننا إلى العالم الحقيقي.
عادةً ما تكون الأجهزة التي نستخدمها معقدة للغاية. لكن فصل الواجهة الداخلية عن الواجهة الخارجية يسمح باستخدامها دون مشاكل.
على سبيل المثال، آلة القهوة. بسيطة من الخارج: زر، وشاشة عرض، وبضعة ثقوب... وبالتأكيد، النتيجة - قهوة رائعة! :)
لكن بالداخل... (صورة من دليل الإصلاح)
الكثير من التفاصيل. ولكن يمكننا استخدامه دون معرفة أي شيء.
آلات القهوة موثوقة تماما، أليس كذلك؟ يمكننا استخدام واحدة لسنوات، وفقط إذا حدث خطأ ما - أحضرها للإصلاحات.
سر الموثوقية والبساطة في ماكينة القهوة - جميع التفاصيل مضبوطة جيدًا ومخفية بالداخل.
إذا قمنا بإزالة الغطاء الواقي من ماكينة القهوة، فسيكون استخدامه أكثر تعقيدًا (أين نضغط؟) وخطيرًا (يمكن أن يسبب صدمة كهربائية).
كما سنرى، في برمجة الأشياء تشبه آلات القهوة.
ولكن من أجل إخفاء التفاصيل الداخلية، لن نستخدم غطاءًا واقيًا، بل تركيبًا خاصًا للغة والاصطلاحات.
في البرمجة الشيئية، يتم تقسيم الخصائص والأساليب إلى مجموعتين:
الواجهة الداخلية – الأساليب والخصائص التي يمكن الوصول إليها من خلال أساليب أخرى للفئة، ولكن ليس من الخارج.
الواجهة الخارجية – الأساليب والخصائص، التي يمكن الوصول إليها أيضًا من خارج الفصل.
إذا واصلنا القياس على ماكينة القهوة - ما هو مخفي بالداخل: أنبوب غلاية، وعنصر تسخين، وما إلى ذلك - فهو واجهتها الداخلية.
يتم استخدام واجهة داخلية لكي يعمل الكائن، وتستخدم تفاصيله بعضها البعض. على سبيل المثال، يتم توصيل أنبوب المرجل بعنصر التسخين.
لكن من الخارج يتم إغلاق ماكينة القهوة بالغطاء الواقي، حتى لا يتمكن أحد من الوصول إليها. التفاصيل مخفية ولا يمكن الوصول إليها. يمكننا استخدام ميزاته عبر الواجهة الخارجية.
لذلك، كل ما نحتاجه لاستخدام كائن ما هو معرفة واجهته الخارجية. ربما لا ندرك تمامًا كيف يعمل الأمر في الداخل، وهذا رائع.
وكانت تلك مقدمة عامة.
يوجد في JavaScript نوعان من حقول الكائنات (الخصائص والأساليب):
عام: يمكن الوصول إليه من أي مكان. وهي تشكل الواجهة الخارجية. حتى الآن كنا نستخدم فقط الممتلكات والأساليب العامة.
خاص: يمكن الوصول إليه فقط من داخل الفصل. هذه هي للواجهة الداخلية.
في العديد من اللغات الأخرى توجد أيضًا حقول "محمية": يمكن الوصول إليها فقط من داخل الفصل وتلك التي تمتد إليه (مثل القطاع الخاص، ولكن بالإضافة إلى الوصول من الطبقات الموروثة). كما أنها مفيدة للواجهة الداخلية. وهي إلى حد ما أكثر انتشارًا من تلك الخاصة، لأننا عادةً ما نرغب في أن تتمكن الطبقات الموروثة من الوصول إليها.
لا يتم تنفيذ الحقول المحمية في JavaScript على مستوى اللغة، ولكنها عمليًا مريحة جدًا، لذا تتم محاكاتها.
الآن سنقوم بصنع ماكينة القهوة بلغة JavaScript مع كل هذه الأنواع من الخصائص. تحتوي ماكينة القهوة على الكثير من التفاصيل، ولن نصممها لتظل بسيطة (على الرغم من أننا نستطيع ذلك).
دعونا نصنع درسًا بسيطًا لآلة القهوة أولاً:
فئة ماكينة القهوة { waterAmount = 0; // كمية الماء بالداخل منشئ (الطاقة) { this.power = power; تنبيه( `تم إنشاء ماكينة القهوة، الطاقة: ${power}` ); } } // إنشاء آلة القهوة دع CoffeeMachine = new CoffeeMachine(100); // أضف الماء CoffeeMachine.waterAmount = 200;
في الوقت الحالي، أصبحت خصائص waterAmount
power
عامة. يمكننا بسهولة الحصول عليها/ضبطها من الخارج إلى أي قيمة.
دعونا نغير خاصية waterAmount
إلى protected لمزيد من التحكم فيها. على سبيل المثال، لا نريد لأحد أن يضعه تحت الصفر.
عادةً ما تكون الخصائص المحمية مسبوقة بشرطة سفلية _
.
لا يتم فرض ذلك على مستوى اللغة، ولكن هناك اتفاقية معروفة بين المبرمجين مفادها أنه لا ينبغي الوصول إلى هذه الخصائص والأساليب من الخارج.
لذا فإن ممتلكاتنا سوف تسمى _waterAmount
:
فئة ماكينة القهوة { _waterAmount = 0; تعيين كمية المياه (القيمة) { إذا (القيمة <0) { القيمة = 0؛ } this._waterAmount = value; } الحصول على كمية الماء () { إرجاع this._waterAmount; } منشئ (الطاقة) { this._power = power; } } // إنشاء آلة القهوة دع CoffeeMachine = new CoffeeMachine(100); // أضف الماء CoffeeMachine.waterAmount = -10; // _waterAmount سيصبح 0، وليس -10
الآن أصبح الوصول تحت السيطرة، لذا أصبح من المستحيل ضبط كمية المياه تحت الصفر.
بالنسبة لخاصية power
، فلنجعلها للقراءة فقط. يحدث أحيانًا أنه يجب تعيين الخاصية في وقت الإنشاء فقط، ثم لا يتم تعديلها مطلقًا.
هذا هو الحال تمامًا بالنسبة لآلة صنع القهوة: الطاقة لا تتغير أبدًا.
للقيام بذلك، نحتاج فقط إلى إنشاء أداة getter، وليس أداة setter:
فئة ماكينة القهوة { // ... منشئ (الطاقة) { this._power = power; } الحصول على الطاقة () { إرجاع هذا._power; } } // إنشاء آلة القهوة دع CoffeeMachine = new CoffeeMachine(100); تنبيه ("الطاقة هي: ${coffeeMachine.power}W`)؛ // الطاقة: 100 واط CoffeeMachine.power = 25; // خطأ (لا يوجد أداة ضبط)
وظائف Getter/Setter
استخدمنا هنا بناء جملة getter/setter.
ولكن في معظم الأحيان، تُفضل وظائف get.../set...
، مثل هذا:
فئة ماكينة القهوة { _waterAmount = 0; مجموعة WaterAmount (القيمة) { إذا (القيمة <0) القيمة = 0؛ this._waterAmount = value; } الحصول على المياه () { إرجاع this._waterAmount; } } new CoffeeMachine().setWaterAmount(100);
يبدو ذلك أطول قليلاً، لكن الوظائف أكثر مرونة. يمكنهم قبول حجج متعددة (حتى لو لم نكن بحاجة إليها الآن).
من ناحية أخرى، يكون بناء جملة get/set أقصر، لذا لا توجد قاعدة صارمة في النهاية، والأمر متروك لك لتقرر.
الحقول المحمية موروثة
إذا ورثنا class MegaMachine extends CoffeeMachine
، فلا شيء يمنعنا من الوصول إلى this._waterAmount
أو this._power
من توابع الفئة الجديدة.
لذا فإن الحقول المحمية قابلة للتوريث بشكل طبيعي. على عكس تلك الخاصة التي سنراها أدناه.
إضافة حديثة
هذه إضافة حديثة للغة. غير مدعوم في محركات JavaScript، أو مدعوم جزئيًا حتى الآن، ويتطلب تعبئة متعددة.
يوجد مقترح جافا سكريبت نهائي، تقريبًا في المعيار، يوفر دعمًا على مستوى اللغة للخصائص والأساليب الخاصة.
يجب أن يبدأ القطاع الخاص بـ #
. ولا يمكن الوصول إليها إلا من داخل الفصل.
على سبيل المثال، إليك خاصية #waterLimit
الخاصة والطريقة الخاصة لفحص المياه #fixWaterAmount
:
فئة ماكينة القهوة { # حد الماء = 200؛ #fixWaterAmount(value) { إذا (القيمة <0) تُرجع 0؛ إذا كانت (القيمة> هذا.#waterLimit) تُرجع هذا.#waterLimit؛ } مجموعة WaterAmount (القيمة) { this.#waterLimit = this.#fixWaterAmount(value); } } دع CoffeeMachine = new CoffeeMachine(); // لا يمكن الوصول إلى الأفراد من خارج الفصل CoffeeMachine.#fixWaterAmount(123); // خطأ CoffeeMachine.#waterLimit = 1000; // خطأ
على مستوى اللغة، يعد #
إشارة خاصة إلى أن الحقل خاص. لا يمكننا الوصول إليه من الخارج أو من وراثة الطبقات.
الحقول الخاصة لا تتعارض مع الحقول العامة. يمكننا الحصول على حقول #waterAmount
الخاصة وحقول waterAmount
العامة في نفس الوقت.
على سبيل المثال، لنجعل waterAmount
ملحقًا لـ #waterAmount
:
فئة ماكينة القهوة { #waterAmount = 0; الحصول على كمية الماء () { إرجاع هذا.#waterAmount؛ } تعيين كمية المياه (القيمة) { إذا (القيمة <0) القيمة = 0؛ this.#waterAmount = value; } } Let Machine = new CoffeeMachine(); waterAmount = 100; تنبيه(machine.#waterAmount); // خطأ
على عكس الحقول المحمية، يتم فرض الحقول الخاصة بواسطة اللغة نفسها. هذا شيء جيد.
ولكن إذا ورثنا من CoffeeMachine
، فلن يكون لدينا إمكانية الوصول المباشر إلى #waterAmount
. سنحتاج إلى الاعتماد على مُحضر/أداة ضبط waterAmount
:
يمتد فئة MegaCoffeeMachine إلى CoffeeMachine { طريقة() { تنبيه (this.#waterAmount)؛ // خطأ: لا يمكن الوصول إلا من خلال CoffeeMachine } }
وفي العديد من السيناريوهات، يكون هذا القيد شديدًا للغاية. إذا قمنا بتوسيع CoffeeMachine
، فقد يكون لدينا أسباب مشروعة للوصول إلى أجزائها الداخلية. ولهذا السبب يتم استخدام الحقول المحمية في كثير من الأحيان، على الرغم من أنها غير مدعومة من خلال بناء جملة اللغة.
الحقول الخاصة غير متوفرة بهذا الاسم [الاسم]
الحقول الخاصة خاصة.
كما نعلم، عادةً يمكننا الوصول إلى الحقول باستخدام this[name]
:
فئة المستخدم { ... قل مرحبا () { دع اسم الحقل = "الاسم"؛ تنبيه(`مرحبا، ${this[fieldName]}`); } }
مع الحقول الخاصة، هذا مستحيل: this['#name']
لا يعمل. هذا قيد بناء الجملة لضمان الخصوصية.
فيما يتعلق بـ OOP، فإن تحديد الواجهة الداخلية من الواجهة الخارجية يسمى التغليف.
ويعطي الفوائد التالية:
حماية للمستخدمين، حتى لا يطلقوا النار على أقدامهم
تخيل أن هناك فريقًا من المطورين يستخدم ماكينة صنع القهوة. وهي من صناعة شركة “Best CoffeeMachine”، وتعمل بشكل جيد، ولكن تم إزالة الغطاء الواقي عنها. لذلك يتم الكشف عن الواجهة الداخلية.
جميع المطورين متحضرون – فهم يستخدمون آلة القهوة على النحو المنشود. لكن أحدهم، جون، قرر أنه الأذكى، وأجرى بعض التعديلات على الأجزاء الداخلية لآلة القهوة. لذلك تعطلت ماكينة القهوة بعد يومين.
وهذا بالتأكيد ليس خطأ جون، بل خطأ الشخص الذي أزال الغطاء الواقي وترك جون يقوم بتلاعباته.
نفس الشيء في البرمجة. إذا قام مستخدم الفصل بتغيير أشياء لا يقصد تغييرها من الخارج، فإن العواقب لا يمكن التنبؤ بها.
قابلة للدعم
الوضع في البرمجة أكثر تعقيدًا مما هو عليه الحال مع ماكينة القهوة الواقعية، لأننا لا نشتريها مرة واحدة فقط. يخضع الكود للتطوير والتحسين باستمرار.
إذا قمنا بتحديد الواجهة الداخلية بشكل صارم، فيمكن لمطور الفصل تغيير خصائصه وأساليبه الداخلية بحرية، حتى بدون إبلاغ المستخدمين.
إذا كنت مطورًا لمثل هذه الفئة، فمن الرائع أن تعرف أنه يمكن إعادة تسمية الطرق الخاصة بأمان، ويمكن تغيير معلماتها، بل وحتى إزالتها، لأنه لا يوجد أي تعليمات برمجية خارجية تعتمد عليها.
بالنسبة للمستخدمين، عندما يصدر إصدار جديد، قد يكون إصلاحًا شاملاً داخليًا، ولكن لا يزال من السهل الترقية إذا كانت الواجهة الخارجية هي نفسها.
إخفاء التعقيد
يعشق الناس استخدام الأشياء البسيطة. على الأقل من الخارج. ما في الداخل هو شيء مختلف.
المبرمجين ليسوا استثناء.
يكون الأمر مناسبًا دائمًا عندما تكون تفاصيل التنفيذ مخفية، وتتوفر واجهة خارجية بسيطة وموثقة جيدًا.
لإخفاء واجهة داخلية نستخدم إما خصائص محمية أو خاصة:
الحقول المحمية تبدأ بـ _
. هذه اتفاقية معروفة، ولا يتم تطبيقها على مستوى اللغة. يجب على المبرمجين الوصول فقط إلى الحقل الذي يبدأ بـ _
من فئته والفئات الموروثة منه.
الحقول الخاصة تبدأ بـ #
. تتأكد JavaScript من أنه لا يمكننا الوصول إلا إلى تلك الموجودة داخل الفصل.
في الوقت الحالي، الحقول الخاصة ليست مدعومة بشكل جيد بين المتصفحات، ولكن يمكن ملؤها بشكل متعدد.