S.O.L.I.D. لتقف على:
- S — مبدأ المسؤولية الفردية
- O — مبدأ مفتوح مغلق
- L — مبدأ استبدال ليسكوف
- I — مبدأ الفصل البيني
- D — مبدأ انعكاس التبعية
# مبدأ المسؤولية الواحدة
يجب أن يكون للصف سبب واحد فقط للتغيير، وهذا يعني أن الصف يجب أن يكون له وظيفة واحدة فقط.
على سبيل المثال، نقول لدينا بعض الأشكال وأردنا أن نجمل جميع مناطق الأشكال. حسنا هذا هو حق بسيط جدا؟
دائرة const = (نصف قطر) = > {
const proto = {
النوع: 'دائرة'،
التعليمات البرمجيه
}
إرجاع Object.assign(Object.create(proto)، {radius})
}
مربع const = (طول) = > {
const proto = {
النوع: 'مربع'،
التعليمات البرمجيه
}
إرجاع Object.assign(Object.create(proto)، {طول})
}
أولا، نحن إنشاء وظائف المصنع الأشكال لدينا وإعداد المعلمات المطلوبة.
ما هي وظيفة المصنع؟
في جافا سكريبت، يمكن لأي وظيفة إرجاع كائن جديد. عندما لا تكون دالة منشئ أو فئة، فإنها تسمى وظيفة المصنع. لماذا لاستخدام وظائف المصنع، وهذه المادة توفر تفسيرا جيدا وهذا الفيديو أيضا شرح واضح جدا
المقبل، ونحن نمضي قدما من خلال خلق وظيفة مصنع المنطقةCalculator ومن ثم كتابة ما يصل منطقنا لتلخيص المنطقة من جميع الأشكال المقدمة.
منطقة constCalculator = (ق) = > {
const proto = {
المجموع() {
منطق إلى مجموع
},
الإخراج () {
العودة ' <h1>مجموع مساحات الأشكال المتوفرة:
${هذا.sum()} </h1>}
}
إرجاع Object.assign(Object.create(proto)، {الأشكال: s})
}
لاستخدام وظيفة المصنع areaCalculator ، ونحن ببساطة استدعاء وظيفة وتمرير في مجموعة من الأشكال ، وعرض الإخراج في الجزء السفلي من الصفحة.
أشكال const = [
دائرة(2),
مربع(5)،
مربع(6)
]
مناطق const = areaCalculator(الأشكال)
وحدة التحكم.log(areas.output())
المشكلة مع أسلوب الإخراج هو أن المنطقةCalculator يعالج منطق إخراج البيانات. لذلك ، ماذا لو أراد المستخدم لإخراج البيانات مثل json أو شيء آخر؟
كل من المنطق سيتم التعامل معها من قبل وظيفة مصنع المنطقةCalculator، وهذا هو ما ‘مبدأ المسؤولية واحدة’ يعبس ضد; يجب أن الدالة areaCalculatorfactory مجموع فقط المناطق من الأشكال المقدمة، فإنه لا ينبغي أن الرعاية ما إذا كان المستخدم يريد JSON أو HTML.
لذلك، لإصلاح هذا يمكنك إنشاء وظيفة مصنع SumCalculatorOutputter واستخدام هذا للتعامل مع أي منطق تحتاج على كيفية عرض مناطق مجموع جميع الأشكال المقدمة.
وظيفة مصنع sumCalculatorOutputter من شأنه أن يعمل مثل هذا:
أشكال const = [
دائرة(2),
مربع(5)،
مربع(6)
]
مناطق const = areaCalculator(الأشكال)
const الناتج = sumCalculatorOputter(المناطق)
وحدة التحكم.log(الإخراج. JSON())
وحدة التحكم.log(الإخراج. هامل())
وحدة التحكم.log(الإخراج. HTML())
وحدة التحكم.log(الإخراج. JADE())
الآن ، أيا كان المنطق الذي تحتاجه لإخراج البيانات للمستخدمين يتم التعامل معها الآن من قبل وظيفة مصنع sumCalculatorOutputter.
# مبدأ مفتوح مغلق
يجب أن تكون الكائنات أو الكيانات مفتوحة للامتداد، ولكن مغلقة للتعديل.
فتح للامتداد يعني أننا يجب أن تكون قادرة على إضافة ميزات جديدة أو مكونات إلى التطبيق دون كسر التعليمات البرمجية الموجودة.
مغلق للتعديل يعني أننا لا ينبغي أن أعرض كسر التغييرات على وظيفة القائمة، لأن ذلك من شأنه أن يجبرك على إعادة بناء الكثير من التعليمات البرمجية الموجودة – اريك إليوت
وبعبارة أبسط، يعني أن وظيفة فئة أو مصنع في حالتنا، ينبغي أن تكون قابلة للتمديد بسهولة دون تعديل فئة أو وظيفة نفسها. دعونا ننظر في وظيفة مصنع المنطقةCalculator، وخاصة انها طريقة المبلغ.
مجموع () {
منطقة cons[]t =
لـ (شكل هذا.الأشكال) {
إذا (shape.type === 'مربع') {
area.push(Math.pow(shape.length, 2)
} آخر إذا (shape.type === 'دائرة') {
area.push(Math.PI * Math.pow (shape.length، 2)
}
}
منطقة العودة.reduce((v, c) = > c += v, 0)
}
إذا أردنا أن تكون طريقة المبلغ قادرة على جمع مناطق المزيد من الأشكال ، سيتعين علينا إضافة المزيد إذا / آخر كتل وهذا يتعارض مع مبدأ مفتوح.
طريقة يمكننا أن نجعل هذا الأسلوب المبلغ أفضل هو إزالة منطق لحساب مساحة كل شكل من طريقة المبلغ وإرفاقه لوظائف المصنع الشكل.
مربع const = (طول) = > {
const proto = {
النوع: 'مربع'،
المنطقة () {
عودة Math.pow (this.length، 2)
}
}
إرجاع Object.assign(Object.create(proto)، {طول})
}
وينبغي أن يتم الشيء نفسه لوظيفة المصنع دائرة، ينبغي إضافة areamethod. الآن، لحساب مجموع أي شكل المقدمة يجب أن تكون بسيطة مثل:
المجموع() {
منطقة con[]st =
لـ (شكل هذا.الأشكال) {
area.push(shape.area())
}
منطقة العودة.reduce((v, c) = > c += v, 0)
}
الآن يمكننا إنشاء فئة شكل آخر وتمريره في عند حساب المجموع دون كسر التعليمات البرمجية لدينا. ومع ذلك، الآن تنشأ مشكلة أخرى، كيف يمكننا أن نعرف أن الكائن الذي تم تمريره في المنطقةCalculator هو في الواقع شكل أو إذا كان الشكل لديه أسلوب مسماة منطقة ؟
الترميز إلى واجهة هو جزء لا يتجزأ من S.O.L.I.D. ، على سبيل المثال السريع هو أننا نخلق واجهة ، أن كل شكل ينفذ.
منذ جافا سكريبت ليس لديها واجهات، وانا ذاهب لتظهر لك، وكيف سيتم تحقيق ذلك في TypeScript، منذ نماذج TypeScript الكلاسيكية OOP لJavaScript، والفرق مع نقية جافا سكريبت بروتيبال OO.
واجهة شكليالواجهة {
المساحة(): عدد
}
فئة الدائرة ينفذ ShapeInterface {
السماح نصف القطر: عدد = 0
المنشئ (ص: عدد) {
هذا.نصف القطر = r
}
المساحة العامة(): الرقم {1
عودة الرياضيات. PI * MATH.pow (هذا.radius، 2)
}
}
في المثال أعلاه يوضح كيف سيتم تحقيق ذلك في TypeScript ، ولكن تحت غطاء محرك يجمع TypeScript التعليمات البرمجية إلى JavaScript النقية وفي التعليمات البرمجية المترجمة يفتقر إلى الواجهات نظرًا لأن جافا سكريبت لا يملكها.
فكيف يمكننا تحقيق ذلك، في عدم وجود واجهات؟
تكوين وظيفة لانقاذ!
أولا نخلق shapeInterface وظيفة المصنع، ونحن نتحدث عن واجهات، وسوف يكون لدينا shapeInterface تجريدها كواجهة، وذلك باستخدام تكوين وظيفة، لشرح عميق من تكوين انظر هذا الفيديو كبيرة.
const shapeالواجهة = (حالة) = > ({
النوع: 'شكلالوجه'،
المنطقة: () => state.area(state)
})
ثم نقوم بتنفيذه إلى لدينا وظيفة مصنع مربع.
مربع const = (طول) = > {
const proto = {
طول
نوع : 'مربع',
المنطقة : (args) = > Math.pow (args.length، 2)
}
أساسيات const = shapeInterface(proto)
مركب const = Object.assign ({}، الأساسيات)
إرجاع Object.assign(Object.create(مركب)، {طول})
}
ونتيجة استدعاء وظيفة مصنع مربع ستكون التالية:
const s = مربع(5)
وحدة التحكم.log ('OBJn'، ق)
وحدة التحكم.log ('بروتوn'، Object.getPrototypeOf(s))
s.area()
الاخراج
الكائنات
{ طول: 5 }
بروتو
{النوع: 'شكلالوجه'، المنطقة:[Function: area] }
25
في منطقتناCalculator مجموع الأسلوب يمكننا التحقق مما إذا كانت الأشكال provieded هي في الواقع أنواع shapeInterface ، وإلا فإننا رمي استثناء :
المجموع() {
منطقة c[]onst =
لـ (شكل هذا.الأشكال) {
إذا (Object.getPrototypeOf(الشكل).type === 'shapeInterface') {
area.push(shape.area())
} آخر {
طرح خطأ جديد ('هذا ليس كائناً شكليّةاً')
}
}
منطقة العودة.reduce((v, c) = > c += v, 0)
}
ومرة أخرى ، منذ جافا سكريبت لا يملك الدعم للواجهات مثل لغات مطبوعة المثال أعلاه يوضح كيف يمكننا محاكاته ، ولكن أكثر من محاكاة واجهات ، ما نقوم به هو استخدام الإغلاق وتكوين وظيفة إذا كنت لا تعرف ما هي إغلاق هو هذا المقال يفسر ذلك بشكل جيد للغاية وتكميلة انظر هذا الفيديو.
# Liskov استبدال مبدأ
اسمحوا q(x) تكون خاصية provable حول الكائنات من س من نوع T.ثم q(y) يجب أن يكون قابل للإثبات للكائنات y من النوع S حيث S هو نوع فرعي من T.
كل هذا هو تفيد أن كل فئة فرعية/مشتقة يجب أن تكون قابلة لاستبدال لفئة الأساس/الأصل الخاصة بهم.
بمعنى آخر، بسيطة مثل ذلك، يجب أن تتجاوز فئة فرعية أساليب الفئة الأصل بطريقة لا قطع وظيفة من وجهة نظر العميل.
لا تزال الاستفادة من منطقتناCalculator وظيفة المصنع، ويقول لدينا volumeCalculator وظيفة المصنع التي تمتد في مجالCalculator وظيفة المصنع، وفي حالتنا لتوسيع كائن دون كسر التغييرات في ES6 نفعل ذلك باستخدام Object.assign(
) و Object.getPrototypeOf()
:
حجم constCalculator = (ق) = > {
const proto = {
النوع: 'حجمCalculator'
}
منطقة constCalProto = Object.getPrototypeOf(منطقةCalculator())
وراثة const = Object.assign ({}, areaCalProto, proto)
إرجاع Object.assign(Object.create(ترث)، {الأشكال: s})
}
# مبدأ الفصل البيني
لا يجب أبداً إجبار العميل على تطبيق واجهة لا يستخدمها أو لا يجب إجبار العملاء على الاعتماد على الطرق التي لا يستخدمونها.
استمرارا لدينا أشكال المثال، ونحن نعلم أن لدينا أيضا الأشكال الصلبة، وذلك منذ كنا نريد أيضا لحساب حجم الشكل، يمكننا إضافة عقد آخر إلى shapeInterface:
const shapeالواجهة = (حالة) = > ({
النوع: 'شكلالوجه'،
المساحة: () = > state.area(state)،
وحدة التخزين: () = > الحالة.volume(حالة)
})
يجب أن أي شكل نقوم بإنشاء implemet أسلوب وحدة التخزين، ولكن نحن نعلم أن المربعات هي أشكال مسطحة وأنها لا تملك وحدات التخزين، لذلك هذه الواجهة سوف تجبر الدالة المصنع مربع لتنفيذ أسلوب أنه لا يوجد لديه استخدام.
واجهة مبدأ الفصل يقول لا لهذا، بدلا من ذلك يمكن أن تخلق واجهة أخرى تسمى solidShapeInterface الذي لديه عقد حجم والأشكال الصلبة مثل مكعبات الخ يمكن تنفيذ هذه الواجهة.
const shapeالواجهة = (حالة) = > ({
النوع: 'شكلالوجه'،
المنطقة: () => state.area(state)
})
شكل صلب constالأحرفالمقطعة = (حالة) = > ({
النوع: 'solidShapeInterface',
وحدة التخزين: () = > الحالة.volume(حالة)
})
const cubo = (طول) = > {
const proto = {
طول
نوع : 'كوبو',
المساحة: (args) = > Math.pow (args.length، 2)،
المجلد : (args) = > Math.pow (args.length ، 3)
}
أساسيات const = shapeInterface(proto)
const المعقدة = solidShapeالوجه(بروتو)
مركب const = Object.assign ({}، الأساسيات، المعقدة)
إرجاع Object.assign(Object.create(مركب)، {طول})
}
هذا هو أفضل بكثير النهج ، ولكن pitfall لمشاهدة ل عندما لحساب مجموع للشكل ، بدلا من استخدام shapeInterface أو SolidShapeInterface.
يمكنك إنشاء واجهة أخرى ، وربما إدارةShapeInterface ، وتنفيذها على كل من الأشكال المسطحة والصلبة ، وهذه هي الطريقة التي يمكن أن نرى بسهولة أن لديها واجهة برمجة تطبيقات واحدة لإدارة الأشكال ، على سبيل المثال :
const إدارة الشكلين = (fn) = > ({}
النوع: 'إدارة شكليّة خطّ',
حساب: () = > fn()
})
دائرة const = (نصف قطر) = > {
const proto = {
Radius
النوع: 'دائرة'،
المنطقة : (args) = > Math.PI * Math.pow (args.radius ، 2)
}
أساسيات const = shapeInterface(proto)
const abstraccion = إدارة الخط الخطيالوجه(() = > الأساسيات.area())
مركب const = Object.assign ({}، الأساسيات، abstraccion)
إرجاع Object.assign(Object.create(مركب)، {نصف قطر})
}
const cubo = (طول) = > {
const proto = {
طول
نوع : 'كوبو',
المساحة: (args) = > Math.pow (args.length، 2)،
المجلد : (args) = > Math.pow (args.length ، 3)
}
أساسيات const = shapeInterface(proto)
const المعقدة = solidShapeالوجه(بروتو)
const abstraccion = إدارةShapeInterface(
() = > الأساسيات.area() + complex.volume()
)
مركب const = Object.assign ({}، الأساسيات، abstraccion)
إرجاع Object.assign(Object.create(مركب)، {طول})
}
كما ترون حتى الآن، ما كنا نفعله للواجهات في وظائف مصنع جافا سكريتاري لتكوين وظيفة.
وهنا، مع إدارةShapeInterface ما نقوم به هو تجريد مرة أخرى وظيفة حساب، ما نقوم به هنا وفي واجهات أخرى (إذا يمكننا أن نسميها واجهات)، ونحن نستخدم “وظائف من أجل عالية” لتحقيق التجريدات.
إذا كنت لا تعرف ما هي وظيفة النظام العالي هو يمكنك الذهاب والتحقق من هذا الفيديو.
# مبدأ انعكاس التبعية
يجب أن تعتمد الكيانات على التجريدات وليس على التجسيدات. فإنه ينص على أن وحدة مستوى عال يجب أن لا تعتمد على وحدة المستوى المنخفض، ولكن ينبغي أن تعتمد على تجريدات.
كلغة ديناميكية، لا تتطلب جافا سكريبت استخدام التجريدات لتسهيل الفصل. لذلك ، فإن النص على أن التجريدات لا ينبغي أن تعتمد على التفاصيل ليست ذات صلة خاصة لتطبيقات جافا سكريبت. ومع ذلك، فإن النص على أن الوحدات النمطية عالية المستوى لا ينبغي أن تعتمد على وحدات المستوى المنخفض هو، مع ذلك، ذات الصلة.
من وجهة نظر وظيفية، يمكن حل هذه الحاويات ومفاهيم الحقن مع وظيفة بسيطة من أجل أعلى، أو ثقب في نمط نوع الأوسط التي بنيت الحق في اللغة.
كيف يرتبط عكس التبعية بوظائف ذات ترتيب أعلى؟ هو سؤال طرح في stackExchange إذا كنت تريد تفسيرا عميقا.
قد يبدو هذا منتفخًا ، ولكن من السهل فهم ذلك. هذا المبدأ يسمح للفصل.
ونحن قد جعلت من قبل، يتيح مراجعة التعليمات البرمجية لدينا مع إدارةShapeInterface وكيف يمكننا إنجاز طريقة حساب.
const إدارة الشكلين = (fn) = > ({}
النوع: 'إدارة شكليّة خطّ',
حساب: () = > fn()
})
ما إدارةالعمليةالخاصة بالمصنع يتلقى كما الوسيطة هو أعلى وظيفة ترتيب، أن decouples لكل شكل وظيفة لإنجاز المنطق المطلوب للوصول إلى الحساب النهائي، واسمحوا نرى كيف يتم ذلك في الأشكال الكائنات.
مربع const = (نصف قطر) = > {
التعليمات البرمجيه
const abstraccion = إدارة الخط الخطيالوجه(() = > الأساسيات.area())
مزيد من التعليمات البرمجية ...
}
const cubo = (طول) = > {
التعليمات البرمجيه
const abstraccion = إدارةShapeInterface(
() = > الأساسيات.area() + complex.volume()
)
مزيد من التعليمات البرمجية ...
}
للمربع ما نحتاج إلى حساب هو مجرد الحصول على مساحة الشكل، وبالنسبة لكبو، ما نحتاجه هو جمع المنطقة مع حجم وهذا هو كل شيء بحاجة لتجنب اقتران والحصول على التجريد.
Ibrahim A