سمع معظم المطورين عن مبدأ الانفتاح - الانغلاق - أحد مبادئ العم بوب SOLID. يبدو الأمر معقولًا، لكنه قد يكون ضبابيًا بعض الشيء حتى الاستخدام الأول على شيفرة "حية". الحالة الكاملة للمبدأ هي: يجب أن تكون كيانات البرمجيات (الأصناف والوحدات والدوال وما إلى ذلك) مفتوحة للتوسع، ولكنها مغلقة للتعديل.
فما الذي يعنيه ذلك حقاً؟
لقد واجهتنا مشكلة تطوير أظهرت لنا ماهية مبدأ الانفتاح المغلق. في أحد تطبيقات الويب الخاصة بنا كان لدينا نموذج يحتوي على قسمين (من بين تطبيقات أخرى):
- قنوات الطلب
- مرشحات ديناميكية
يمكن للمستخدمين إضافة العديد من الفلاتر كما يحلو لهم، ولكن هناك بعض القواعد - يعتمد توافر الفلاتر على القنوات المختارة.
قنوات الطلب: إعلانالتبادل، الرأسالمزايدة، الحجز، مرشحات (أبعاد) ديناميكية أخرى: الموقع الإلكتروني، الإعلانالوحدة، الجغرافية، الإبداعيةالحجم، الجهاز
تدور هذه المقالة في الغالب حول إعادة بناء الشيفرة، لذا سيكون هناك الكثير من مقتطفات الشيفرة أدناه. لقد حاولت تقليلها، ولكن بعض الكودات ضرورية لإظهار إعادة هيكلة التعليمات البرمجية. لست بحاجة إلى فهم كل جزء صغير من التعليمات البرمجية لفهم الفكرة الرئيسية.
كان التنفيذ الأول للمشكلة بسيطاً:
فئة ResearchFormStateUpdater {
تحديث () {
(...)
هذا._updateDynamicFilters();
}
_updateDynamicFilters () { {
$('.عامل التصفية الديناميكي').كل ((_، فلتر)) => {
$(.filter).trigger('dynamicFilter:disableWebsites', this._shouldDisableWebsitesFields());
});
}
_shouldDisableDisableWebsitesFields () { {
إرجاع هذا._shouldDisDisableDisableFields(ResearchFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANELS);
}
_shouldDisDisableFields (disablingDemandChannels) {
// هل تم التحقق من أي من disablingDemandChannels؟
}
}
ResearchFormFormStateUpdater.WEBSITE_DISABLING_DEMAND_CHANELS = ['header_bidding'، 'حجز'، 'أخرى'];
فئة ResearchDynamicFilter {
_ SetDynamicDynamicFilterDisableWebsitesEvent () {
$(this._getBody()).on('dynamicFilter:disableWebsites', (الحدث، shouldDisableWebsites) => {
// تعطيل مرشحات مواقع الويب
});
}
}
كما ترى، من المفترض أن يكون عامل تصفية الموقع الإلكتروني غير متاح لـ HEADERالعطاءات والحجز والقنوات الأخرى، لذا فهي متاحة فقط لقنوات الإعلاناتقناة التبادل.
آخر شيء يمكنك قوله عن الكود هو أنه دائم أو ثابت. لذلك لدينا المزيد من الطلبات من عملائنا مما يجعل هذه الفئات أكبر وأكثر تعقيدًا.
تطوير الميزات
تنبيه مفسد -> عندما يكون المكون مفتوحًا للتغييرات، سيكون هناك الكثير من التغييرات في الاسم في المستقبل. لن ننتبه إلى ذلك في الخطوات التالية.
إضافة فلتر آخر ل "المنتج (المنتج مخطط توفر المرشح هو نفسه الموقع الإلكتروني)
- مرشح البحث الديناميكي يجب على الفصل التحقق من بُعد آخر أثناء تعطيل/تمكين الحقول
دعنا نكبر ونضيف بعض المحوّل فوق القنوات -> "المصدر". جميع قنوات الطلب التي كانت لدينا حتى الآن موجودة في مصدر مدير الإعلانات. المصدر الجديد - SSP - لا يوجد به قنوات طلب، والمرشح الوحيد المتاح هو الموقع الإلكتروني.
القواعد:
- هناك حالتان للمصدر: مدير الإعلان، SSP.
- جميع قنوات الطلب لدينا متاحة فقط لمصدر مدير الإعلانات.
- لا توجد قنوات طلب لمصدر SSP
- "الموقع الإلكتروني" هو عامل التصفية الوحيد المتاح لمصدر SSP.
التنفيذ:
إضافة فلتر آخر ل "المنصة
القواعد:
- النظام الأساسي متاح فقط عندما يكون المصدر هو SSP
الصعوبة:
- لدينا الآن "الموقع الإلكتروني"، وهو متاح لقناة AD_EXCHANGE من مدير الإعلانات ولـ SSP ولدينا "المنصة" وهي متاحة لـ SSP ولكن ليس لمدير الإعلانات
- التبديل يمكن أن تصبح حالة النموذج خادعة ومربكة حقًا
التنفيذ بوظائف جديدة:
أقدم لكم المقتطف التالي بشكل أساسي لإظهار تعقيد الكود. لا تتردد في تركه مخفيًا.
فئة ResearchFormStateUpdater {
تحديث () {
(...)
هذا._triggerCallbacks();
}
_triggerCallbacks () { {
// اختر عمليات الاسترجاع اعتمادًا على المصدر
}
_adManagerSourceCallbacks () { {
(...)
هذا._enableDemableDemandChannels(ResearchFormStateStateUpdater.AD_MANAGER_DEMAND_CHANNELS);
هذا._updateDefateDefaultStateOfDynamicFilters();
هذا._updateAdAdAdManagerDynamicFilters()؛ هذا._updateAdManagerDynamicFilters();
}
_sspSpSourceCallbacks () { {
(...)
هذا._removeDemoveDemandChannelsActiveClassAndDisable(ResearchFormStateUpdater.AD_MANAGER_DEMAND_CHANELS);
هذا._updateDefateDefaultStateOfDynamicFilters();
}
_updateDefateDefaultStateOfDynamicFilters () {
$(".مرشح ديناميكي").كل ((_، مرشح) => {
$(.filter).trigger('dynamicFilter:enableSspFilters', this.isSourceSsp);
});
}
_updateAdateAdManagerDynamicFilters () { {
$('.dynamic-filter').every((_، فلتر) => {
$(.filter).trigger('dynamicFilter:disableWeableWebsitesAndProducts', this._areFormStateDim الأبعاد معطلة() & !this.isSourceSsp);
});
}
_shouldDisableFields (disablingDemandChannels) {
// هل تم التحقق من أي من disablingDemandChannels
}
}
ResearchFormFormStateUpdater.AD_MANAGER_DISABLING_DEMAND_CHANELS = ['header_bidding'، 'حجز'، 'أخرى'، 'ebda'];
فئة ResearchDynamicFilter {
// لم أبسط هاتين الطريقتين لإظهار تعقيد التنفيذ الحالي
_setDefaultDefaultDynamicFiltersToggleEvent () {
$(this._getBody()).on('dynamicFilter:enableSspFilters', (الحدث، shouldEnableSspOptions) => {
هذا._setDefaultDefaultFiltersOiltersOptionDisabledState(shouldEnableSspOptions);
const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
إذا (إذا كان (SelectFilterDimension === 'موقع ويب') {
هذا._toggleChosenChosenFilterDisabledState (خطأ);
} آخر إذا كان (SelectFilterDimension === 'النظام الأساسي') { {
هذا._toggleChosenChosenFilterDisabledState(!shouldEnableSspOptions);
غير ذلك {} {
هذا._toggleChosenChosenFilterDisabledState(!shouldEnableSspOptions);
}
});
}
_setDynamicDynamicFilterDisableWebsitesAndProductsEvent () { {
$(this._getBody()).on('dynamicFilter:disableWebsiteAndProducts'، (الحدث، shouldDisableWebsitesAndProducts) => {
const selectedFilterDimension = this._getFiltersDimension().find('option:selected').val();
إذا كان ($.inArray(SelectFilterDimension, ['موقع ويب'، 'منتج'])>= 0) { {
هذا._toggleChosenChosenFilterDisabledState(shouldDisableWebsitesAndProducts);
}
هذا._setMethodMetSelectSelectWebsiteAndProductOptionDisabledState(shouldDisableWebsitesAndProducts);
});
}
_toggleNonSspFilters (dimensionSelect, shouldDisisable) {
$.each(ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS, (_, option) => {
// تبديل حالة المرشح اعتمادًا على 'shouldDisDisable'
});
}
}
ResearchDynamicFilter.NON_SSP_FILTERS_OPTIONS = ['ad_unit'، 'creative_size'، 'creative_size'، 'geo'، 'device'، 'product'];
ما زلنا نستخدم بعض 'تبديل' الآلية. من الصعب حقًا تبديل 4 أذرع والوصول إلى الحالة المتوقعة والآن يجب أن يعرف DynamicFilter، أي الأبعاد ليست لمصدر ssp.
لدينا بالفعل ResearchFormStateUpdater، لماذا لا يكون مسؤولاً؟
الطلب النهائي
إضافة فلتر آخر ل "شريك العائد
هذه هي اللحظة التي قررنا فيها إعادة هيكلة تلك الفئات. القنوات والمرشحات التي يتم تحليلها هي مجرد جزء صغير من المشكلة. هناك العديد من أقسام النماذج هنا وجميعها تعاني من نفس المشكلة. يجب أن تُبطل عملية إعادة الهيكلة التي أجريناها الحاجة إلى تغيير داخل طرائق تلك الأصناف *لإضافة بعض القنوات أو الأبعاد الجديدة.
في المقتطف التالي، تركت الفئات الرئيسية كما هي تقريبًا كما هي في شيفرة الإنتاج الخاصة بنا لأوضح لك مدى سهولة فهمها الآن.
فئة ResearchFormStateUpdater {
تحديث () {
(...)
هذا._updateDynamicFilters();
}
_updateDynamicFilters () { {
هذا._toggleAllDynamicDynamicFiltersState (هذا._dynamicFiltersDynamicFiltersDimensionsToBeDisabled()));
}
_dynamicFiltersDynamicFiltersDynamicDiltersDimensionsToBeDisabled ()؛ {
إذا (this.isSourceSsp) { إرجاع ResearchFormStateUpdater.NO_SSP_FILTERS؛ }
var disabledFilters = ResearchFormStateUpdater.ONLY_SSP_FILTERS;
إذا (هذا (areDemandChannelsExceptAdxSelected) {
disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_ADX_FILTERS);
}
إرجاع المعطلة;
}
_toggleAllDynamicDynamicFiltersState (معطلةFilters) {
$('.dynamic-filter').every((_، فلتر) => {
هذا._toggleDynamicDynamicFilterState(فلتر، معطلةFilters);
});
}
_toggleDynamicDynamicFilterState (مرشح ديناميكي، معطّل، معطّل) {
$(فلتر ديناميكي).مشغل('dynamicFilter:toggleDynamicFilters'، معطلةالفلاتر);
}
}
SearchFormFormStateUpdater.NO_SSP_FILTERS = ['ad_unit'، 'creative_size'، 'geo'، 'device'، 'product'];
ResearchFormFormStateUpdater.ONLY_SSP_FILTERS = ['النظام الأساسي'];
ResearchFormFormStateUpdater.ONLY_ADX_FILTERS = ['الموقع الإلكتروني'، 'المنتج'];
فئة ResearchDynamicFilter {
_ SetDynamicDynamicFiltersToggleEvent () {
$(this._getBody()).on('dynamicFilter:toggleDynamicFilters', (حدث، تعطيل المرشحات) => {
هذا._disableFilters(disabledFilters.split('،'));
هذا._enableFilters(disabledFilters.split(','))));
});
}
_disableFilters (فلاترToDisable) {
// تعطيل الفلاترToDisable
}
_enableFilters (فلاترToDisable) { {
const const filtersToEnable = $(ResearchDynamicFilter.ALL_FILTERS).not(filtersToDisable).get();
// تمكين الفلاترToEnable
}
}
ResearchDynamicDynamicFilter.ALL_FILTERS = ['الموقع الإلكتروني'، 'الموقع الإلكتروني'، 'وحدة_إعلانية'، 'حجم_إبداعي'، 'جغرافي'، 'جهاز'، 'منتج'، 'منصة'];
لقد فعلناها! هل فعلناها؟
الآن الشيء الوحيد الذي يجب أن يعرفه 'ResearchDynamicFilter' هو قائمة بجميع الفلاتر - يبدو عادلاً. تأتي بقية المنطق والتحكم من الأعلى - بعض الطرق والثوابت العليا.
لذا دعونا نجرب هيكلنا الجديد بإضافة عامل تصفية لـ "Yield_partner":
فئة ResearchFormStateUpdater {
_dynamicFiltersDiltersDimensionsToBeDisabled () {
(...)
إذا (هذا (areDemandChannelsExceptEbdaSelected) {
disabledFilters = disabledFilters.concat(ResearchFormStateUpdater.ONLY_EBDA_FILTERS);
}
إرجاع المعطلة;
}
}
ResearchFormFormStateUpdater.NO_SSP_FILTERS = [(...)، 'yield_partner'];
ResearchFormFormStateUpdater.ONLY_EBDA_FILTERS = [(...)، 'yield_partner'];
ResearchDynamicFilter.ALL_FILTERS = [(...)، 'yield_partner']؛
كما ترى، الأمر كله يتعلق بإضافة بعض القيم إلى الثوابت وبعض الشروط الإضافية.
بفضل "مبدأ "مفتوح-مغلق" يمكننا تغيير منطق العمل الخاص بالنموذج بإضافة بعض القيم والشروط على مستوى أعلى من التجريد. لا نحتاج إلى الدخول داخل المكوّن وتغيير أي شيء. أثرت عملية إعادة الهيكلة هذه على النموذج بأكمله وكان هناك المزيد من الأقسام وجميعها تخضع لمبدأ "مفتوح-مغلق" الآن.
لم نقم بتقليل كمية التعليمات البرمجية - بل قمنا بزيادتها (قبل/بعد):
- نموذج البحثFormStateUpdater - 211/282 سطر
- مرشح البحث الديناميكي - 267/256 سطر
الأمر كله يتعلق بالمجموعة في الثوابت -> إنها واجهتنا العامة الآن، وحدة التحكم في العملية بدون عشرات المحولات.
اقرأ أيضًا: