مبدأ هوليوود (IoC)

في هذه المقالة ، سأحاول التحدث عن مبدأ التصميم المسمى Inversion of Control / IoC ، والذي يسمى أيضًا مبدأ هوليوود. سأوضح كيف يرتبط هذا بمبدأ استبدال Barbara Liskovo (LSP) ، وكذلك المساهمة في الحرب المقدسة الخاصة مقابل المحمية.



كتقديم ، أود أن أقول بضع كلمات عن نفسي. أنا مهندس برمجيات عن طريق التدريب ، لقد عملت في صناعة تكنولوجيا المعلومات منذ أكثر من 10 سنوات ، ومؤخراً كنت مولعا بكتابة مقالات مهنية موضوعية. البعض منهم كانوا ناجحين. في وقت سابق ، نشرت على مورد آخر ، لسوء الحظ ، يتعذر الوصول إليها في روسيا (تحيات إلى Roskomnadzor). إذا أراد شخص ما التعرف عليه ، فأنت تعرف ما يجب القيام به.

يتم تقديم جميع أمثلة الكود ، كالمعتاد ، في المقالة برمز زائف منمق كـ "php مكروه".

المهمة الأولية


لجعله أسرع وأكثر قابلية للفهم ، ننتقل إلى المثال على الفور. من قسم المبيعات أردت أن أرى المقاييس: كم من المال نربحه شهريًا ، يوميًا ، كل ساعة.

نحل هذه المشكلة بمساعدة ثلاثة فرق يتم تشغيلها بشكل دوري وفق جدول زمني:

  • MonthlyReportCommand
  • DailyReportCommand
  • HourlyRerortCommand

سنحتاج إلى واجهات:

interface ReportCommandInterface { public function createReport(): Money; } interface MoneyRepositoryInterface { /** @return Money[] */ public function getMoney(Period $period): array; } interface MetricRepositoryInterface { public function saveMoneyMetric(Period $period, Money $amount, string $metricType); } 

نكتب فرق التقارير (تم حذف الأخير ، كتمرين عملي لأولئك الذين يرغبون في الفهم والممارسة بشكل جيد ، اكتبه بنفسك):

 class MonthlyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'monthly income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here is calculating sum of money records } } class DailyReportCommand implements ReportCommandInterface { //lets assume constructor is already here public function createReport(): Money { $period = new Period(new DateTime('yesterday'), new DateTime('today')); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, 'daily income'); } /** @param Money[] $moneyRecords */ private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class HourlyReportCommand ... { //the same as previous two but hourly } 

