Hai, Habr. Baru-baru ini saya tergila-gila dengan desain - akses pengubah dan antarmuka, kemudian saya porting ke bahasa pemrograman Python. Saya bertanya di bawah kat - Saya membagikan hasil dan cara kerjanya. Bagi yang berminat, di akhir artikel ada tautan ke proyek di Github.
Akses pengubah
Pengubah akses membatasi akses ke objek - ke metode kelas mereka, atau ke kelas anak - ke metode kelas induknya. Menggunakan pengubah akses membantu menyembunyikan data di kelas sehingga tidak ada orang di luar yang dapat mengganggu pekerjaan kelas ini.
metode
pribadi hanya tersedia di dalam kelas,
dilindungi (di dalam) - di dalam kelas dan di kelas anak-anak.
Bagaimana metode pribadi dan terlindungi diimplementasikan dalam Python
Spoiler - pada tingkat kesepakatan bahwa orang dewasa tidak akan memanggil mereka di luar kelas. Sebelum metode pribadi, Anda perlu menulis garis bawah ganda, sebelum yang terlindungi. Dan Anda masih dapat mengakses metode, meskipun aksesnya "terbatas".
class Car: def _start_engine(self): return "Engine's sound." def run(self): return self._start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() assert "Engine's sound." == car._start_engine()
Kerugian berikut dapat ditentukan:
- Jika metode _start_engine memperbarui beberapa variabel kelas atau status tetap, dan tidak hanya mengembalikan "perhitungan bodoh", Anda bisa merusak sesuatu untuk pekerjaan di masa depan dengan kelas. Anda tidak akan membiarkan diri Anda memperbaiki sesuatu di mesin mobil Anda, karena Anda tidak akan pergi ke mana pun, bukan?
- Titik mengalir dari yang sebelumnya - untuk memastikan bahwa Anda dapat "dengan aman" (memanggil metode tidak membahayakan kelas itu sendiri) menggunakan metode yang dilindungi - Anda perlu melihat ke dalam kode dan menghabiskan waktu.
- Penulis perpustakaan berharap bahwa tidak ada yang menggunakan metode kelas yang dilindungi dan pribadi yang Anda gunakan dalam proyek Anda. Oleh karena itu, mereka dapat mengubah implementasinya dalam rilis apa pun (yang tidak akan memengaruhi metode publik karena kompatibilitas ke belakang, tetapi Anda akan menderita).
- Penulis kelas, kolega Anda, berharap Anda tidak akan menambah hutang teknis proyek dengan menggunakan metode yang dilindungi atau pribadi di luar kelas yang ia buat. Lagi pula, orang yang akan memperbaiki atau memodifikasinya (metode kelas privat) harus memastikan (misalnya, melalui tes) bahwa perubahannya tidak akan merusak kode Anda. Dan jika mereka memecahkannya, dia harus menghabiskan waktu mencoba menyelesaikan masalah ini (dengan tongkat penyangga, karena dia membutuhkannya kemarin).
- Mungkin Anda memastikan bahwa pemrogram lain tidak menggunakan metode yang dilindungi atau pribadi pada ulasan kode dan “mengalahkannya”, jadi habiskan waktu.
Cara menerapkan metode yang dilindungi menggunakan perpustakaan
from accessify import protected class Car: @protected def start_engine(self): return "Engine's sound." def run(self): return self.start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() car.start_engine()
Mencoba memanggil metode
start_engine di luar kelas akan menghasilkan kesalahan berikut (metode ini tidak tersedia sesuai dengan kebijakan akses):
Traceback (most recent call last): File "examples/access/private.py", line 24, in <module> car.start_engine() File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper class_name=instance_class.__name__, method_name=method.__name__, accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level
Menggunakan perpustakaan:
- Anda tidak perlu menggunakan garis bawah jelek (subyektif) atau menggandakan garis bawah.
- Anda mendapatkan metode (subyektif) yang indah untuk menerapkan pengubah akses dalam kode - dekorator pribadi dan terlindungi .
- Mentransfer tanggung jawab dari orang ke juru bahasa.
Cara kerjanya:
- Dekorator pribadi atau terlindungi - dekorator paling "tinggi", menyala sebelum metode kelas , yang dinyatakan sebagai pengubah akses pribadi atau terlindungi.
- Menggunakan built-in inspect library, dekorator mengambil objek saat ini dari tumpukan panggilan - inspect.currentframe () . Objek ini memiliki atribut berikut yang berguna bagi kami: namespace (lokal) dan tautan ke objek sebelumnya dari tumpukan panggilan (objek yang memanggil metode dengan pengubah akses).
(Ilustrasi yang sangat sederhana)
- inspect.currentframe (). f_back - gunakan atribut ini untuk memeriksa apakah objek sebelumnya dari tumpukan panggilan ada di badan kelas atau tidak. Untuk melakukan ini, lihat namespace - f_locals . Jika ada atribut mandiri di namespace, metode ini disebut di dalam kelas, jika tidak, di luar kelas. Jika Anda memanggil metode dengan pengubah akses pribadi atau yang dilindungi di luar kelas, akan ada kesalahan kebijakan akses.
Antarmuka
Antarmuka adalah kontrak interaksi dengan kelas yang mengimplementasikannya. Antarmuka berisi tanda tangan metode (nama fungsi, argumen input), dan kelas yang mengimplementasikan antarmuka, mengikuti tanda tangan, mengimplementasikan logika. Kesimpulannya, jika dua kelas mengimplementasikan antarmuka yang sama, Anda dapat yakin bahwa kedua objek dari kelas ini memiliki metode yang sama.
Contoh
Kami memiliki kelas
pengguna yang menggunakan objek
penyimpanan untuk membuat pengguna baru.
class User: def __init__(self, storage): self.storage = storage def create(self, name): return storage.create_with_name(name=name)
Anda dapat menyimpan pengguna ke database menggunakan
DatabaseStorage.create_with_name .
class DatabaseStorage: def create_with_name(self, name): ...
Anda dapat menyimpan pengguna ke file menggunakan
FileStorage.create_with_name .
class FileStorage: def create_with_name(self, name): ...
Karena fakta bahwa tanda tangan dari metode
create_with_name (nama, argumen) adalah sama untuk kelas - kelas
Pengguna tidak perlu khawatir tentang objek mana yang diganti jika keduanya memiliki metode yang sama. Ini dapat dicapai jika kelas
FileStorage dan
DatabaseStorage menerapkan antarmuka yang sama (yaitu, mereka terikat oleh kontrak untuk mendefinisikan beberapa metode dengan logika di dalamnya).
if __name__ == '__main__': if settings.config.storage = FileStorage: storage = FileStorage() if settings.config.storage = DatabaseStorage: storage = DatabaseStorage() user = User(storage=storage) user.create_with_name(name=...)
Cara bekerja dengan antarmuka menggunakan perpustakaan
Jika sebuah kelas mengimplementasikan antarmuka, kelas tersebut harus berisi
semua metode antarmuka . Dalam contoh di bawah ini, antarmuka HumanInterface berisi metode eat, dan kelas Human mengimplementasikannya, tetapi tidak mengimplementasikan metode eat.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: pass
Script akan keluar dengan kesalahan berikut:
Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs)
Jika suatu kelas mengimplementasikan suatu antarmuka, kelas tersebut harus berisi semua metode antarmuka,
termasuk semua argumen yang masuk . Dalam contoh di bawah ini, antarmuka HumanInterface berisi metode eat, yang mengambil 4 argumen untuk input, dan kelas Human mengimplementasikannya, tetapi mengimplementasikan metode eat dengan hanya 1 argumen.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food): pass
Script akan keluar dengan kesalahan berikut:
Traceback (most recent call last): File "examples/interfaces/single_arguments.py", line 16, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments
Jika suatu kelas mengimplementasikan suatu antarmuka, kelas tersebut harus berisi semua metode antarmuka, termasuk argumen yang masuk dan
pengubah akses . Dalam contoh di bawah ini, antarmuka HumanInterface berisi metode makan pribadi, dan kelas Manusia mengimplementasikannya, tetapi tidak menerapkan pengubah akses pribadi untuk metode makan.
from accessify import implements, private class HumanInterface: @private @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass
Script akan keluar dengan kesalahan berikut:
Traceback (most recent call last): File "examples/interfaces/single_access.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator interface_method_name=interface_method.name, accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier.
Kelas dapat mengimplementasikan beberapa (jumlah tidak terbatas) antarmuka. Jika suatu kelas mengimplementasikan beberapa antarmuka, kelas tersebut harus berisi
semua metode dari semua antarmuka, termasuk argumen yang masuk dan pengubah akses . Dalam contoh di bawah ini, kelas Manusia mengimplementasikan metode makan dari antarmuka HumanBasicsInterface, tetapi tidak menerapkan metode cinta dari antarmuka HumanSoulInterface.
from accessify import implements class HumanSoulInterface: def love(self, who, *args, **kwargs): pass class HumanBasicsInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanSoulInterface, HumanBasicsInterface) class Human: def love(self, who, *args, **kwargs): pass
Script akan keluar dengan kesalahan berikut:
Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs)
Fitur Killer - metode antarmuka dapat "menyatakan" kesalahan apa yang harus dilakukan oleh metode kelas yang mengimplementasikannya "melempar". Dalam contoh di bawah ini, "dinyatakan" bahwa metode "cinta" dari antarmuka "ManusiaInterface" harus melempar pengecualian "HumanDoesNotExistError" dan
"HumanAlreadyInLoveError", tetapi metode "cinta" dari kelas "Manusia" tidak "melempar" salah satunya.
from accessify import implements, throws class HumanDoesNotExistError(Exception): pass class HumanAlreadyInLoveError(Exception): pass class HumanInterface: @throws(HumanDoesNotExistError, HumanAlreadyInLoveError) def love(self, who, *args, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: def love(self, who, *args, **kwargs): if who is None: raise HumanDoesNotExistError('Human whom need to love does not exist')
Script akan keluar dengan kesalahan berikut:
Traceback (most recent call last): File "examples/interfaces/throws.py", line 21, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator class_method_arguments=class_member.arguments_as_string, accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs)
Untuk meringkas, gunakan perpustakaan:
- Anda dapat mengimplementasikan satu atau lebih antarmuka.
- Antarmuka digabungkan dengan pengubah akses.
- Anda akan mendapatkan pemisahan antarmuka dan kelas abstrak ( modul abc dengan Python ), sekarang Anda tidak perlu menggunakan kelas abstrak sebagai antarmuka jika Anda melakukannya (saya lakukan).
- Dibandingkan dengan kelas abstrak. Jika Anda belum mendefinisikan semua argumen metode dari antarmuka, Anda akan mendapatkan kesalahan menggunakan kelas abstrak - tidak.
- Dibandingkan dengan kelas abstrak. Menggunakan antarmuka, Anda akan mendapatkan kesalahan saat membuat kelas (ketika Anda menulis kelas dan memanggil file * .py ). Di kelas abstrak, Anda akan mendapatkan kesalahan pada tahap memanggil metode objek kelas.
Cara kerjanya:
- Menggunakan built-in inspect library di dekorator implement , semua metode dari kelas dan interface-nya - inspect.getmembers () diperoleh . Indeks unik dari suatu metode adalah kombinasi dari nama dan jenisnya (metode statis, properti, dan sebagainya).
- Dan dengan inspect.signature () , argumen untuk metode ini.
- Kami mengulangi semua metode antarmuka, dan melihat apakah ada metode seperti itu (dengan indeks unik) di kelas yang mengimplementasikan antarmuka, apakah argumen yang masuk sama, apakah pengubah akses sama, apakah metode mengimplementasikan kesalahan yang dinyatakan dalam metode antarmuka.
Terima kasih atas perhatiannya pada artikel ini.
Tautan ke proyek di Github .