يقدم
في هذه المقالة ، نعتبر جوانب مختلفة من البرمجة الموجهة للكائنات في ECMAScript (على الرغم من أن هذا الموضوع قد تمت مناقشته في العديد من المقالات من قبل). سننظر في هذه القضايا أكثر من منظور نظري. على وجه الخصوص ، سننظر في خوارزمية إنشاء الكائن ، وكيف تتوفر العلاقة بين الكائنات (بما في ذلك العلاقات الأساسية - الميراث) في المناقشة (آمل أن تتم إزالة بعض الغموض المفاهيمي لـ OOP في JavaScript).
النص الإنجليزي الأصلي: http://dmitrysoshnikov.com/ecmascript/chapter-7-1-ooop-general-theory/
مقدمة ونموذج وأفكار
قبل إجراء التحليل الفني لـ OOP في ECMAScript ، من الضروري إتقان بعض الخصائص الأساسية لـ OOP وتوضيح المفاهيم الرئيسية في المقدمة.
يدعم ECMASCRIPT طرق البرمجة المختلفة بما في ذلك المهيكلة والموجهة نحو الكائنات والوظيفية والضرورية وما إلى ذلك ، وفي بعض الحالات ، تدعم أيضًا البرمجة الموجهة إلى الجانب ؛ لكن هذه المقالة تناقش البرمجة الموجهة للكائنات ، لذلك سنقدم تعريف البرمجة الموجهة للكائنات في ECMASCRIPT:
ECMASCRIPT هي لغة برمجة موجهة نحو الكائن تستند إلى تنفيذ النموذج الأولي.
هناك العديد من الاختلافات بين OOP المستندة إلى النموذج الأولي والأساليب القائمة على الفئة الثابتة. دعونا نلقي نظرة على الاختلافات التفصيلية المباشرة.
القائم على الفئة ونموذج أولي
لاحظ أن النقطة المهمة في الجملة السابقة قد أشارت بالفعل - وهي تعتمد بالكامل على الطبقات الثابتة. مع كلمة "ثابت" ، نفهم الأشياء الثابتة والفئات الثابتة ، التي كتبت بقوة (على الرغم من أنها غير مطلوبة).
فيما يتعلق بهذا الموقف ، تؤكد العديد من وثائق المنتدى على أن هذا هو السبب الرئيسي وراء معارضتهم لمقارنة "الفئات بالنماذج الأولية" في JavaScript. على الرغم من أن اختلافات التنفيذ الخاصة بهم (مثل Python و Ruby على أساس الفئات الديناميكية) ليست معارضة جدًا لهذه النقطة (بعض الشروط مكتوبة ، على الرغم من وجود بعض الاختلافات في الأفكار ، فإن JavaScript ليست بديلة) ، ولكن معارضةها هي فئات ثابتة ونماذج أولية ديناميكية (فئات الإحصائيات + الديناميات + النماذج البينية). لكي تكون دقيقًا ، يمكن أن تسمح لنا فئة ثابتة (مثل C ++ ، Java) ومرؤوسيها وآليات تعريف الطريقة ، الفرق الدقيق بينه وبين تنفيذ النموذج الأولي.
ولكن دعنا ندرجهم واحدا تلو الآخر. دعونا نفكر في المبادئ العامة والمفاهيم الرئيسية لهذه النماذج.
بناءً على الطبقات الثابتة
في نموذج قائم على الفصل ، هناك مفهوم حول الفصول والحالات. غالبًا ما يتم تسمية حالات الفصول الكائنات أو النماذج.
الفصول والكائنات
يمثل الفئة تجريدًا للمثيل (أي كائن). هذا يشبه إلى حد ما الرياضيات ، لكننا نسميها نوع أو تصنيف.
على سبيل المثال (الأمثلة هنا وأقل هي رمز زائفة):
نسخة الكود كما يلي:
C = Class {A ، B ، C} // Class C ، بما في ذلك الميزات A ، B ، C
خصائص المثيل هي: السمات (وصف الكائن) والطرق (نشاط الكائن). يمكن أيضًا اعتبار الخصائص نفسها ككائنات: أي ما إذا كانت السمة قابلة للكتابة ، قابلة للتكوين ، قابلة للتسوية (getter/setter) ، إلخ. لذلك ، تحدد حالة الكائنات (أي ، القيم المحددة لجميع السمات الموضحة في الفصل) ، والفئات تحدد الهياكل الثابتة بشكل صارم (الخصائص) وسلوكيات غير واضحة بشكل صارم (الطرق).
نسخة الكود كما يلي:
c = class {a ، b ، c ، method1 ، method2}
C1 = {A: 10 ، B: 20 ، C: 30} // Class C هي مثيل: كائن с1
C2 = {A: 50 ، B: 60 ، C: 70} // Class C هي مثيل: الكائن с2 ، الذي له حالته الخاصة (أي ، قيمة السمة)
الميراث الهرمي
لتحسين إعادة استخدام التعليمات البرمجية ، يمكن تمديد الفصول من واحد إلى آخر ، مع إضافة معلومات إضافية. وتسمى هذه الآلية الميراث (التسلسل الهرمي).
نسخة الكود كما يلي:
d = class يمتد c = {d ، e} // {a ، b ، c ، d ، e}
D1 = {A: 10 ، B: 20 ، C: 30 ، D: 40 ، E: 50}
عند الاتصال بالحزب على مثيل الفصل ، عادة ما يبحث الفصل الأصلي عن الطريقة الآن. إذا لم يتم العثور عليها ، فانتقل إلى فئة الوالدين للبحث مباشرة. إذا لم يتم العثور عليها ، فانتقل إلى فئة الوالدين لفئة الوالدين للبحث (على سبيل المثال ، في سلسلة الميراث الصارمة). إذا تبين أنه لم يتم العثور على الجزء العلوي من الميراث ، فإن النتيجة هي: الكائن ليس له سلوك مماثل ولا يمكنه الحصول على النتيجة.
نسخة الكود كما يلي:
d1.method1 () // d.method1 (no) -> c.method1 (نعم)
d1.method5 () // d.method5 (no) -> c.method5 (no) -> لا توجد نتيجة
مقارنة بطرق الميراث التي لا يتم نسخها إلى فئة فرعية ، تكون الخصائص معقدة دائمًا في فئات فرعية. يمكننا أن نرى أن الفئة الفرعية d ترث من الفئة الأصل C: يتم نسخ الصفات A ، B ، C ، وأن بنية D هي {A ، B ، C ، D ، E}}. ومع ذلك ، فإن الطريقة {method1 ، method2} لا تنسخ الماضي ، ولكنها ترث الماضي. لذلك ، وهذا هو ، إذا كان لدى فئة عميقة للغاية بعض الخصائص التي لا تحتاجها الكائنات على الإطلاق ، فإن الفئة الفرعية لديها أيضًا هذه الخصائص.
المفاهيم الرئيسية القائمة على الفصول الدراسية
لذلك ، لدينا المفاهيم الرئيسية التالية:
1. قبل إنشاء كائن ، يجب إعلان الفصل. أولاً ، من الضروري تحديد فصلها.
2. لذلك ، سيتم إنشاء الكائن من فئة مجردة إلى "تصويره وتشابهه" (الهيكل والسلوك)
3. يتم التعامل مع الطريقة من خلال سلسلة الميراث الصارمة والمباشرة وغير المتغيرة.
4. تحتوي الفئة الفرعية على جميع السمات في سلسلة الميراث (حتى لو لم تكن بعض السمات مطلوبة بواسطة الفئة الفرعية) ؛
5. إنشاء مثيل فئة. لا يمكن للفئة (بسبب نموذج ثابت) تغيير خصائص (سمات أو طرق) لمثيلها ؛
6. لا يمكن أن يكون للحالات (بسبب النماذج الثابتة الصارمة) سلوكيات أو سمات إضافية باستثناء السلوكيات والسمات المعلنة في الفئة المقابلة إلى المثيل.
دعونا نرى كيفية استبدال نموذج OOP في JavaScript ، وهو OOP القائم على النموذج الأولي الذي نوصي به.
بناء على النموذج الأولي
المفهوم الأساسي هنا هو كائنات قابلة للتغيير الديناميكية. يرتبط التحويل (التحويل الكامل ، بما في ذلك القيم ، ولكن أيضًا الميزات) مباشرة باللغة الديناميكية. يمكن للكائنات مثل ما يلي تخزين جميع خصائصها بشكل مستقل (الخصائص ، الأساليب) بدون فئات.
نسخة الكود كما يلي:
Object = {a: 10 ، b: 20 ، c: 30 ، الطريقة: fn} ؛
Object.A ؛ // 10
Object.c ؛ // 30
object.method () ؛
بالإضافة إلى ذلك ، بسبب الديناميكية ، يمكنهم بسهولة تغيير (إضافة وحذف وتعديل) ميزاتهم الخاصة:
نسخة الكود كما يلي:
object.method5 = function () {...} ؛ // أضف طريقة جديدة
Object.d = 40 ؛ // أضف سمة جديدة "د"
حذف object.c ؛ // حذف السمة "с"
Object.a = 100 ؛ // تعديل السمة "а"
// النتيجة هي: الكائن: {a: 100 ، b: 20 ، d: 40 ، الطريقة: fn ، method5: fn} ؛
هذا هو ، عند التعيين ، إذا لم تكن هناك بعض الميزات ، يتم إنشاؤها ويتم تهيئة المهمة معها ، وإذا كانت موجودة ، فسيتم تحديثها للتو.
في هذه الحالة ، لا يتم تنفيذ إعادة استخدام التعليمات البرمجية عن طريق توسيع الفصل (لاحظ أننا لم نقول أنه لا يمكن تغيير الفصل لأنه لا يوجد مفهوم للفصل هنا) ، ولكن يتم تنفيذه بواسطة النماذج الأولية.
النموذج الأولي هو كائن يستخدم كنسخة بدائية من الكائنات الأخرى ، أو إذا لم يكن لدى بعض الكائنات خصائصها اللازمة ، فيمكن استخدام النموذج الأولي كمندوب لهذه الكائنات والعمل ككائن مساعد.
بناء على التفويض
يمكن استخدام أي كائن ككائن نموذج أولي لكائن آخر ، لأن الكائن يمكنه بسهولة تغيير ديناميات النموذج الأولي في وقت التشغيل.
لاحظ أننا نفكر حاليًا في مقدمة بدلاً من تطبيق ملموس ، وعندما نناقش التنفيذ الملموس في ECMASCRIPT ، سنرى بعض خصائصها الخاصة.
مثال (رمز كاذب):
نسخة الكود كما يلي:
x = {a: 10 ، b: 20} ؛
y = {a: 40 ، c: 50} ؛
y. [[النموذج الأولي]] = x ؛ // x هو النموذج الأولي لـ y
يا // 40 ، خصائصها الخاصة
YC ؛ // 50 ، إنه أيضًا خاصته الخاصة به
Yb ؛ // 20 احصل من النموذج الأولي: yb (no) -> y. [[النموذج الأولي]]. ب (نعم): 20
حذف يا ؛ // حذف "а"
يا // 10 احصل عليه من النموذج الأولي
z = {a: 100 ، e: 50}
y. [[النموذج الأولي]] = z ؛ // تعديل النموذج الأولي لـ y إلى z
يا // 100 احصل من النموذج الأولي Z
ye // 50 ، تم الحصول عليها أيضًا من النموذج الأولي z
ZQ = 200 // أضف سمة جديدة إلى النموذج الأولي
yq // تعديل ينطبق أيضًا على y
يوضح هذا المثال الوظائف والآليات المهمة للنموذج الأولي كسمات كائن مساعد ، تمامًا كما تحتاج إلى سماتك الخاصة. بالمقارنة مع سماتك الخاصة ، فإن هذه السمات هي سمات تفويض. تسمى هذه الآلية مندوبًا ، ونموذج النموذج الأولي يعتمد عليه هو نموذج أولي مندوب (أو نموذج أولي على أساس مندوب). تسمى الآلية المرجعية إرسال المعلومات إلى كائن. إذا لم يحصل الكائن على استجابة ، فسيتم تفويضه إلى النموذج الأولي للعثور عليه (يتطلب الأمر محاولة الرد على الرسالة).
يسمى إعادة استخدام الكود في هذه الحالة الميراث القائم على المندوبين أو الميراث القائم على النموذج الأولي. نظرًا لأنه يمكن اعتبار أي كائن نموذجًا أوليًا ، أي أن النموذج الأولي يمكن أن يكون له النموذج الأولي الخاص به أيضًا. ترتبط هذه النماذج الأولية معًا لتشكيل سلسلة النماذج الأولية المزعومة. تعد السلاسل أيضًا هرمية في الطبقات الثابتة ، ولكن يمكن إعادة ترتيبها بسهولة ، وتغيير التسلسلات الهرمية والهياكل.
نسخة الكود كما يلي:
x = {a: 10}
y = {b: 20}
y. [[النموذج الأولي]] = x
z = {c: 30}
z. [[النموذج الأولي]] = y
ZA // 10
// ZA وجدت في سلسلة النموذج الأولي:
// ZA (NO) ->
// z. [[النموذج الأولي]]. a (no) ->
// z. [[النموذج الأولي]]. [[النموذج الأولي]]. A (نعم): 10
إذا لم يتمكن كائن وسلسلة النموذج الأولي الخاص به من إرسال الرسائل ، فيمكن للكائن تنشيط إشارة النظام المقابلة ، والتي يمكن معالجتها من قبل مندوبين آخرين على سلسلة النموذج الأولي.
تتوفر إشارة النظام هذه في العديد من التطبيقات ، بما في ذلك الأنظمة القائمة على الفئات الديناميكية: #doesnotunderstand في Smalltalk ، method_missing في Ruby ؛ __getattr__ في Python ، __call في php ؛ و __nosuchmethod__ تنفيذ ecmascript ، إلخ.
مثال (تطبيق Ecmascript الخاص بـ SpiderMonkey):
نسخة الكود كما يلي:
var object = {
// اصطياد إشارة النظام التي لا يمكنها الاستجابة للرسائل
__nosuchmethod__: وظيفة (الاسم ، args) {
تنبيه ([الاسم ، args]) ؛
if (name == 'test') {
يتم التعامل مع طريقة الإرجاع '.Test () ؛
}
إرجاع مندوب [name] .apply (هذا ، args) ؛
}
} ؛
var depegate = {
مربع: وظيفة (أ) {
إرجاع A * A ؛
}
} ؛
التنبيه (object.square (10)) ؛ // 100
التنبيه (object.test ()) ؛ //. Test () يتم التعامل مع طريقة
وهذا يعني ، استنادًا إلى تنفيذ الفئات الثابتة ، عندما لا يمكن الرد على الرسالة ، فإن الاستنتاج هو أن الكائن الحالي لا يحتوي على الخصائص المطلوبة ، ولكن إذا حاولت الحصول عليها من سلسلة النموذج الأولي ، فقد لا يزال لديك النتيجة ، أو أن الكائن لديه خاصية بعد سلسلة من التغييرات.
فيما يتعلق بـ ECMASCRIPT ، فإن التنفيذ المحدد هو: استخدام نموذج أولي يعتمد على المندوب. ومع ذلك ، كما سنرى من المواصفات والتطبيقات ، لديهم أيضًا خصائصهم الخاصة.
نموذج متسلسل
بصراحة ، من الضروري أن نقول شيئًا عن موقف آخر (لا يستخدم في ECMASCript في أقرب وقت ممكن): هذا الموقف عندما يكون النموذج الأولي معقدًا من كائنات أخرى إلى استبدال الكائنات الأصلية. في هذه الحالة ، يعد إعادة استخدام رمز إعادة استخدام نسخة حقيقية (استنساخ) لكائن بدلاً من مندوب أثناء مرحلة إنشاء الكائن. ويسمى هذا النموذج الأولي النموذج الأولي المتسلسل. يمكن أن يؤدي نسخ خصائص جميع النماذج الأولية للكائن إلى تغيير خصائصه وطرقه تمامًا ، وكنموذج أولي ، يمكنك أيضًا تغيير نفسك (في نموذج قائم على المندوبين ، لن يغير هذا التغيير سلوك الكائنات الموجودة ، ولكنه سيغير خصائصه النموذجية). ميزة هذه الطريقة هي أنه يمكن أن يقلل من وقت الجدولة والتفويض ، في حين أن العيب هو أنه يستخدم الذاكرة.
نوع البط
بالعودة إلى كائن تغيير النوع الضعيف ديناميكيًا ، مقارنة بالنموذج الثابت المستند إلى الفئة ، من الضروري التحقق مما إذا كان بإمكانه القيام بهذه الأشياء وما هو النوع (الفئة) للكائن ، ولكن ما إذا كان يمكن أن يكون مرتبطًا بالرسالة المقابلة (أي ما إذا كان لديه القدرة على القيام بذلك بعد التحقق).
على سبيل المثال:
نسخة الكود كما يلي:
// في نموذج ثابت
if (object extryof someclass) {
// بعض السلوكيات تعمل
}
// في التنفيذ الديناميكي
// لا يهم نوع الكائن في هذا الوقت
// لأن الطفرات والأنواع والخصائص يمكن تغييرها بحرية.
// ما إذا كانت الكائنات المهمة يمكنها الاستجابة للاختبار الرسائل
if (isFunction (Object.test)) // ecmascript
إذا كان الكائن
إذا hasattr (كائن ، 'اختبار'): // python
وهذا ما يسمى نوع الرصيف. أي أنه يمكن تحديد الكائنات من خلال خصائصها الخاصة عند التحقق ، بدلاً من موقع الكائنات في التسلسل الهرمي أو أنها تنتمي إلى أي نوع محدد.
المفاهيم الرئيسية القائمة على النماذج الأولية
لنلقي نظرة على الميزات الرئيسية لهذه الطريقة:
1. المفهوم الأساسي هو الكائن
2. الكائنات ديناميكية تمامًا وقابلة للتغيير (من الناحية النظرية ، يمكن تحويلها بالكامل من نوع إلى آخر)
3. لا تحتوي الكائنات على فئات صارمة تصف بنيةها وسلوكها ، ولا تحتاج الكائنات إلى فئات.
4. الكائنات ليس لها فئات فئة ولكن يمكن أن تحتوي على نماذج أولية. إذا لم يتمكنوا من الرد على الرسائل ، فيمكنهم تفويض النماذج الأولية.
5. يمكن تغيير النموذج الأولي للكائن في أي وقت أثناء وقت التشغيل ؛
6. في نموذج قائم على المندوبين ، سيؤثر تغيير خصائص النموذج الأولي على جميع الكائنات المتعلقة بالنموذج الأولي ؛
7. في نموذج النموذج الأولي المتسلسل ، يكون النموذج الأولي هو النسخة الأصلية المستنسخة من كائنات أخرى وتصبح كذلك نسخة مستقلة تمامًا. لن يؤثر تحول خصائص النموذج الأولي على الكائن المستنسخ منه.
8. إذا كان لا يمكن الرد على الرسالة ، يمكن أن يتخذ المتصل تدابير إضافية (على سبيل المثال ، تغيير الجدولة)
9. يمكن تحديد فشل الكائن ليس بمستوىهم وأي فئة ينتمون إليها ، ولكن من خلال الخصائص الحالية.
ومع ذلك ، هناك نموذج آخر يجب أن نفكر فيه أيضًا.
بناءً على الطبقات الديناميكية
نحن نعتقد أن الفرق "الفئة VS Ortype" الموضحة في المثال أعلاه ليس مهمًا في هذا النموذج الديناميكي القائم على الفئة (خاصةً إذا لم تتغير سلسلة النموذج الأولي ، فلا يزال من الضروري النظر في فئة ثابتة للتمييز أكثر دقة). على سبيل المثال ، يمكن أيضًا استخدامه في Python أو Ruby (أو لغات مماثلة أخرى). كل هذه اللغات تستخدم نماذج ديناميكية قائمة على الفصل. ومع ذلك ، في بعض الجوانب ، يمكننا أن نرى بعض الوظائف التي يتم تنفيذها على أساس النماذج الأولية.
في المثال التالي ، يمكننا أن نرى أنه استنادًا إلى النموذج الأولي للفوض ، يمكننا تضخيم فئة (النموذج الأولي) ، وبالتالي التأثير على جميع الكائنات المتعلقة بهذه الفئة ، يمكننا أيضًا تغيير فئة هذا الكائن ديناميكيًا في وقت التشغيل (توفير كائن جديد للمفوض) ، وما إلى ذلك.
نسخة الكود كما يلي:
# بيثون
الفئة A (كائن):
def __init __ (الذات ، أ):
Self.a = أ
مربع ديف (الذات):
العودة self.a * self.a
a = a (10) # قم بإنشاء مثيل
طباعة (AA) # 10
AB = 20 # توفير خاصية جديدة للفصل
طباعة (AB) # 20 يمكنك الوصول إليها في مثيل "A"
A = 30 # قم بإنشاء خاصية خاصة بها
طباعة (AB) # 30
del ab # حذف سماتها الخاصة
طباعة (AB) # 20 - احصل عليها من الفصل مرة أخرى (النموذج الأولي)
# مثل النموذج القائم على النموذج الأولي
# يمكن أن يغير النموذج الأولي للكائن في وقت التشغيل
الفئة ب (كائن): # الفئة الفارغة ب
يمر
B = B () # B مثيل
ب .__ class__ = A # تغيير ديناميكي (النموذج الأولي)
BA = 10 # قم بإنشاء سمة جديدة
طباعة (b.square ()) # 100 - طريقة الفئة A متوفرة في هذا الوقت
# يمكن عرض المراجع على الفصول المحذوفة
ديل أ
ديل ب
# لكن الكائن لا يزال لديه مراجع ضمنية ، ولا تزال هذه الطرق متوفرة
طباعة (b.square ()) # 100
# لكن لا يمكنك تغيير الفصل في هذا الوقت
# هذه هي ميزة التنفيذ
ب .__ class__ = dict # error
يتشابه التنفيذ في Ruby: كما أنه يستخدم فئات ديناميكية تمامًا (بالمناسبة ، في الإصدار الحالي من Python ، مقارنةً مع Ruby و Ecmascript ، لا يمكن إجراء فئات تضخيم (النماذج الأولية)) ، ويمكننا أن نؤثر تمامًا على خصائص الكائن (أو الفئة) (إضافة طرق/سمات إلى الفئة ، وستؤثر هذه التغييرات على الكائنات الحالية) ، لكن لا يمكن تغييرها بشكل ديناميكي.
ومع ذلك ، فإن هذه المقالة ليست على وجه التحديد للبيثون وروبي ، لذلك لن نقول الكثير ، دعنا نستمر في مناقشة ECMASCRIPT نفسها.
ولكن قبل ذلك ، يتعين علينا أن ننظر إلى "السكر الاصطناعي" الموجود في بعض OOPs ، لأن العديد من المقالات السابقة حول JavaScript غالبًا ما تغطي هذه المشكلات.
الجملة الخاطئة الوحيدة التي يجب الإشارة إليها في هذا القسم هي: "JavaScript ليس فئة ، وله نموذج أولي ويمكنه استبدال فئة." من الضروري جدًا معرفة أنه ليس كل التطبيقات القائمة على الفصل مختلفًا تمامًا. حتى لو كان بإمكاننا أن نقول "JavaScript مختلف" ، فمن الضروري أيضًا النظر (إلى جانب مفهوم "الفئات") أن هناك ميزات أخرى ذات صلة.
ميزات أخرى لتطبيقات OOP المختلفة
في هذا القسم ، نقدم باختصار ميزات أخرى وتطبيقات OOP المختلفة حول إعادة استخدام التعليمات البرمجية ، بما في ذلك تطبيقات OOP في ECMASCRIPT. والسبب هو أن المظهر السابق لتنفيذ OOP في JavaScript له بعض قيود التفكير المعتاد. المطلب الرئيسي الوحيد هو أنه ينبغي إثباته تقنيًا وأيديولوجيًا. لا يمكن القول أنك لم تكتشف وظيفة Syntax Sugar في تطبيقات OOP الأخرى ، وليس لديك أي فكرة أن JavaScript ليست لغة OOP خالصة ، وهذا خطأ.
متعدد الأشكال
في ECMascript ، هناك العديد من الأشكال المتعددة التي تعني فيها الأشياء.
على سبيل المثال ، يمكن تطبيق وظيفة على كائنات مختلفة ، تمامًا مثل خصائص كائن أصلي (لأن هذه القيمة يتم تحديدها عند إدخال سياق التنفيذ):
نسخة الكود كما يلي:
اختبار الوظيفة () {
تنبيه ([this.a ، this.b]) ؛
}
Test.Call ({a: 10 ، b: 20}) ؛ // 10 ، 20
Test.Call ({a: 100 ، b: 200}) ؛ // 100 ، 200
var a = 1 ؛
var b = 2 ؛
امتحان()؛ // 1 ، 2
ومع ذلك ، هناك استثناءات: date.prototype.getTime () طريقة ، وفقًا للمعيار ، يجب أن يكون هناك دائمًا كائن تاريخ ، وإلا سيتم طرح استثناء.
نسخة الكود كما يلي:
ALERT (date.prototype.gettime.call (date ())) ؛ // وقت
ALERT (date.prototype.gettime.call (سلسلة جديدة (''))) ؛ // typeerror
تعدد تعدد الأشكال المعلمة المزعومة عند تحديد وظيفة ما يعادل جميع أنواع البيانات ، ولكنه يقبل فقط المعلمات الأشكال المتعددة (مثل طريقة فرز .SORT للمصفوفة ومعلماتها - وظيفة الفرز من الأشكال). بالمناسبة ، يمكن أيضًا اعتبار المثال أعلاه بمثابة تعدد الأشكال.
يمكن تعريف الطريقة في النموذج الأولي على أنها فارغة ، ويجب إعادة تعريف جميع الكائنات التي تم إنشاؤها (تنفيذها) الطريقة (أي "واجهة واحدة (توقيع) ، تطبيقات متعددة").
يرتبط تعدد الأشكال بنوع البط الذي ذكرناه أعلاه: أي نوع الكائن وموضعه في التسلسل الهرمي ليس مهمًا ، ولكن يمكن قبوله بسهولة إذا كان يحتوي على جميع الميزات اللازمة (أي الواجهة العامة مهمة ، ويمكن أن يكون التنفيذ متنوعًا).
طَرد
غالبًا ما تكون هناك وجهات نظر خاطئة حول التغليف. في هذا القسم ، سنناقش بعض السكريات النحوية في تطبيقات OOP - أي المعدلات المعروفة: في هذه الحالة ، سنناقش بعض تطبيقات OOP المريحة "السكريات" - المعدلات المعروفة: خاصة ، محمية وعامة (أو مستوى الوصول إلى الكائنات أو معدلات الوصول).
أود هنا أن أذكرك عن الغرض الرئيسي من التغليف: التغليف هو إضافة مجردة ، بدلاً من اختيار "قراصين ضار" مخفي يكتب شيئًا مباشرة في صفك.
هذا خطأ كبير: استخدم Hidden من أجل الاختباء.
تم تنفيذ مستويات الوصول (خاصة ، محمية وعامة) ، من أجل راحة البرمجة ، في العديد من الموجهة نحو الكائنات (السكر المبزيئ المريح للغاية) ، ووصف النظام وبناءه بشكل أكثر تجريدية.
يمكن رؤية هذه في بعض التطبيقات (مثل Python و Ruby التي سبق ذكرها). من ناحية (في Python) ، لا يمكن الوصول إلى هذه الخصائص المحمية (من خلال مواصفات الاسم السفلية) من الخارج. من ناحية أخرى ، يمكن الوصول إلى Python من الخارج من خلال قواعد خاصة (_className__field_Name).
نسخة الكود كما يلي:
الفئة A (كائن):
def __init __ (الذات):
Self.public = 10
الذات. __ private = 20
def get_private (الذات):
إرجاع الذات .__ الخاصة
# الخارج:
A = A () # مثال على أ
طباعة (A.Public) # OK ، 30
PRINT (A.GET_PRIVAL ()) # OK ، 20
طباعة (A.__ private) # فشل لأنه يمكن أن يكون متاحًا فقط في أ
# ولكن في بيثون ، يمكن الوصول إلى قواعد خاصة
طباعة (A._A__Private) # OK ، 20
في Ruby: من ناحية ، لديها القدرة على تحديد خصائص الخاصة والمحمية ، ومن ناحية أخرى ، هناك أيضًا طرق خاصة (مثل exital_variable_get ، evalual_variable_set ، إرسال ، إلخ) للحصول على بيانات مغلفة.
نسخة الكود كما يلي:
الفئة أ
DEF تهيئة
A = 10
نهاية
def public_method
private_method (20)
نهاية
خاص
def private_method (ب)
إرجاع A + B
نهاية
نهاية
A = A.New # مثيل جديد
A.Public_Method # OK ، 30
فشل AA # ، A - هو متغير مثيل خاص
# "private_method" خاص ولا يمكن الوصول إليه إلا في الفئة أ
A.Private_Method # خطأ
# ولكن هناك اسم طريقة بيانات تعريف خاصة يمكنها الحصول على البيانات
A.Send (: private_method ، 20) # OK ، 30
A.Instance_variable_get (:@a) # ok ، 10
السبب الرئيسي هو أن تغليف (لاحظ أنني لا أستخدم بيانات "مخفية") التي يريد المبرمج الحصول عليها. إذا تم تغيير هذه البيانات بشكل غير صحيح بطريقة أو بأخرى ، فهناك أي أخطاء ، فإن المسؤولية بأكملها هي المبرمج ، ولكن ليس مجرد "Spellow" أو "تغيير بعض الحقول بشكل عرضي". ولكن إذا حدث هذا بشكل متكرر ، فهذا عادةً ما يكون برمجة وأسلوبًا سيئًا ، لأنه عادة ما يكون واجهة برمجة تطبيقات شائعة "للتحدث" مع الأشياء.
كرر ، الغرض الأساسي من التغليف هو تجريده من مستخدم البيانات المساعدة ، بدلاً من منع المتسللين من إخفاء البيانات. أكثر خطورة ، لا يتمثل التغليف في استخدام Private لتعديل البيانات لتحقيق الغرض من أمان البرامج.
تغليف الكائنات المساعد (المحلي) ، نحن نقدم جدوى للتغيرات السلوكية في الواجهات العامة بأقل تكلفة وتوطين وتغييرات تنبؤية ، وهو بالضبط الغرض من التغليف.
بالإضافة إلى ذلك ، فإن الغرض المهم من طريقة Setter هو تجريد الحسابات المعقدة. على سبيل المثال ، element.innerhtml setter - عبارة ملخص - "HTML داخل هذا العنصر أصبح الآن كما يلي" ، وستكون وظيفة setter في سمة innerhtml يصعب حسابها والتحقق منها. في هذه الحالة ، تتضمن المشكلة في الغالب التجريد ، ولكن يمكن أن يحدث التغليف.
لا يرتبط مفهوم التغليف فقط بـ OOP. على سبيل المثال ، قد تكون وظيفة بسيطة تغلف جميع أنواع الحسابات بحيث تكون مجردة (لا حاجة لإعلام المستخدم ، على سبيل المثال كيف يتم تنفيذ وظيفة Math.round (......) ، يدعو المستخدم ببساطة). إنه تغليف ، لاحظ أنني لم أقل أنه "خاص ومحمي وعامة".
لا يحدد الإصدار الحالي من مواصفات ECMASCRIPT المعدلات الخاصة والمحمية والعامة.
ومع ذلك ، في الممارسة العملية ، من الممكن رؤية شيء يسمى "تقليد تغليف JS". بشكل عام ، يتم استخدام الغرض من هذا السياق (كقاعدة عامة ، المُنشئ نفسه). لسوء الحظ ، يمكن القيام بتنفيذ هذا "التقليد" في كثير من الأحيان ، ويمكن للمبرمجين إنتاج كيانات غير متكافئة من غيرها لتعيين "طريقة getter/setter" (أقول ذلك مرة أخرى ، هذا خطأ):
نسخة الكود كما يلي:
وظيفة A () {
var _a ؛ // "الخاص" أ
this.geta = function _geta () {
العودة _A ؛
} ؛
this.seta = function _seta (a) {
_a = a ؛
} ؛
}
var a = new a () ؛
A.Seta (10) ؛
تنبيه (A._A) ؛ // غير محدد ، "خاص"
تنبيه (A.Geta ()) ؛ // 10
لذلك ، يدرك الجميع أنه لكل كائن تم إنشاؤه ، يتم إنشاء طريقة GETA/SETA أيضًا ، وهو أيضًا سبب زيادة الذاكرة (مقارنة بتعريف النموذج الأولي). على الرغم من أنه ، من الناحية النظرية ، يمكن تحسين الكائن في الحالة الأولى.
بالإضافة إلى ذلك ، غالبًا ما تذكر بعض مقالات JavaScript مفهوم "الطريقة الخاصة". ملاحظة: لا يحدد معيار ECMA-262-3 أي مفهوم لـ "الطريقة الخاصة".
ومع ذلك ، في بعض الحالات ، يمكن إنشاءها في المُنشئ لأن JS هي لغة أيديولوجية - الكائنات قابلة للتغيير تمامًا ولها خصائص فريدة (في ظل ظروف معينة في المنشئ ، يمكن لبعض الكائنات الحصول على طرق إضافية ، بينما لا يمكن للآخرين).
بالإضافة إلى ذلك ، في JavaScript ، إذا كان التغليف لا يزال يسيء تفسيره كوسيلة لمنع المتسللين الخبيثين من كتابة قيم معينة تلقائيًا بدلاً من استخدام طريقة Setter ، فإن ما يسمى "Hidden" و "Private" ليس "مخفيًا". يمكن أن تحصل بعض التطبيقات على قيم على سلسلة النطاق ذات الصلة (وجميع الكائنات المتغيرة المقابلة) عن طريق استدعاء سياق وظيفة eval (والتي يمكن اختبارها على SpidermonKey 1.7).
نسخة الكود كما يلي:
eval ('_ a = 100' ، a.geta) ؛ // أو A.Seta ، لأن "_A" طرق على [[النطاق]]
A.Geta () ؛ // 100
بدلاً من ذلك ، في التنفيذ ، السماح بالوصول المباشر إلى الكائن النشط (مثل وحيد القرن) ، من خلال الوصول إلى الخواص المقابلة للكائن ، يمكن تغيير قيمة المتغير الداخلي:
نسخة الكود كما يلي:
// وحيد القرن
var foo = (function () {
var x = 10 ؛ // "خاص"
وظيفة الإرجاع () {
طباعة (x) ؛
} ؛
}) () ؛
foo () ؛ // 10
foo .__ parent __. x = 20 ؛
foo () ؛ // 20
في بعض الأحيان ، في JavaScript ، يتم تحقيق الغرض من البيانات "الخاصة" و "المحمية" من خلال المتغيرات الساحرة (ولكن بالمقارنة مع Python ، هذه مجرد مواصفات تسمية):
var _myprivatedata = 'testString' ؛
غالبًا ما يتم استخدامه في إرفاق سياق التنفيذ بأقواس ، ولكن بالنسبة للبيانات المساعدة الحقيقية ، ليس له أي ارتباط مباشر مع الكائنات ، وهو مناسب للتجريد من واجهات برمجة التطبيقات الخارجية:
نسخة الكود كما يلي:
(وظيفة () {
// تهيئة السياق
}) () ؛
ميراث متعدد
تعدد الأداء هو السكر النحوي مريح للغاية لإعادة استخدام الكود (إذا استطعنا أن نرث فئة واحدة في وقت واحد ، فلماذا لا نرث 10 في وقت واحد؟). ومع ذلك ، نظرًا لبعض أوجه القصور في الميراث المتعدد ، لم تصبح شائعة في التنفيذ.
لا يدعم ECMASCRIPT الوراثة المتعددة (على سبيل المثال ، يمكن استخدام كائن واحد فقط كنموذج أولي مباشر) ، على الرغم من أن لغات الأسلوب الذاتي لأسلافها لها مثل هذه القدرات. ولكن في بعض التطبيقات (مثل SpidermonKey) باستخدام __nosuchmethod__ يمكن استخدامها لإدارة الجدولة والفوض لاستبدال سلاسل النموذج الأولي.
Mixins
Mixins هي وسيلة مريحة لإعادة استخدام الكود. وقد أوصى Mixins كبديل للميراث المتعدد. يمكن خلط جميع هذه العناصر المستقلة مع أي كائن لتوسيع وظائفها (بحيث يمكن للكائنات أيضًا خلط Mixins متعددة). لا تحدد مواصفات ECMA-262-3 مفهوم "mixins" ، ولكن وفقًا لتعريف Mixins و ECMASCript له كائنات قابلة للتغيير ديناميكية ، لذلك لا توجد عقبة أمام توسيع الميزات باستخدام Mixins.
أمثلة نموذجية:
نسخة الكود كما يلي:
// المساعد لزيادة
Object.Extend = Function (الوجهة ، المصدر) {
لـ (خاصية في المصدر) if (source.hasownproperty (property)) {
الوجهة [الخاصية] = المصدر [خاصية] ؛
}
وجهة العودة
} ؛
var x = {a: 10 ، b: 20} ؛
var y = {c: 30 ، d: 40} ؛
Object.Extend (x ، y) ؛ // امزج y في x
التنبيه ([XA ، XB ، XC ، XD]) ؛ 10 ، 20 ، 30 ، 40
يرجى ملاحظة أنني أخذت هذه التعريفات ("Mixin" ، "Mix") في عروض الأسعار المذكورة في ECMA-262-3. لا يوجد مثل هذا المفهوم في المواصفات ، وهو ليس مزيجًا بل وسيلة شائعة الاستخدام لتوسيع الكائنات من خلال ميزات جديدة. (يتم تعريف مفهوم Mixins في Ruby رسميًا. يخلق Mixin إشارة إلى الوحدة النمطية بدلاً من ببساطة نسخ جميع سمات الوحدة النمطية إلى وحدة أخرى - في الواقع: إنشاء كائن إضافي (النموذج الأولي) للفضول).
سمات
تحتوي السمات والمزيجات على مفاهيم مماثلة ، ولكن لديها العديد من الوظائف (بحكم التعريف ، لأنه يمكن تطبيق mixins بحيث لا يمكن أن تحتوي على حالات لأن لديها القدرة على التسبب في تعارضات التسمية). وفقًا لـ ECMASCRIPT ، تتبع السمات والمزيج نفس المبادئ ، وبالتالي فإن المواصفات لا تحدد مفهوم "السمات".
واجهة
تشبه الواجهات التي يتم تنفيذها في بعض عفوا المزيج والسمات. ومع ذلك ، مقارنة مع Mixins والسمات ، تفرض واجهة فئة التنفيذ لتنفيذ سلوك توقيع الطريقة.
يمكن اعتبار الواجهات فصولًا مجردة. ومع ذلك ، بالمقارنة مع الطبقات المجردة (يمكن أن تنفذ الأساليب في الفئات التجريدية جزءًا منها فقط ، ولا يزال الجزء الآخر محددًا على أنه توقيعات) ، يمكن أن يكون الميراث فقط فئة قاعدة ميراث واحدة ، ولكن يمكن أن يرث واجهات متعددة. هذا هو السبب في أن واجهات (متعددة مختلطة) يمكن اعتبارها بديلاً للميراث المتعدد.
لا يحدد معيار ECMA-262-3 مفهوم "الواجهة" ولا مفهوم "الطبقة التجريدية". ومع ذلك ، كتقليد ، يمكن تنفيذها بواسطة كائن طريقة "فارغة" (أو استثناء يتم إلقاؤه بطريقة فارغة ، يخبر المطور أن هذه الطريقة تحتاج إلى تنفيذ).
مزيج الكائن
مزيج الكائن هو أيضًا أحد تقنيات إعادة استخدام التعليمات البرمجية الديناميكية. تختلف مجموعات الكائنات عن ميراث المرونة العالية ، والتي تنفذ مندوب ديناميكي ومتغير. وهذا يعتمد أيضًا على النموذج الأولي الرئيسي. بالإضافة إلى النماذج الأولية للتبديل الديناميكي ، يمكن أن يكون الكائن كائنًا مجمعًا للفوضى (قم بإنشاء مجموعة كنتيجة - التجميع) ، وإرسال الرسائل إلى الكائن والفوض إلى المندوب. يمكن أن يكون هذا أكثر من مندوبين ، لأن طبيعته الديناميكية تحدد أنه يمكن تغييره في وقت التشغيل.
مثال __nosuchmethod__ المذكور مثل هذا ، ولكن يتيح لنا أيضًا إظهار كيفية استخدام المندوبين بشكل صريح:
على سبيل المثال:
نسخة الكود كما يلي:
var _delegate = {
Foo: Function () {
ALERT ('_ depegate.foo') ؛
}
} ؛
var acmgregate = {
مندوب: _delegate ،
Foo: Function () {
إرجاع this.delegate.foo.call (هذا) ؛
}
} ؛
compregate.foo () ؛ // devoo.foo
compregate.delegate = {
Foo: Function () {
ALERT ('FOO من New Peping') ؛
}
} ؛
compregate.foo () ؛ // فو من نيو مندوب
تسمى علاقة الكائن هذه "HAS-A" ، في حين أن التكامل هو علاقة "IS-A".
نظرًا لعدم وجود مجموعات عرض (المرونة مقارنة بالميراث) ، فمن الجيد أيضًا إضافة التعليمات البرمجية الوسيطة.
ميزات AOP
كدالة موجهة نحو الجانب ، يمكن استخدام ديكور الوظائف. لا تحتوي مواصفات ECMA-262-3 على مفهوم محدد بوضوح لـ "ديكورات الوظائف" (على عكس Python ، يتم تعريف الكلمة رسميًا في Python). ومع ذلك ، فإن الوظائف ذات المعلمات الوظيفية تزيين وتفعيل في بعض النواحي (من خلال تطبيق ما يسمى بالاقتراحات):
أبسط مثال على الديكور:
نسخة الكود كما يلي:
وظيفة checkDecorator (OriginalFunction) {
وظيفة الإرجاع () {
if (foobar! = 'test') {
تنبيه ('معلمة خاطئة') ؛
العودة كاذبة
}
إرجاع OriginalFunction () ؛
} ؛
}
اختبار الوظيفة () {
تنبيه ('وظيفة الاختبار') ؛
}
var testWithCheck = checkDecorator (test) ؛
var foobar = false ؛
امتحان()؛ // "وظيفة الاختبار"
testwithcheck () ؛ // "معلمة خاطئة"
foobar = 'test' ؛
امتحان()؛ // "وظيفة الاختبار"
testwithcheck () ؛ // "وظيفة الاختبار"
ختاماً
في هذه المقالة ، قمنا بفرز مقدمة OOP (آمل أن تكون هذه المعلومات مفيدة لك) ، وفي الفصل التالي ، سنستمر في تنفيذ ECMASCRIPT في البرمجة الموجهة للكائنات.