OOP, "Holy Trinity" dan SOLID: sedikit pengetahuan tentang mereka

Wajib masuk


Saya tidak dapat menjamin bahwa penafsiran istilah dan prinsip yang diterima secara umum yang ditetapkan di sini bertepatan dengan apa yang disajikan profesor California dalam artikel ilmiah yang kuat di paruh kedua abad terakhir. Saya tidak dapat menjamin bahwa interpretasi saya sepenuhnya dibagikan atau dibagikan oleh sebagian besar profesional TI di industri atau komunitas akademik. Saya bahkan tidak dapat menjamin bahwa interpretasi saya akan membantu Anda dalam wawancara, meskipun saya berasumsi bahwa itu akan berguna.


Tetapi saya menjamin bahwa jika kurangnya pemahaman diganti oleh interpretasi saya dan mulai menerapkannya, maka kode yang Anda tulis akan lebih mudah untuk dipertahankan dan dimodifikasi. Saya juga mengerti betul bahwa dalam komentar saya akan menulis dengan marah melengkapi apa yang telah ditulis, yang akan memungkinkan untuk mengoreksi kelalaian dan inkonsistensi yang benar-benar terang-terangan.


Jaminan kecil seperti itu menimbulkan pertanyaan tentang alasan mengapa artikel itu ditulis. Saya percaya bahwa hal-hal ini harus diajarkan di mana pun mereka mengajar pemrograman, hingga pelajaran sains komputer di sekolah-sekolah dengan studi mendalam tentang hal itu. Namun demikian, bagi saya itu menjadi situasi yang menakutkan ketika saya mengetahui bahwa lawan bicaranya adalah rekan saya, dan yang telah bekerja selama beberapa tahun, tetapi tentang enkapsulasi “Saya mendengar sesuatu di sana”. Kebutuhan untuk mengumpulkan semua ini di satu tempat dan memberikan tautan ketika pertanyaan muncul sudah matang untuk waktu yang lama. Dan kemudian "proyek kesayangan" saya memberi saya cukup banyak makanan untuk dipikirkan.


Di sini mereka mungkin keberatan dengan saya bahwa terlalu dini untuk mempelajari hal-hal ini di sekolah, dan secara umum cahaya belum menyatu dalam OOP. Pertama, itu tergantung pada bagaimana Anda belajar. Kedua, 70% dari materi dalam artikel ini berlaku tidak hanya untuk OOP. Apa yang akan saya catat secara terpisah.




Singkatnya


Ini mungkin bagian yang paling sulit bagi saya. Tapi tetap saja, Anda perlu membangun basis dan menjelaskan dengan singkat apa esensi dari OOP untuk memahami mengapa enkapsulasi, polimorfisme, pewarisan, dan prinsip-prinsip PADAT memperkuatnya. Dan saya akan melakukan ini dengan berbicara tentang bagaimana Anda bahkan bisa memikirkan hal seperti itu.


Itu dimulai dengan Dijkstra, yang membuktikan bahwa algoritma apa pun dapat diekspresikan dalam tiga cara memilih perintah berikut: eksekusi linear (dalam urutan), percabangan dengan kondisi, eksekusi loop saat kondisi terpenuhi. Dengan menggunakan tiga koneksi ini, Anda dapat membuat algoritma apa pun. Selain itu, disarankan untuk menulis program, membatasi diri pada pengaturan linier dari perintah satu demi satu, percabangan dan loop. Ini telah disebut "pemrograman struktural prosedural " (terima kasih atas klarifikasi sshikov ).


Juga di sini kita perhatikan bahwa urutan perintah harus dikombinasikan ke dalam subprogram dan setiap subprogram dapat dieksekusi menggunakan satu perintah.


Selain urutan tindakan, penting bagi kami untuk melakukan tindakan. Dan mereka dilakukan pada data yang telah menjadi kebiasaan untuk disimpan dalam variabel. Variabel menyimpan data. Mereka ditafsirkan berdasarkan tipenya. Jelas untuk menggiling gigi, tapi bersabarlah sedikit, tolong.


Sejak awal, sekumpulan tipe data primitif yang kurang lebih umum terbentuk. Integer, bilangan real, variabel Boolean, array, string. Algoritma + struktur data = program, sebagaimana Nicklaus Wirth diwariskan.


Juga, sejak awal waktu, ada dalam berbagai bentuk seperti tipe data sebagai subprogram. Atau sepotong kode jika Anda mau. Beberapa mungkin mengatakan bahwa menggunakan subprogram sebagai variabel adalah hak prerogatif pemrograman fungsional. Meski begitu, kemampuan untuk membuat sepotong kode variabel bahkan di assembler. Biarkan kemungkinan ini dikurangi menjadi "well, ini nomor byte di RAM tempat subrutin ini hidup, dan kemudian perintah CALL dengan panggilan stack di gigi dan putar yang Anda bisa."


