Ini adalah versi baru dari artikel lama saya. Itu telah diubah dan ditambah untuk Python dll. — saluran telegram tentang Python dan banyak lagi.

Mari kita selidiki situasi yang cukup umum. Anda memiliki beberapa subkelas, dan Anda harus memilih satu berdasarkan beberapa parameter yang disediakan oleh pengguna (biasanya disebut type atau semacamnya).
Berikut ini contohnya: pengguna Anda mengirim pesan melalui layanan Anda dengan menentukan jenis pesan dan beberapa parameter tambahan.

Kode ini berfungsi cukup baik, namun ada beberapa teknik yang dapat Anda gunakan untuk memperbaikinya.

Pabrik

Pertama-tama, saya ingin entitas terpisah peduli dengan pembuatan objek pesan. Ada dua tipe konvensional entitas tersebut: pabrik atau metode pabrik.

Pabrik:

Metode pabrik:

Bagi saya, pabrik agak berlebihan di sini, jadi saya tetap menggunakan pendekatan metode pabrik pada contoh berikut.

Pilih kelas, bukan objek

Sekarang kita dapat memperbaiki kode metode pabrik itu sendiri. Misalnya, karena kelas adalah “objek kelas satu” dengan Python, Anda tidak perlu memilih objek pesan, cukup pilih kelas:

Gunakan pemetaan

Sekarang jelas bahwa kita berurusan dengan pemetaan tipe ke kelas yang membosankan. Jadi sebaiknya ubah pernyataan if kita menjadi peta yang sebenarnya:

Perhatikan, kita tidak dapat menjadikan MESSAGE_TYPE_TO_CLASS_MAP sebagai atribut kelas karena keturunan BaseMessage harus dideklarasikan setelah BaseMessage dan oleh karena itu belum ada pada saat BaseMessage dibuat.

Hasilkan nama kelas (jangan)

Anda mungkin memperhatikan bahwa nama setiap kelas hampir sama dengan nama tipenya. Kita bisa menggunakannya untuk menghasilkan nama kelas berdasarkan nama tipe dan tidak menyimpan pemetaan sebenarnya:

Namun sebenarnya Anda tidak boleh melakukan ini. Ada banyak masalah dengan pendekatan ini. Pertama-tama, Anda tidak dapat menjamin bahwa beberapa kelas bernama SomethingMessage akan ditemukan meskipun Anda tidak menginginkannya. Kedua, hal ini tidak sejelas mungkin. Ketiga, IDE Anda mungkin tidak akan memahami apa pun dalam kekacauan ini.

Jika karena alasan tertentu Anda memutuskan untuk tetap menggunakan metode ini, Anda setidaknya harus memeriksa apakah kelas hasil adalah subkelas dari BaseMessage.

Daftarkan subkelas

Pemetaan polos yang kami gunakan masih belum sempurna. Jika Anda membuat subkelas pesan baru, Anda harus mendefinisikannya danmenambahkannya ke pemetaan. Sebaiknya tandai subkelasnya, sehingga subkelas tersebut akan muncul di pemetaan secara otomatis. Dan di sinilah dekorator python bersinar.

Ingat saya pernah mengatakan bahwa kita tidak dapat menjadikan pemetaan sebagai atribut BaseMessage karena keturunannya belum ditentukan? Sekarang kita dapat membuat mereka menambahkan dirinya ke pemetaan setelah deklarasi.

Untuk ini, kita memerlukan atribut kelas subclasses dan dekorator register_subclass untuk menambahkan subkelas ke atribut tersebut. Perhatikan, bahwa register_subclass adalah dekorator berparameter, sehingga ia mengembalikan fungsi yang menerima subkelas sebagai argumen.

Pendekatan ini telah berhasil bagi saya sejak lama. Hal terbaiknya adalah setiap kali programmer baru ingin membuat subkelas pesan baru, dia hanya menyalin subkelas pesan yang sudah ada sebagai scaffold dan tidak pernah lupa menambahkan dekorator.

kelas meta

Alternatifnya, Anda dapat memanfaatkan fakta bahwa metakelas dari beberapa kelas juga berfungsi untuk semua turunannya. Kita dapat menulis metakelas untuk BaseMessage yang secara otomatis mendaftarkan semua subkelas.

Subkelas masih perlu menyimpan suatu tipe, mari kita masukkan ke dalam atribut _MESSAGE_TYPE. Selain itu, metaclass memerlukan beberapa cara untuk mendeteksi BaseMessage. Untuk melakukan itu, kami menggunakan fakta bahwa ini adalah kelas pertama yang dibuat melalui metaclass.

Lihatlah hasilnya:

Jika Anda tidak dapat memahami apa yang sedang terjadi di sini, tidak mengherankan. Solusi yang menggunakan metaclass terkenal terlalu rumit dan sulit dibaca dan dipahami. Dan ada sedikit yang kita peroleh dengan cara ini: subkelas perlu menentukan _MESSAGE_TYPE, itu tidak lebih sederhana dari dekorator @register.

Selain itu, kelas dengan metakelas khusus mungkin sulit untuk diwarisi. Jika Anda ingin mewarisi dari dua kelas, metakelasnya harus sama atau salah satu kelasnya harus merupakan turunan dari kelas lain untuk menghindari apa yang disebut konflik metakelas.

Init kait subkelas

Karena Python 3.6 memungkinkan untuk mengaitkan pembuatan subkelas tanpa menggunakan metakelas, Anda dapat melakukannya melalui metode ajaib __init_subclass__:

Anda mungkin menganggapnya lebih elegan atau tidak daripada solusi dekorator.

Pengimporan

Semua solusi di atas hanya cocok untuk subkelas yang ada dalam file yang sama dengan kelas dasar atau setidaknya subkelas lain yang sudah diimpor karena alasan tertentu. Tanpa itu, mereka tidak akan mempunyai kesempatan untuk mendaftar.

Untuk memperbaikinya Anda dapat mengimpornya secara manual, satu per satu (yang membosankan) atau mengatur beberapa penemuan otomatis (yang mungkin tidak aman). Anda juga dapat menggunakan solusi pemetaan dan mengimpor modul secara dinamis saat pembuatan objek, tetapi ini lebih buruk lagi. Sayangnya, saya masih belum memiliki solusi elegan untuk masalah ini.