ونرى أن رمز طريقة calculateTotals () سيكون هو نفسه تمامًا في جميع الحالات. أول ما يتبادر إلى الذهن هو وضع رمز مكرر في فئة مجردة مشتركة. مثل هذا:



 abstract class AbstractReportCommand { protected function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand implements ReportCommandInterface { public function createReport(): Money { //realization is here, calls calculateTotals($moneyRecords) } } class DailyReportCommand extends AbstractReportCommand implements ReportCommandInterface { //the same as previous two but daily } class HourlyReportCommand ... { //the same as previous two but hourly } 

طريقة calculateTotals () هي جزء من الآليات الداخلية لفصلنا. نحن نغلقه بحكمة ، لأنه لا ينبغي أن يطلق عليه عملاء خارجيون - نحن لا نقوم بتصميمه لهذا الغرض. نعلن هذه الطريقة محمية ، لأن نحن نخطط للاتصال به في الورثة - هذا هو هدفنا. من الواضح أن مثل هذا الفصل التجريدي يشبه إلى حد كبير ما يشبه المكتبة - فهو يوفر بعض الطرق فقط (لخبراء php: أي أنها تعمل مثل Trait).

سر الطبقات التجريدية


لقد حان الوقت لأخذ استراحة من المثال واستعادة الغرض من الطبقات المجردة:

تتضمن الطبقة التجريدية آليات عامة ، مع السماح للورثة في نفس الوقت بتنفيذ سلوكهم الخاص.

التجريد (اللات. Abstract - الهاء) هو الهاء عن التفاصيل والتعميم. في هذه اللحظة ، يعمم الفصل AbstractReportCommand فقط الأموال التي يتم حسابها لجميع التقارير. ولكن يمكننا أن نجعل تجريدنا أكثر كفاءة باستخدام مبدأ هوليوود ، والذي يبدو كالتالي:

"لا تتصل بنا ، سنتصل بك"

لنرى كيف يعمل هذا ، دعونا نضع في AbstractReportCommand آلية إبلاغ عامة:



 abstract class AbstractReportCommand implements ReportCommandInterface { /** @var MoneyRepositoryInterface */ private $moneyRepository; /** @var MetricRepositoryInterface */ private $metricRepository; //lets assume constructor is already here public function createReport(): Money { $period = $this->getPeriod(); $metricType = $this->getMetricType(); $moneyRecords = $this->moneyRepository->getMoney($period); $amount = $this->calculateTotals($moneyRecords); $this->metricRepository->saveMoneyMetric($period, $amount, $metricType); } abstract protected function getPeriod(): Period; abstract protected function getMetricType(): string; private function calculateTotals(array $moneyRecords): Money { //here calculates sum of money records } } class MonthlyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('first day of previous month'), new DateTime('last day of previous month')); } protected function getMetricType(): string { return 'monthly income'; } } class DailyReportCommand extends AbstractReportCommand { protected function getPeriod(): Period { return new Period(new DateTime('yesterday'), new DateTime('today')); } protected function getMetricType(): string { return 'daily income'; } } class HourlyReportCommand ... { //the same as previous two but hourly } 

ماذا فعلنا؟ لا ينطبق أي من أحفاد الصف التجريدي على الآليات الشائعة (لا تتصل بنا). بدلاً من ذلك ، يمنح التجريد ورثته مخططًا عامًا للوظائف ويتطلب منهم تنفيذ ميزات سلوكية معينة ، وذلك باستخدام النتائج فقط (سوف ندعوك لأنفسنا).

ولكن ماذا عن IoC الموعودة ، LSP ، الخاص مقابل المحمي؟


فما علاقة انقلاب السيطرة به؟ من أين يأتي هذا الاسم؟ بسيط للغاية: أولاً ، قمنا بتعيين تسلسل المكالمات مباشرة في التطبيقات النهائية ، والتحكم في ما سيتم القيام به ومتى. وبعد ذلك ، نقلنا هذا المنطق إلى تجريد عام. الآن ، يتحكم التجريد في ما سيتم تحديده ومتى سيتم ذلك ، والتطبيقات ببساطة تطيع هذا. وهذا هو ، ونحن مقلوب السيطرة.

لإصلاح هذا السلوك وتجنب المشكلات المتعلقة بمبدأ استبدال Barbara Liskov (LSP) ، يمكنك إغلاق الأسلوب createReport () من خلال تضمين final في تعريف الطريقة. بعد كل شيء ، يعلم الجميع أن LSP يرتبط مباشرة بالميراث.

 abstract class AbstractReportCommand implements ReportCommandInterface { final public function createReport(): Money { //bla-bla realization } ... } 

ثم تصبح جميع أحفاد الفئة AbstractReportCommand تابعة بشكل صارم لمنطق واحد لا يمكن إعادة تعريفه. الانضباط الحديد ، النظام ، مستقبل مشرق.

لنفس السبب ، تصبح ميزة الحماية الخاصة المحمية ظاهرة. يجب توصيل كل ما يتعلق بآليات التشغيل العامة في فصل تجريدي ولا يمكن إعادة تعريفه - خاص. كل ما يحتاج إلى إعادة تعريف / تنفيذها في حالات خاصة هو حماية مجردة. تم تصميم أي طرق لأغراض محددة. وإذا كنت لا تعرف نوع النطاق الذي يجب تعيينه لطريقة ما ، فهذا يعني أنك لا تعرف سبب إنشائها. هذا التصميم يستحق المراجعة.

النتائج


يفضل بناء الطبقات المجردة دائمًا باستخدام Inversion of control ، منذ ذلك الحين يسمح لك باستخدام فكرة التجريد على أكمل وجه. لكن استخدام الفصول المجردة كمكتبات في بعض الحالات يمكن تبريره أيضًا.

إذا نظرت على نطاق أوسع ، فإن مواجهتنا للمدينة الصغيرة بين مبدأ هوليوود وفئة المكتبة المجردة تتحول إلى نزاع: إطار (IoC للبالغين) مقابل مكتبة. لا يوجد أي فائدة في إثبات أيهما أفضل - يتم إنشاء كل منهما لغرض محدد. الشيء الوحيد المهم هو خلق واعية لمثل هذه الهياكل.

شكرا لجميع الذين قرأوا بعناية من البداية إلى النهاية - أنت القراء المفضلين لدي.

Source: https://habr.com/ru/post/ar477700/


All Articles