Tentu saja, beberapa jenis data sangat sedikit dan orang-orang mulai berpikir tentang menambahkan kemampuan untuk membuat jenis mereka sendiri ke berbagai PL. Salah satu variasi fitur ini adalah apa yang disebut rekaman. Berikut adalah dua contoh rekaman dalam bahasa pemrograman yang tidak ada (selanjutnya NEPL - Bahasa Pemrograman Tidak Ada):


type Name: record consisting of FirstName: String, MiddleName: String, LastName: String. type Point: record consisting of X: Double, Y: Double. 

Artinya, alih-alih menyeret sepanjang dua atau tiga variabel terkait, Anda mengelompokkannya ke dalam satu struktur dengan bidang bernama. Kemudian Anda mendeklarasikan variabel tipe Name dan merujuk ke bidang FirstName, misalnya.


Apa yang sangat berharga dalam variabel "ditingkatkan" ini untuk topik kita? Bahwa dari sini hanya ada satu langkah kecil ke OOP. Saya tidak hanya menyorot paragraf tebal untuk menunjukkan bahwa potongan kode juga dapat ditempatkan dalam variabel. Lihat bagaimana variabel berubah menjadi objek:


 type Name: class consisting of FirstName: String, MiddleName: String, LastName: String, GetFullName: subprogram with no parameters returns String. type Point: class consisting of X: Double, Y: Double, ScalarMultiply: subprogram with (Double) parameters returns Point. 

NB NEPL secara aktif berkembang dan telah mengganti kata kunci catatan dengan kelas.


Artinya, kita dapat mengakses bidang "GetFullName" dan menyebutnya . Variabel tidak hanya berisi data yang menjelaskan statusnya, tetapi juga perilaku. Dengan demikian, variabel berubah menjadi objek yang memiliki beberapa keterampilan dan keadaan. Dan kami sudah bekerja tidak hanya dengan variabel, tetapi dengan sistem kecil yang dapat diberikan perintah.


Di masa muda saya, ide ini membuat saya terpesona. Bayangkan saja, Anda dapat membuat semua jenis data . Dan Anda bekerja bukan dengan sejumlah angka, tetapi dengan objek-objek dunia yang Anda ciptakan. Tidak ada siksaan dengan array yang membosankan atau set angka yang rumit. Kami bekerja secara langsung dengan objek bertipe Pemain, Musuh, Peluru, Bos! Ya, di masa muda saya, saya ingin membuat video game.


Pada kenyataannya, semuanya ternyata tidak sesederhana itu. Dan tanpa beberapa ide "penguatan", OOP akan mengubah hidup seorang programmer menjadi neraka. Namun sebelum melanjutkan, mari berikan beberapa istilah lagi:


  • Tipe data yang "diperkuat" oleh perilaku mereka di OOP disebut kelas .
  • Variabel jenis ini disebut objek .
  • Dan rutinitas yang mendefinisikan perilaku objek disebut metode . Sebagai aturan, setiap kelas memiliki seperangkat metode sendiri, dan tidak setiap objek. Sehingga setiap objek dari kelas tertentu berperilaku seperti objek lain dari kelas yang sama. Saya akan senang mengetahui dari komentar tentang bahasa di mana segalanya berbeda.

Trinitas Suci


Itu terjadi secara historis sehingga mereka bertanya tentang hal-hal ini di wawancara. Mereka ditulis dalam buku teks bahasa OOP. Mengapa Karena jika Anda merancang program OOP tanpa memperhatikan enkapsulasi dan polimorfisme, maka Anda akan mendapatkan "peti mati, peti mati, pemakaman, tanpa ditemani." Warisan tidak begitu diperlukan, tetapi konsep ini memungkinkan Anda untuk memahami OOP dengan lebih baik dan merupakan salah satu alat utama dalam mendesain menggunakan OOP.


Enkapsulasi


Baiklah, mari kita mulai dengan definisi dari Wikipedia: "mengemas data dan fungsi menjadi satu komponen." Definisi tersebut tampak jelas, tetapi pada saat yang sama terlalu digeneralisasi. Oleh karena itu, mari kita bicara tentang mengapa ini perlu sama sekali, apa yang akan diberikan kepada kita, dan bagaimana tepatnya kita perlu mengemas data dan fungsi menjadi satu komponen.


Saya sudah menulis artikel yang berhubungan dengan enkapsulasi. Dan di sana saya benar dicela dengan fakta bahwa saya mengurangi enkapsulasi ke penyembunyian informasi, dan ini adalah hal-hal yang agak berbeda. Secara khusus, EngineerSpock menghasilkan formulasi elegan seperti perlindungan invarian . Saya mengakui kesalahan saya, dan kemudian saya akan menjelaskan mengapa saya membuatnya.


Sementara itu, saya, definisi awal dari prinsip enkapsulasi, atau jika Anda ingin, proses enkapsulasi, yang memberikan deskripsi tidak hanya tentang prinsip enkapsulasi, tetapi juga tentang apa yang seharusnya dicapai dengan prinsip:


Entitas perangkat lunak apa pun dengan status non-sepele harus diubah menjadi sistem tertutup, yang hanya dapat ditransfer dari satu kondisi yang benar ke yang lain.


