Prinsip Hollywood (IoC)

Pada artikel ini, saya akan mencoba berbicara tentang prinsip desain yang disebut Inversion of Control / IOC, juga disebut Prinsip Hollywood. Saya akan menunjukkan bagaimana ini berhubungan dengan prinsip penggantian Barbara Liskovo (LSP) , serta berkontribusi pada perang suci yang privat vs yang dilindungi.



Sebagai kata pengantar, saya ingin mengatakan beberapa kata tentang diri saya. Saya seorang insinyur perangkat lunak dengan pelatihan, saya telah bekerja di industri TI selama lebih dari 10 tahun dan baru-baru ini saya gemar menulis artikel profesional tematik. Beberapa dari mereka berhasil. Sebelumnya, saya menerbitkan di sumber lain, sayangnya, tidak dapat diakses di Rusia (salam untuk Roskomnadzor). Jika seseorang ingin mengenal mereka, Anda tahu apa yang harus dilakukan.

Semua contoh kode, seperti biasa, disajikan dalam artikel dengan pseudo-code bergaya sebagai "hated php".

Tugas awal


Untuk membuatnya lebih cepat dan lebih mudah dipahami, kami segera melanjutkan ke contoh. Dari bagian penjualan saya ingin melihat metrik: berapa banyak uang yang kita hasilkan setiap bulan, setiap hari, setiap jam.

Kami memecahkan masalah ini dengan bantuan tiga tim yang dijalankan secara berkala sesuai jadwal:

  • PerintahLaporan Bulanan
  • DailyReportCommand
  • Perintah JamlyRerort

Kami akan membutuhkan antarmuka:

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); } 

Kami menulis laporan tim (yang terakhir dihilangkan, sebagai latihan praktis bagi mereka yang ingin memahami dan berlatih dengan baik, tulis sendiri):

 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 } 

Dan kita melihat bahwa kode metode calculTotals () akan persis sama dalam semua kasus. Hal pertama yang terlintas dalam pikiran adalah menempatkan kode duplikat di kelas abstrak umum. Seperti ini:



 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 } 

Metode calculTotals () adalah bagian dari mekanisme internal kelas kami. Kami dengan hati-hati menutupnya, karena seharusnya tidak dipanggil oleh pelanggan eksternal - kami tidak sedang merancang untuk ini. Kami menyatakan metode ini dilindungi, karena Kami berencana untuk memanggilnya ahli waris - ini adalah tujuan kami. Jelas, kelas abstrak seperti itu sangat mirip dengan sesuatu seperti perpustakaan - hanya menyediakan beberapa metode (untuk para ahli php: yaitu, ia bekerja seperti Trait).

Rahasia kelas abstrak


Sudah waktunya untuk mengambil istirahat dari contoh dan mengingat tujuan dari kelas abstrak:

Kelas abstrak merangkum mekanisme umum, sementara pada saat yang sama memungkinkan pewaris untuk menerapkan perilaku khusus mereka sendiri.

Abstraksi (lat. Abstractio - distraksi) adalah gangguan dari detail dan generalisasi. Saat ini, kelas AbstractReportCommand hanya menggeneralisasi penghitungan uang untuk semua laporan. Tetapi kita dapat membuat abstraksi kita lebih efisien dengan menggunakan prinsip Hollywood, yang terdengar seperti ini:

"Jangan panggil kami, kami akan memanggilmu sendiri"

Untuk melihat bagaimana ini bekerja, mari masukkan AbstractReportCommand mekanisme pelaporan umum:



 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 } 

Apa yang kita lakukan Tidak ada keturunan dari kelas abstrak yang berlaku untuk mekanisme umum (jangan panggil kami). Sebaliknya, abstraksi memberikan ahli warisnya skema fungsi umum dan mengharuskan mereka untuk menerapkan fitur perilaku tertentu, hanya menggunakan hasilnya (kami akan menantang Anda).

Tapi bagaimana dengan IoC yang dijanjikan, LSP, private vs protected?


Jadi apa hubungan Inversion of control dengan itu? Dari mana nama ini berasal? Sangat sederhana: pertama-tama kita mengatur urutan panggilan langsung dalam implementasi akhir, mengendalikan apa yang akan dilakukan dan kapan. Dan kemudian, kami mentransfer logika ini ke abstraksi umum. Sekarang abstraksi mengontrol apa dan kapan akan dipanggil, dan implementasi cukup patuhi ini. Artinya, kami membalikkan kontrol.

Untuk memperbaiki perilaku ini dan menghindari masalah dengan prinsip substitusi Barbara Liskov (LSP) , Anda bisa menutup metode createReport () dengan memasukkan final dalam deklarasi metode. Bagaimanapun, semua orang tahu bahwa LSP secara langsung terkait dengan warisan.

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

Kemudian semua keturunan kelas AbstractReportCommand menjadi bawahan kaku untuk satu logika yang tidak dapat didefinisikan ulang. Disiplin besi, ketertiban, masa depan yang cerah.

Untuk alasan yang sama, keuntungan pribadi lebih terlindungi menjadi jelas. Segala sesuatu yang berkaitan dengan mekanisme fungsi umum harus ditransfer dalam kelas abstrak dan tidak dapat diakses untuk redefinisi - pribadi. Semua yang perlu didefinisikan ulang / diimplementasikan dalam kasus khusus dilindungi secara abstrak. Metode apa pun dirancang untuk tujuan tertentu. Dan jika Anda tidak tahu ruang lingkup seperti apa yang harus ditetapkan untuk suatu metode, itu artinya Anda tidak tahu mengapa Anda membuatnya. Desain ini layak direvisi.

Kesimpulan


Konstruksi kelas abstrak selalu lebih disukai dengan penggunaan Pembalikan kontrol, sejak itu memungkinkan Anda untuk menggunakan ide abstraksi sepenuhnya. Tetapi penggunaan kelas abstrak sebagai pustaka dalam beberapa kasus juga dapat dibenarkan.

Jika Anda melihat lebih luas, maka konfrontasi kota kecil kami antara prinsip Hollywood dan kelas perpustakaan abstrak berubah menjadi perselisihan: kerangka kerja (IoC dewasa) vs perpustakaan. Tidak ada gunanya membuktikan mana yang lebih baik - masing-masing dibuat untuk tujuan tertentu. Satu-satunya hal yang penting adalah penciptaan struktur semacam itu secara sadar.

Terima kasih kepada semua orang yang membaca dengan seksama dari awal hingga akhir - Anda adalah pembaca favorit saya.

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


All Articles