أحد أهم مبادئ البرمجة الشيئية – تحديد الواجهة الداخلية عن الواجهة الخارجية.
هذه ممارسة “لا بد منها” في تطوير أي شيء أكثر تعقيدًا من تطبيق “hello world”.
لفهم هذا ، دعونا نبتعد عن التنمية ونحول أعيننا إلى عالم حقيقي.
عادةً ما تكون الأجهزة التي نستخدمها معقدة للغاية. لكن تحديد الواجهة الداخلية عن الواجهة الخارجية يسمح باستخدامها دون مشاكل.
مثال واقعي
على سبيل المثال ، آلة صنع القهوة. بسيط من الخارج: زر ، شاشة ، بضع ثقوب … وبالتأكيد النتيجة – قهوة رائعة! :)
ولكن في الداخل … (صورة من دليل الإصلاح)
الكثير من التفاصيل. ولكن يمكننا استخدامه دون معرفة أي شيء.
آلات القهوة موثوقة تمامًا ، أليس كذلك؟ يمكننا استخدام واحد لسنوات ، وفقط إذا حدث خطأ – إحضاره للإصلاحات.
سر الموثوقية والبساطة في آلة القهوة – كل التفاصيل مضبوطة جيدًا و _ مخفية _ من الداخل.
إذا أزلنا الغطاء الواقي من آلة القهوة ، فسيكون استخدامه أكثر تعقيدًا (أين نضغط؟) ، وخطير (يمكن أن يصعق بالكهرباء).
كما سنرى ، في أدوات البرمجة تشبه آلات القهوة.
ولكن من أجل إخفاء التفاصيل الداخلية ، لن نستخدم غطاءًا واقيًا ، بل صياغة خاصة للغة والاتفاقيات.
الواجهة الداخلية والخارجية
في البرمجة الشيئية ، تنقسم الخصائص والأساليب إلى مجموعتين:
-
- الواجهة الداخلية * – الأساليب والخصائص ، يمكن الوصول إليها من طرق أخرى للفئة ، ولكن ليس من الخارج.
-
- الواجهة الخارجية * – الأساليب والخصائص ، ويمكن الوصول إليها أيضًا من خارج الفصل.
إذا واصلنا المقارنة مع آلة القهوة – ما هو مخفي في الداخل: أنبوب غلاية ، وعنصر تسخين ، وما إلى ذلك – هي واجهتها الداخلية.
يتم استخدام واجهة داخلية لكي يعمل الكائن ، وتفاصيله تستخدم بعضها البعض. على سبيل المثال ، يتم إرفاق أنبوب مرجل بعنصر التسخين.
ولكن من الخارج يتم إغلاق آلة القهوة بواسطة الغطاء الواقي ، بحيث لا يمكن لأحد الوصول إليها. التفاصيل مخفية ولا يمكن الوصول إليها. يمكننا استخدام ميزاته عبر الواجهة الخارجية.
لذا ، كل ما نحتاجه لاستخدام كائن هو معرفة واجهته الخارجية. قد نكون غير مدركين تمامًا لكيفية عملها في الداخل ، وهذا أمر رائع.
كانت تلك مقدمة عامة.
في JavaScript ، هناك نوعان من حقول الكائن (الخصائص والأساليب):
- عام: يمكن الوصول إليه من أي مكان. وهي تشمل الواجهة الخارجية. حتى الآن كنا نستخدم فقط الممتلكات العامة والأساليب.
- خاص: يمكن الوصول إليه فقط من داخل الفصل. هذه هي للواجهة الداخلية.
في العديد من اللغات الأخرى ، توجد أيضًا حقول “محمية”: يمكن الوصول إليها فقط من داخل الفصل وتلك التي توسعه (مثل الخاصة ، ولكن بالإضافة إلى الوصول من وراثة الطبقات). كما أنها مفيدة للواجهة الداخلية. إنها بمعنى أكثر انتشارًا من تلك الخاصة ، لأننا عادة ما نريد أن ترث الصفوف للوصول إليها.
لا يتم تنفيذ الحقول المحمية في JavaScript على مستوى اللغة ، ولكنها عمليًا مريحة للغاية ، لذا فهي محاكات.
الآن سنقوم بصنع آلة قهوة في JavaScript مع كل هذه الأنواع من الخصائص. تحتوي آلة القهوة على الكثير من التفاصيل ، لن نقوم بتصميمها لتبقى بسيطة (على الرغم من أننا نستطيع).
حماية “waterAmount”
دعونا نصنع صانعة قهوة بسيطة أولاً:
class CoffeeMachine {
waterAmount = 0; // the amount of water inside
constructor(power) {
this.power = power;
alert(`Created a coffee-machine, power: ${power}`);
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
// add water
coffeeMachine.waterAmount = 200;
الآن خصائص “waterAmount” و “power” عامة. يمكننا بسهولة الحصول عليها / تعيينها من الخارج إلى أي قيمة.
دعنا نغير خاصية waterAmount
لتكون محمية لمزيد من السيطرة عليها. على سبيل المثال ، لا نريد أن يقوم أي شخص بوضعه تحت الصفر.
** عادةً ما تكون الخصائص المحمية مسبوقة بشرطة سفلية _
. **
لا يتم فرض ذلك على مستوى اللغة ، ولكن هناك اتفاقية معروفة بين المبرمجين مفادها أنه لا يمكن الوصول إلى هذه الخصائص والأساليب من الخارج.
لذلك سيتم تسمية ممتلكاتنا _waterAmount
:
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) {
value = 0;
}
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this._power = power;
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
// add water
coffeeMachine.waterAmount = -10; // Error: Negative water
Now the access is under control, so setting the water amount below zero becomes impossible.
“قوة” للقراءة فقط
بالنسبة إلى خاصية power
، دعنا نجعلها للقراءة فقط. يحدث أحيانًا أنه يجب تعيين خاصية في وقت الإنشاء فقط ، ثم لا يتم تعديلها مطلقًا.
هذا هو الحال تمامًا بالنسبة لآلة القهوة: لا تتغير الطاقة أبدًا.
للقيام بذلك ، نحن بحاجة فقط إلى جعل getter ، ولكن ليس المحدد:
class CoffeeMachine {
// ...
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
// create the coffee machine
let coffeeMachine = new CoffeeMachine(100);
alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W
coffeeMachine.power = 25; // Error (no setter)
استخدمنا هنا صيغة getter / setter.
ولكن يُفضل استخدام وظائف get ... / set ...
في معظم الأوقات ، مثل هذا:
class CoffeeMachine {
_waterAmount = 0;
setWaterAmount(value) {
if (value < 0) value = 0;
this._waterAmount = value;
}
getWaterAmount() {
return this._waterAmount;
}
}
new CoffeeMachine().setWaterAmount(100);
يبدو ذلك أطول قليلاً ، ولكن الوظائف أكثر مرونة. يمكنهم قبول الحجج المتعددة (حتى لو لم نكن بحاجة إليها الآن).
من ناحية أخرى ، يكون بناء جملة get / set أقصر ، لذلك في النهاية لا توجد قاعدة صارمة ، الأمر متروك لك لاتخاذ القرار.
إذا ورثنا class MegaMachine يوسع CoffeeMachine
، فلا شيء يمنعنا من الوصول إلىthis._waterAmount
أو this._power
من طرق الفصل الجديد.
لذا فإن الحقول المحمية قابلة للتوريث بشكل طبيعي. على عكس الخاصة التي سنراها أدناه.
Private “#waterLimit”
هناك اقتراح جافا سكريبت مكتمل ، تقريبًا بشكل قياسي ، يوفر دعمًا على مستوى اللغة للممتلكات والأساليب الخاصة.
يجب أن يبدأ الأفراد بـ #
. يمكن الوصول إليها فقط من داخل الفصل.
على سبيل المثال ، إليك خاصية # waterLimit
الخاصة والطريقة الخاصة لفحص المياه# checkWater
:
class CoffeeMachine {
#waterLimit = 200;
#fixWaterAmount(value) {
if (value < 0) return 0;
if (value > this.#waterLimit) return this.#waterLimit;
}
setWaterAmount(value) {
this.#waterLimit = this.#fixWaterAmount(value);
}
}
let coffeeMachine = new CoffeeMachine();
// can't access privates from outside of the class
coffeeMachine.#fixWaterAmount(123); // Error
coffeeMachine.#waterLimit = 1000; // Error
على مستوى اللغة ، يعد #
علامة خاصة على أن المجال خاص. لا يمكننا الوصول إليها من الخارج أو من وراثة الطبقات.
لا تتعارض الحقول الخاصة مع الحقول العامة. يمكننا الحصول على حقلي # waterAmount
الخاص وحقلwaterAmount
العام في نفس الوقت.
على سبيل المثال ، دعنا نجعل waterAmount
موصلًا لـ# waterAmount
:
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount;
}
set waterAmount(value) {
if (value < 0) value = 0;
this.#waterAmount = value;
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
على عكس المجالات المحمية ، يتم فرض الحقول الخاصة بواسطة اللغة نفسها. هذا شيء جيد.
ولكن إذا ورثنا من “CoffeeMachine” ، فلن يكون لدينا وصول مباشر إلى “# waterAmount”. سنحتاج إلى الاعتماد على مُحضِّر / أداة تعيين “waterAmount”:
class MegaCoffeeMachine extends CoffeeMachine {
method() {
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
}
}
في العديد من السيناريوهات ، يكون هذا التقييد شديدًا جدًا. إذا قمنا بتمديد “CoffeeMachine” ، فقد تكون لدينا أسباب مشروعة للوصول إلى محتوياتها الداخلية. هذا هو السبب في استخدام الحقول المحمية في كثير من الأحيان ، على الرغم من أنها لا تدعمها بنية اللغة.
الحقول الخاصة خاصة.
كما نعلم ، عادة يمكننا الوصول إلى الحقول باستخدامthis[name]
:
class User {
...
sayHi() {
let fieldName = "name";
alert(`Hello, ${this[fieldName]}`);
}
}
مع الحقول الخاصة المستحيلة: لا يعمل هذا [‘# name’]. هذا قيود على بناء الجملة لضمان الخصوصية.
ملخص
من حيث OOP ، فإن تحديد الواجهة الداخلية من الواجهة الخارجية يسمى [التغليف] (https://en.wikipedia.org/wiki/Encapsulation_ (computer_programming)).
يعطي الفوائد التالية:
- حماية للمستخدمين ، حتى لا يطلقوا النار على أنفسهم في القدم
- تخيل ، هناك فريق من المطورين يستخدمون آلة صنع القهوة. تم تصنيعه من قبل شركة “Best CoffeeMachine” ، ويعمل بشكل جيد ، ولكن تمت إزالة غطاء واقٍ. لذلك يتم الكشف عن الواجهة الداخلية.
جميع المطورين متحضرين – يستخدمون آلة القهوة على النحو المنشود. لكن أحدهم ، جون ، قرر أنه الأذكى ، وقام ببعض التعديلات في آلة صنع القهوة الداخلية. لذلك فشلت آلة القهوة بعد ذلك بيومين.
هذا بالتأكيد ليس خطأ جون ، بل الشخص الذي أزال الغطاء الواقي وترك جون يتلاعب به.
نفس الشيء في البرمجة. إذا قام مستخدم من فئة ما بتغيير الأشياء التي لا يقصد تغييرها من الخارج – فإن العواقب لا يمكن التنبؤ بها.
- يمكن دعمه
- الوضع في البرمجة أكثر تعقيدًا من ماكينة القهوة الواقعية ، لأننا لا نشتريها مرة واحدة فقط. يخضع القانون باستمرار للتطوير والتحسين.
** إذا قمنا بتحديد الواجهة الداخلية بشكل صارم ، فيمكن لمطور الفئة تغيير خصائصه وأساليبه الداخلية بحرية ، حتى بدون إعلام المستخدمين. **
إذا كنت مطورًا لمثل هذه الفئة ، فمن الرائع أن تعرف أنه يمكن إعادة تسمية الطرق الخاصة بأمان ، ويمكن تغيير معلماتها ، بل وإزالتها ، لأنه لا يوجد كود خارجي يعتمد عليها.
بالنسبة للمستخدمين ، عندما يظهر إصدار جديد ، قد يكون إصلاحًا شاملاً داخليًا ، ولكن لا يزال من السهل الترقية إذا كانت الواجهة الخارجية هي نفسها.
- إخفاء التعقيد
- يعشق الناس باستخدام أشياء بسيطة. على الأقل من الخارج. ما في الداخل شيء مختلف.
المبرمجون ليسوا استثناء.
** من الملائم دائمًا أن تكون تفاصيل التنفيذ مخفية ، وتتوافر واجهة خارجية بسيطة وموثقة جيدًا. **
لإخفاء واجهة داخلية ، نستخدم إما خصائص محمية أو خاصة:
- تبدأ الحقول المحمية بـ
_
. هذه اتفاقية معروفة جيدًا ، ولا يتم تطبيقها على مستوى اللغة. يجب على المبرمجين الوصول فقط إلى حقل يبدأ بـ_
من فئته والفصول الموروثة منه. - تبدأ الحقول الخاصة بـ
#
. جافا سكريبت تتأكد من أنه لا يمكننا الوصول إلا من داخل الفصل.
في الوقت الحالي ، لا يتم دعم الحقول الخاصة بشكل جيد بين المتصفحات ، ولكن يمكن إعادة ملؤها.