Tentang bagian di mana "entitas perangkat lunak apa pun yang memiliki status non-sepele", mari kita sedikit nanti. Untuk saat ini, kita akan berbicara secara eksklusif tentang objek. Dan tentang bagian kedua dari definisi saya. Mengapa kita membutuhkan ini?


Semuanya sederhana di sini: apa yang hanya dapat ditransfer dari satu kondisi ke kondisi lain tidak dapat dilanggar. Artinya, kita perlu memastikan bahwa tidak ada benda yang bisa dipatahkan. Kedengarannya, secara halus, ambisius. Bagaimana cara mencapai ini?


Di nol, segala sesuatu yang berhubungan dengan objek harus terletak di satu tempat, di dalam batas arsitektur yang sama, katakanlah. Jika ternyata sangat musykil, saya akan mengulangi definisi dari Wikipedia: "mengemas data dan fungsi menjadi satu komponen".


Pertama, untuk memisahkan antarmuka dan implementasinya dengan jelas. Saya pikir semua rekan saya akrab dengan API singkatan. Jadi, setiap objek harus memiliki API sendiri, atau PI, jika kita ingin teliti. Apa yang mereka ciptakan, dan apa yang akan digunakan orang lain, apa yang akan mereka sebabkan. Seperti apa seharusnya? Sehingga tidak seorang pun akan berpikir untuk masuk ke dalam suatu objek dan menggunakannya secara tidak tepat. Tapi tidak lebih dari itu.


Dalam sebuah buku, sayangnya, saya tidak ingat yang mana, ini dijelaskan oleh contoh microwave. Ada tombol di situ. Pena Mereka memungkinkan Anda untuk menghangatkan makanan. Anda tidak perlu melepas microwave, dan menyolder sesuatu di sana untuk menghangatkan sup kemarin. Anda memiliki antarmuka, tombol. Letakkan piring, tekan beberapa tombol, tunggu sebentar dan bahagia.


Pikirkan saja tombol mana yang harus ditekan oleh pengguna objek Anda, dan pisahkan dari jeroan ayam itik internal. Dan jangan sekali-kali jangan menambahkan tombol ekstra! Itu yang pertama.


Kedua, hormati batas antara antarmuka dan implementasi dan buat orang lain menghormatinya. Pada prinsipnya, ide ini intuitif dan berada di antara kebijaksanaan rakyat dalam banyak bentuk. Ambil setidaknya "jika Anda mengambil keuntungan dari sesuatu yang tidak berdokumen, dan kemudian ada sesuatu yang rusak untuk Anda, Anda yang harus disalahkan." Saya pikir, dengan "jangan memutar microwave sampai berfungsi sebagaimana Anda membutuhkannya," semuanya jelas. Sekarang tentang bagaimana membuat orang lain menghormati perbatasan yang terkenal itu.


Di sinilah informasi bersembunyi datang untuk menyelamatkan. Ya, Anda selalu dapat menyetujui, bertanya, mengatur konvensi kode, menunjukkan ulasan kode bahwa ini tidak mungkin. Tetapi kemungkinan mendaki di luar perbatasan ini akan tetap ada. Ini adalah penyembunyian informasi yang datang untuk menyelamatkan.


Kita tidak dapat melintasi perbatasan yang terkenal itu jika kode kita tidak dapat belajar tentang keberadaannya dan apa yang ada di baliknya. Dan bahkan jika dia tahu, kompiler akan berpura-pura bahwa tidak ada bidang atau metode seperti itu, dan bahkan jika ada, maka tidak perlu menyentuhnya, saya menolak untuk mengkompilasi, dan umumnya siapa Anda, kami tidak memanggil Anda, menggunakan bagian antarmuka.



Di sinilah segala macam pengubah akses publik, pribadi dan lainnya ikut bermain dari bahasa favorit Anda. Sangat "menyembunyikan informasi" adalah cara yang paling dapat diandalkan untuk menjaga manfaat enkapsulasi sia-sia. Bagaimanapun, tidak masuk akal untuk mengelompokkan segala sesuatu yang menyangkut satu kelas di satu tempat, jika kode menggunakan apa yang diinginkan dan di mana ia inginkan. Tetapi dengan penyembunyian informasi, situasi seperti itu seharusnya tidak lagi muncul secara prinsip. Dan metode ini sangat dapat diandalkan sehingga di benak ribuan programmer (termasuk saya, yang sudah ada) perbedaan antara enkapsulasi dan menyembunyikan informasi entah bagaimana dihaluskan.


Bagaimana jika YP favorit Anda tidak memungkinkan Anda menyembunyikan informasi? Pada topik ini Anda bisa bersenang-senang berbicara tentang komentar. Saya melihat jalan keluar selanjutnya. Naik:


  • Dokumentasikan hanya bagian antarmuka dan pertimbangkan semua yang tidak didokumentasikan sebagai implementasi.
  • Pisahkan implementasi dari antarmuka melalui konvensi kode (contoh - dengan python ada variabel __all__ yang menunjukkan apa yang akan diimpor dari modul ketika Anda meminta untuk mengimpor semuanya).
  • Buat konvensi kode ini sangat ketat sehingga mereka dapat diperiksa secara otomatis, setelah itu setiap pelanggaran terhadap mereka akan disamakan dengan kesalahan kompilasi dan build yang jatuh.

Sekali lagi:


  • Segala sesuatu yang terkait dengan satu kelas dikemas ke dalam satu modul.
  • Di antara kelas-kelas, batas-batas arsitektur yang ketat dibuat.
  • Di kelas mana pun, bagian antarmuka dipisahkan dari penerapan bagian antarmuka itu sendiri.
  • Batas antara kelas harus dihormati dan dipaksa untuk menghormati orang lain!

Saya akan mengakhiri dengan contoh tentang NEPL, yang masih sangat aktif berkembang dan, setelah sepuluh paragraf, telah memperoleh pengubah akses:


 type Microwave: class consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns ButtonsPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. 

Saya harap jelas dari kode apa esensi dari contoh tersebut. Saya hanya akan mengklarifikasi satu hal: GetAccessToControlPanel memeriksa apakah kita bahkan dapat menyentuh microwave. Bagaimana jika dia rusak? Maka Anda tidak dapat mengklik apa pun. Anda hanya bisa mendapatkan pesan kesalahan.


Nah, fakta bahwa ButtonsPanel telah menjadi kelas terpisah dengan lancar membawa kita pada pertanyaan penting: apa "komponen tunggal" dari definisi enkapsulasi di Wikipedia? Di mana dan bagaimana seharusnya batas antara kelas berada? Kami pasti akan kembali ke masalah ini nanti.

Spoiler
Prinsip Tanggung Jawab Tunggal

Gunakan di luar OOP


Begitu banyak programmer telah belajar tentang enkapsulasi dari Java / C ++ / C # tutorial / gantikan bahasa OOP pertama Anda. Oleh karena itu, enkapsulasi dalam kesadaran massa terhubung dengan OOP. Tetapi mari kita kembali ke dua definisi enkapsulasi.


Mengemas data dan fungsi menjadi satu komponen.

Entitas perangkat lunak apa pun dengan status non-sepele harus diubah menjadi sistem tertutup, yang hanya dapat ditransfer dari satu kondisi yang benar ke yang lain.


Pernahkah Anda memperhatikan? Tidak ada sama sekali tentang kelas dan objek!


Jadi contohnya. Anda seorang DBA . Tugas Anda adalah mengawasi beberapa jenis basis data relasional. Biarkan saja, misalnya, di MySQL. Basis data Anda yang berharga menggunakan beberapa program. Anda tidak memiliki kendali atas beberapa dari mereka. Apa yang harus dilakukan


Buat satu set prosedur yang tersimpan. Kami menyusunnya menjadi satu sirkuit, yang akan kami sebut antarmuka. Kami membuat satu pengguna untuk program kami tanpa hak apa pun. Ini adalah perintah CREATE USER. Kemudian, menggunakan perintah GRANT, memberikan pengguna hanya hak untuk menjalankan prosedur tersimpan ini dari skema antarmuka.


Itu saja. Kami memiliki database, entitas yang sangat lunak dengan keadaan non-sepele, yang cukup mudah untuk dipecah. Dan agar kami tidak merusaknya, kami membuat antarmuka dari prosedur yang tersimpan. Dan setelah sarana MySQL itu sendiri, kami membuatnya sehingga entitas pihak ketiga hanya dapat menggunakan antarmuka ini.


Perhatikan enkapsulasi terkenal itu, sebagaimana adanya, dan bagaimana itu diuraikan, digunakan untuk potensi penuhnya. Tetapi ada kesenjangan antara representasi relasional data dan objek yang harus ditutup dengan kerangka kerja ORM yang besar.


Itu sebabnya kelas dan objek tidak muncul dalam definisi enkapsulasi. Idenya jauh lebih luas daripada OOP. Dan itu membawa terlalu banyak manfaat untuk berbicara di dalamnya hanya dalam buku teks tentang bahasa OOP.


Polimorfisme


Polimorfisme memiliki banyak bentuk dan definisi. Sudah cukup bahwa Kondratius mencukupi saya ketika saya membuka Wikipedia. Di sini saya akan berbicara tentang polimorfisme, seperti yang dirumuskan Straustrup: satu antarmuka - banyak implementasi .


Dalam bentuk ini, gagasan polimorfisme dapat sangat memperkuat posisi penulis dengan mata untuk enkapsulasi. Lagi pula, jika kita memisahkan antarmuka dari implementasi, maka orang yang menggunakan antarmuka tidak perlu tahu bahwa ada sesuatu yang berubah dalam implementasi. Mereka yang menggunakan antarmuka (idealnya) bahkan tidak perlu tahu bahwa implementasinya telah berubah sama sekali! Dan ini membuka kemungkinan tanpa akhir untuk ekspansi dan modifikasi. Apakah pendahulu Anda memutuskan bahwa yang terbaik adalah menghangatkan makanan dengan radar militer? Jika jenius gila ini memisahkan antarmuka dari implementasi dan memformalkannya dengan jelas, maka radar militer dapat disesuaikan dengan kebutuhan lain, dan antarmuka untuk memanaskan makanan dapat direalisasikan menggunakan microwave.


NEPL berkembang pesat dan, di bawah pengaruh C #, memperoleh (dengan hati-hati, tidak tersandung kata-kata) tipe data data seperti antarmuka.


 type FoodWarmer: interface consisting of GetAccessToControlPanel: no parameters returns FoodWarmerControlPanel, OpenDoor: no parameters returns nothing, Put: have (Food) parameters returns nothing, CloseDoor: no parameters returns nothing. type FoodWarmerControlPanel: interface consisting of PressOn: no parameters returns nothing, PressOff: no parameters returns nothing, PressIncreaseTime: no parameters returns nothing, PressDecreaseTime: no parameters returns nothing, PressStart: no parameters returns nothing, PressStop: no parameters returns nothing. type EnemyFinder: interface consisting of FindEnemies: no parameters returns List of Enemy. type Radar: class implementing FoodWarmer, EnemyFinder and consisting of private secretMilitaryChips: List of Chip, private giantMicrowavesGenerator: FoodWarmerController, private strangeControlPanel: AlarmClock, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing, public FindEnemies: subprogram with no parameters returns List of Enemy. type AlarmClock: class implementing FoodWarmerControlPanel and consisting of private mechanics: List of MechanicPart, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. type Microwave: class implementing FoodWarmer and consisting of private fancyInnerChips: List of Chip, private foodWarmingThing: FoodWarmerController, private buttonsPanel: ButtonsPanel, public GetAccessToControlPanel: subprogram with no parameters returns FoodWarmerControlPanel, public OpenDoor: subprogram with no parameters returns nothing, public Put: subprogram with (Food) parameters return nothing, public CloseDoor: subprogram with no parameters returns nothing. type ButtonsPanel: class implementing FoodWarmerControlPanel and consisting of private buttons: List of ButtonState, public PressOn: subprogram with no parameters returns nothing, public PressOff: subprogram with no parameters returns nothing, public PressIncreaseTime: subprogram with no parameters returns nothing, public PressDecreaseTime: subprogram with no parameters returns nothing, public PressStart: subprogram with no parameters returns nothing, public PressStop: subprogram with no parameters returns nothing. 

Jadi, jika kelas dinyatakan sebagai mengimplementasikan antarmuka, maka harus menerapkan semua metode dari antarmuka ini. Jika tidak, kompiler akan memberi tahu kami "fi". Dan kami memiliki dua antarmuka: FoodWarmer dan FoodWarmerControlPanel. Lihatlah dengan cermat, lalu mari kita menganalisis implementasinya.


Sebagai warisan dari masa lalu Soviet yang sulit, kami menerima kelas penggunaan ganda Radar, yang dapat digunakan untuk memanaskan makanan dan menemukan musuh. Dan bukannya panel kontrol, alarm digunakan, karena rencana telah terlampaui, dan mereka perlu diletakkan di suatu tempat. Tapi, untungnya, MNS yang tidak disebutkan namanya dari Lembaga Penelitian Kimia, Pupuk dan Racun, yang mereka dorong, menerapkan antarmuka FoodWarmer untuk radar dan FoodWarmerControlPanel untuk jam alarm.


Setelah satu generasi, terpikir oleh seseorang bahwa lebih baik memanaskan makanan dengan microwave, dan lebih baik mengendalikan microwave dengan tombol. Dan sekarang kelas Microwave dan ButtonsPanel dibuat. Dan mereka mengimplementasikan antarmuka yang sama. FoodWarmer dan FoodWarmerControl. Apa yang ini berikan pada kita?


Jika di mana-mana dalam kode kami, kami menggunakan variabel seperti FoodWarmer untuk memanaskan makanan, maka kami dapat dengan mudah mengganti implementasinya dengan yang lebih modern dan tidak ada yang akan melihat apa pun. Artinya, kode yang menggunakan antarmuka tidak peduli tentang detail implementasi. Atau fakta bahwa itu telah berubah sepenuhnya. Kami bahkan dapat membuat kelas FoodWarmerFactory yang menghasilkan implementasi FoodWarmer berbeda tergantung pada konfigurasi aplikasi Anda.


Lihat juga bidang tertutup di kelas Microwave dan Radar. Di sana kami memiliki jam alarm dan panel dengan tombol. Namun di luar kami memberikan variabel tipe FoodWarmerControlPanel.


Di suatu tempat di Picabu ada sebuah kisah tentang bagaimana seorang kandidat tertentu menjelaskan prinsip polimorfisme sebagai berikut:

Di sini saya punya pena. Aku bisa menulis namaku padanya, tapi aku bisa menempelkannya di matamu. Ini adalah prinsip polimorfisme.
Gambarannya lucu, situasinya mengerikan, dan penjelasan tentang prinsip polimorfisme tidak berguna.

Prinsip polimorfisme bukanlah bahwa kelas pena dengan beberapa ketakutan menyadari antarmuka alat tulis dan baja dingin pada saat yang sama. Prinsip polimorfisme adalah bahwa segala sesuatu yang dapat ditusuk dapat tertancap di mata. Karena itu bisa ditusuk. Dan hasilnya akan berbeda, tetapi idealnya harus memberikan kekurangan visual. Dan metode polimorfisme memungkinkan Anda untuk mencerminkan fakta ini dalam model yang Anda bangun untuk dunia Anda.


Gunakan di luar OOP


Ada bahasa yang lucu dan lucu dalam semua arti kata-kata ini sebagai Erlang. Dan itu memiliki fitur seperti perilaku. Awasi tangan Anda:


Kode di sana dibagi menjadi beberapa modul. Anda dapat menggunakan nama modul sebagai variabel. Artinya, Anda dapat menulis panggilan fungsi dari modul seperti ini:


 %option 1 foobar:function(), %option 2 Module = foobar, Module:function(). 

Untuk memastikan bahwa modul memiliki fungsi tertentu, ada fitur bahasa sebagai perilaku. Dalam modul yang menggunakan modul lain, Anda menentukan persyaratan untuk modul variabel menggunakan deklarasi behaviour_info. Dan kemudian modul yang modul ayah Anda, yang mengatur behaviour_info, akan menggunakan, menggunakan deklarasi perilaku, memberi tahu kompiler: "kami berkomitmen untuk menerapkan perilaku ini sehingga modul ayah dapat menggunakan kami."


Sebagai contoh, modul gen_server memungkinkan Anda untuk membuat server yang sinkron atau asinkron dalam proses terpisah (tidak ada utas di Erlang, hanya ribuan proses paralel kecil), menjalankan permintaan dari proses lain. Dan di gen_server, semua logika yang terkait dengan permintaan dari proses lain dikumpulkan. Tetapi pemrosesan langsung dari permintaan ini dilakukan oleh mereka yang menerapkan perilaku gen_server. Dan sementara modul lain mengimplementasikannya dengan benar (bahkan jika ada bertopik kosong), gen_server bahkan tidak peduli bagaimana permintaan ini ditangani. Selain itu, modul pemrosesan dapat diubah dengan cepat.


Satu antarmuka - banyak implementasi. Saat Straustrup mewariskan kepada kami. Seperti yang ditulis dalam buku pintar tentang OOP. Dan sekarang kutipan dari wikipedia ke studio:


Erlang — , .

« — » , , .


. .NET. . CLR . CLR Microsoft , , , ( ECMA-335).


.NET , Windows, Windows Phone, XBox ( XNA, , ), . Microsoft. , , . , Mono Project. .NET. .


, . Microsoft , .NET . . , , .NET Core. , .NET Core .NET Framework, , , . , .


, « — » - . , .



, . , , , . , .


, , . , , NEPL. . Name? , . EtiquetteInfo - .


 import class EtiquetteInfo from Diplomacy. type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteFirstName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteFirstName(FirstName). subprogram GetPoliteMiddleName.PoliteName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteMiddleName(MiddleName). subprogram GetPoliteLastName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return _EtiquetteInfo.PoliteLastName(LastName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as return GetPoliteFirstName(_EtiquetteInfo) + GetPoliteMiddleName(_EtiquetteInfo) + GetPoliteLastName(_EtiquetteInfo). 

, GetFullName - , ( , , ?). , , - . , , . , , , , . , . PoliteName . ExoticPoliteName — . , , .


- . ExoticPoliteName, PoliteName, . PoliteExoticName. , PoliteName.


 import class EtiquetteInfo from Diplomacy. type PoliteExoticName: class extending PoliteName and consisting of private MoreMiddleNames: List of String, for descendants overridden GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, public overriden GetFullName: subprogram with (EtiquetteInfo) parameters returns String. subprogram GetPoliteMiddleName.PoliteExoticName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String AggregatedMiddleName = String.Join(" ", MoreMiddleNames), return base.GetPoliteMiddleName(_EtiquetteInfo + AggregatedMiddleName). subprogram GetFullName with (EtiquetteInfo _EtiquetteInfo) parameters returning String implemented as String Prefix = "", String FirstName = GetFirstName(_EtiquetteInfo), if _EtiquetteInfo.ComplimentIsAppropriate(FirstName) then Prefix = "Oh, joy of my heart, dear ", return Prefix + base.GetFullName(_EtiquetteInfo). 

: PoliteName . PoliteExoticName -.


, , . , GetPoliteFirstName GetPoliteLastName. . GetFullName, , .


, , PoliteName, PoliteExoticName, GetFullName. , PoliteName, , . , , base.GetFullName(etiquetteInfo). , , .


, " ". , . : , . . .


, . . , Boolean, , . , Object. . , , , , Object, .


, NEPL . PoliteName Object, PoliteExoticName PoliteName Object . , NEPL :


 subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName. 

, , _Object.GetFullName, , . PoliteName PoliteExoticName - Object, - _Object, .


? , . . , ( Object) , - -.


, , , , . ? , . - , . . - , .


? , . . . , «» , . ?


. , NEPL for descendants.


 type PoliteName: class consisting of private FirstName: String, private MiddleName: String, private LastName: String, for descendants GetPoliteFirstName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteMiddleName: subprogram with (EtiquetteInfo) parameters returns String, for descendants GetPoliteLastName: subprogram with (EtiquetteInfo) parameters returns String, public GetFullName: subprogram with (EtiquetteInfo) parameters returns String. 

PoliteExoticName FirstName, «, , , , ». GetPoliteFirstName FirstName.



, , Square Shape, Shape Square . , . Shape , , . Square, Shape. Mengapa , Shape, .


. , ? -, . -, , , , . , , .


, . , . « »? , . . , .


. , . , , . . , . , , .


, , . . ( , ), , . , , . , , . .


, ) , ) , , , 999 1000 . , , .


()


, . - , . , , . - , .


SOLID


— , , . - . SOLID — , , … ? ? , , .


S — The Single Responsibility Principle


. .


.

. . .


, « » - . . « ?». , , ? .


, SRP :

.

, ? — , . ? . « ». , . ? .


, ? . , .txt-. , , , .txt-. - , . , , … .txt-. Mengapa . , , .txt-.


NB ( , ) «», « , ».



. , , , .txt-. . , . , . Tapi! -, , , . -, , , .



, , . , ) , ) .html-. , , .txt/.html.


, , , . , , .txt-. Apa yang bisa salah?


  • . , . , , . , , , , , , . .
  • , . 900 USD 900.00$? 20190826T130000 2019 ? , ?
  • .txt-? .csv? , .txt. ? ? - ? - ? , ?

? — . , -.


, , , . , .


DRY — Don't Repeat Yourself


:


, . .

, , , - , . , , .



SRP . , , . , « ». , , , — .


. HTML . , «» . , HTML 90- . , . - , . , HTML- . CSS .


? -, CSS , «» . , . -, CSS- html- , - text-color . — . .


O — The Open/Closed Principle


, , , - . , «» «». / , , . « » . :


.

. , . , . , — .


, . . ? . , .


  • , . , . , / .
  • , . , . / /, . NB /. , : .
  • , . , .
  • , - , , . (), () «» .
  • . , , ? , . , . , .

, . .



 type SpellChecker: class consisting of public DoSpellCheck: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class consisting of public DoCorporativeStyleCheck: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, ProcessedText = SpellChecker.DoSpellCheck(ProcessedText), ProcessedText = CorporativeStyleChecker.DoCorporativeStyleCheck(ProcessedText), return ProcessedText. 

,


 type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextProcessor: class consisting of private Text: String, private SpellChecker: SpellChecker, private CorporativeStyleChecker: CorporativeStyleChecker, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = (SpellChecker, CorporativeStyleChecker), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText. 

O/CP . TextCheckersSupplier, .


 type TextChecker: interface consisting of Check: have (String) parameters returns String. type SpellChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type CorporativeStyleChecker: class implementing TextChecker and consisting of public Check: subprogram with (String) parameters returns String. type TextCheckersSupplier: class consisting of public GetCheckers: subprogram with no parameters returns List of TextChecker. type TextProcessor: class consisting of private Text: String, private CheckersSupplier: TextCheckersSupplier, public Process: subprogram with no parameters returns String. subprogram TextProcessor.Process with no parameters returning String implemented as String ProcessedText = Text, List of SpellChecker Checkers = CheckersSupplier.GetCheckers(), for each SpellChecker SpellChecker in Checkers do ProcessedText = SpellChecker.Check(ProcessedText) and nothing else, return ProcessedText. 

? , , , . , TextProcessor. , TextCheckerSupplier , , . TextChecker' . , , , . , .



, , , , . .


L — The Liskov Substitute Principle


, :


, , , .

, , . ?



- NEPL:


 type PoliteExoticName: class extending PoliteName and consisting of... subprogram Foo.Bar with no parameters returning nothing implemented as PoliteExoticName _PoliteExoticName = GetSomePoliteExoticName(), PoliteName _PoliteName = _PoliteExoticName, Object _Object = _PoliteExoticName. 

, _PoliteName - . , , . , . PoliteName, . , , , PoliteName . , , , . .


, -, allex ( , ). , , :

-, .

, «Agile Principles, Patterns and Practices in C#». NEPL, .


  Object _Object = GetObjectSomewhere(), PoliteExoticName IHopeItsActuallyName = _Object as PoliteExoticName, 

, , . . , . - , . , . . ( , alias , ):


 from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. type Set: interface consisting of Add: have (Object) parameters returns nothing, Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type UnboundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram BoundedSet.Add with (Object O) parameters returning nothing implemented as ThirdPartSet.Add(O). 


. . . PersistentSet, - . , PersistentObject. - . Delete IsMember . Add...


 from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type PersistentSet: class implementing Set and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. subprogram PersistentSet.Add with (Object O) parameters returning nothing implemented as PersistentObject Po = O as PersistentObject, ThirdPartySet.Add(Po). 


. PersistentSet Object, . , , Set , . ( ):


 type MemberContainer: interface consisting of Delete: have (Object) parameters returns nothing, IsMember: have (Object) parameters returns Boolean. type Set: interface extending MemberContainer and consisting of Add: have (Object) parameters returns nothing. type PersistentSet: interface extending MemberContainer and consisting of Add: have (PersistingObject) parameters returns nothing. 


C#.


, NEPL

NEPL. List of String. , .


 type List: class generalized with (T) parameters consisting of 

Set PersistentSet.


 from UnboundedCollections import UnboundedSet as ThirdPartyUnboundedSet. from BoundedCollections import BoundedSet as ThirdPartBoundedSet. from PersistentCollections import PersistentSet as ThirdPartyPersistentSet, PersistentObject. type Set: interface generalized with (T) parameters consisting of Add: have (T) parameters returns nothing, Delete: have (T) parameters returns nothing, IsMember: have (T) parameters returns Boolean. type UnboundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyUnboundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type BoundedSet: class implementing Set of Object and consisting of private ThirdPartySet: ThirdPartyBoundedSet, public Add: subprogram with (Object) parameters returning nothing, public Delete: subprogram with (Object) parameters returning nothing, public IsMember: subprogram with (Object) parameters returning Boolean. type PersistentSet: class implementing Set of PersistentObject and consisting of private ThirdPartySet: ThirdPartyPersistentSet, public Add: subprogram with (PersistentObject) parameters returning nothing, public Delete: subprogram with (PersistentObject) parameters returning nothing, public IsMember: subprogram with (PersistentObject) parameters returning Boolean. 


()


. , , «» . , « — » . , . .


I — The Interface Segregation Principle


, . . .



, ? , - . , , SQL? , . , API . API , «interface», , . . Apa yang bisa salah?


, , - , DBA . , 100500 , . . , .


, , , «» . , « interface, ». DBA . GDPR, HIPAA , .


- . , . Dan sebagainya.


? , . :


, ( ).

? interface «interface_billing», «interface_customer_data» . .


, , . pet-project. IActor. , . , IActor : ICollidable, IDisplayble, IUpdatable. ?



( , Camera), . , - . , . , , IDisplayble SpecEffect.


CollisionsController , - ICollidable. , , , SOLID . TileWall -, . CollisionsController . , , IActor , .



: , , .


D — The Dependency Inversion


-. , . , , . , , . , , , , - . , , - . , , - .


"Di sini! .» — - ImportantClass. , , . , ImportantClass VeryImportantClass, , , EvenMoreImportantClass, , . , , , . , . , .



ImportantClass VeryImportantClass EvenMoreImportantClass. ImportantClass . , , . , IVeryImportantClass IEvenMoreImportantClass, ImportantClass.


ImportantClass VeryImportantClass . ImportantClass « », IVeryImportantClass .



. , , .


. .
. .

, «» «» - . , , . . , , . , . , -.


. , .


- , . ( MegaSender), . , , SOAP API.


-. SenderAccess. , MegaSender SenderAccess . SenderAccess MegaSender, , MegaSender, MegaSender , Apple i.


MegaSender. LightSender. , SenderAccess c LightSender. , , . , .


SenderAccess, , MegaSender . , SenderAccess MegaSender. MegaSender « ». , MegaSender, , , . . , LightSender , , LightSender, MegaSender.


, SenderAccess , SenderAccess LightSender . .


, IActor ICollidable, IUpdatable, IDisplayble. , IActor . Actor Player, Enemy, Door, Wall . , .


Blueprint. . , , , , , et cetera. , , , .


, , C#, . , - List<String>. , List<T> List<String>. Actor Actor<TBlueprint>.


, , - . Actor<EnemyBlueprint> Actor<DoorBlueprint> , . , .


- . , , . , . , . , IActor, , ActorsFactory .


. : .



, , . - , . () :


. . . TCP/HTTP/SMPP/SOAP, . ? , TCP/HTTP/SMPP/SOAP- TCP/HTTP/SMPP/SOAP- , . , - . ? Pikirkan tentang itu. , « » « 1000 ».



. ? - SOLID'? , . , - , .


- , .
- , .

. , -, , . . - , , , , -. , .


. - , :


KISS — . . . , , . , , .


, , Actor . , — , - . , .


YAGNI — . - , , « », , - . , . « » - . , , . , , .


, OC/P . 50 . , , -. 50 , . , - .


? , , « », . « » , .


, SOLID . , .



, . , -, , . , .


  • «Code Complete» . , « » - .
  • «Clean Code» . «Clean Architecture».
  • «Agile Principles, Patterns and Practices in C#» . SOLID . , language-agnostic.

PS


, . , , . : ! , , language-agnostic .


, language-agnostic. NEPL, : , , , . , , .


, . , , . :


, , , . , , . , . : , .

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


All Articles