Pertanyaan ini agak agnostik bahasa, tetapi tidak sepenuhnya, karena Object Oriented Programming (OOP) berbeda di, misalnya, Java , yang tidak memiliki fungsi kelas satu, daripada di Python .
Dengan kata lain, saya merasa kurang bersalah karena membuat kelas yang tidak perlu dalam bahasa seperti Java, tapi saya merasa mungkin ada cara yang lebih baik dalam bahasa yang kurang boilerplate-y seperti Python.
Program saya perlu melakukan operasi yang relatif kompleks beberapa kali. Operasi itu membutuhkan banyak "pembukuan", harus membuat dan menghapus beberapa file sementara, dll.
Itu sebabnya ia juga perlu memanggil banyak "sub-operasi" lainnya - memasukkan semuanya ke dalam satu metode besar tidak terlalu bagus, modular, mudah dibaca, dll.
Sekarang ini adalah pendekatan yang muncul di pikiran saya:
1. Buat kelas yang hanya memiliki satu metode publik dan pertahankan keadaan internal yang diperlukan untuk suboperasi dalam variabel instan.
Akan terlihat seperti ini:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
Masalah yang saya lihat dengan pendekatan ini adalah bahwa keadaan kelas ini hanya masuk akal secara internal, dan tidak dapat melakukan apa pun dengan instance-nya kecuali memanggil satu-satunya metode publik mereka.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Membuat banyak fungsi tanpa kelas dan meneruskan berbagai variabel yang diperlukan secara internal di antara mereka (sebagai argumen).
Masalah yang saya lihat dengan ini adalah saya harus melewati banyak variabel antar fungsi. Juga, fungsi-fungsi itu akan terkait erat satu sama lain, tetapi mereka tidak akan dikelompokkan bersama.
3. Suka 2. tetapi buat variabel keadaan global alih-alih melewatinya.
Ini tidak akan bagus sama sekali, karena saya harus melakukan operasi lebih dari sekali, dengan input berbeda.
Apakah ada pendekatan keempat yang lebih baik? Jika tidak, pendekatan mana yang lebih baik, dan mengapa? Apakah ada sesuatu yang saya lewatkan?
sumber
Jawaban:
Opsi 1 adalah contoh enkapsulasi yang digunakan dengan benar. Anda ingin status internal disembunyikan dari kode luar.
Jika itu berarti kelas Anda hanya memiliki satu metode publik, maka biarlah. Akan jauh lebih mudah untuk dirawat.
Dalam OOP jika Anda memiliki kelas yang melakukan tepat 1 hal, memiliki permukaan publik kecil, dan menjaga semua keadaan internalnya tersembunyi, maka Anda (seperti yang dikatakan Charlie Sheen) WINNING .
Opsi 2 menderita kohesi rendah . Ini akan membuat perawatan lebih sulit.
Opsi 3, seperti opsi 2, menderita kohesi rendah, tetapi jauh lebih parah!
Sejarah telah menunjukkan bahwa kenyamanan variabel global tidak sebanding dengan biaya pemeliharaan brutal yang ditimbulkannya. Itu sebabnya Anda mendengar kentut tua seperti saya mengomentari enkapsulasi sepanjang waktu.
Pilihan yang menang adalah # 1 .
sumber
ThingDoer(var1, var2).do_it()
vs.do_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
, untuk membuat API eksternal sedikit lebih cantik.Saya pikir # 1 sebenarnya adalah pilihan yang buruk.
Mari pertimbangkan fungsi Anda:
Bagian mana dari data yang digunakan suboperation1? Apakah ia mengumpulkan data yang digunakan oleh suboperation2? Saat Anda meneruskan data dengan menyimpannya sendiri, saya tidak bisa memastikan bagaimana fungsionalitas terkait. Ketika saya melihat diri sendiri, beberapa atribut berasal dari konstruktor, beberapa dari panggilan ke the_public_method, dan beberapa ditambahkan secara acak di tempat lain. Menurut pendapat saya, ini berantakan.
Bagaimana dengan nomor # 2? Pertama, mari kita lihat masalah kedua:
Mereka akan berada dalam modul bersama, sehingga mereka benar-benar akan dikelompokkan bersama.
Menurut saya, ini bagus. Ini membuat eksplisit ketergantungan data dalam algoritme Anda. Dengan menyimpannya baik dalam variabel global atau pada diri, Anda menyembunyikan dependensi dan membuatnya tampak tidak terlalu buruk, tetapi masih ada.
Biasanya, ketika situasi ini muncul, itu berarti Anda belum menemukan cara yang tepat untuk menguraikan masalah Anda. Anda merasa aneh untuk memecah menjadi beberapa fungsi karena Anda mencoba membaginya dengan cara yang salah.
Tentu saja, tanpa melihat fungsi Anda yang sebenarnya, sulit menebak apa saran yang baik. Tapi Anda memberi sedikit petunjuk tentang apa yang Anda hadapi di sini:
Biarkan saya memilih contoh sesuatu yang agak sesuai dengan deskripsi Anda, sebuah installer. Pemasang harus menyalin banyak file, tetapi jika Anda membatalkannya sebagian, Anda harus memundurkan seluruh proses termasuk mengembalikan semua file yang Anda ganti. Algoritma untuk ini terlihat seperti:
Sekarang gandakan logika itu karena harus melakukan pengaturan registri, ikon program, dll, dan Anda punya cukup banyak kekacauan.
Saya pikir solusi # 1 Anda terlihat seperti:
Ini memang membuat keseluruhan algoritma lebih jelas, tetapi menyembunyikan cara berbagai bagian berkomunikasi.
Pendekatan # 2 terlihat seperti:
Sekarang secara eksplisit bagaimana potongan-potongan data berpindah antar fungsi, tetapi sangat canggung.
Jadi bagaimana seharusnya fungsi ini ditulis?
Objek FileTransactionLog adalah manajer konteks. Ketika copy_new_files menyalin file, ia melakukannya melalui FileTransactionLog yang menangani pembuatan salinan sementara dan melacak file mana yang telah disalin. Dalam kasus pengecualian itu menyalin file asli kembali, dan jika berhasil, itu akan menghapus salinan sementara.
Ini berhasil karena kami telah menemukan dekomposisi tugas yang lebih alami. Sebelumnya, kami menggabungkan logika tentang cara menginstal aplikasi dengan logika tentang cara memulihkan instalasi yang dibatalkan. Sekarang log transaksi menangani semua detail tentang file sementara dan pembukuan, dan fungsinya dapat fokus pada algoritma dasar.
Saya menduga bahwa kasus Anda berada di kapal yang sama. Anda perlu mengekstraksi elemen pembukuan menjadi semacam objek sehingga tugas kompleks Anda dapat diekspresikan lebih sederhana dan elegan.
sumber
Karena satu-satunya kelemahan jelas dari metode 1 adalah pola penggunaannya yang kurang optimal, saya pikir solusi terbaik adalah mendorong enkapsulasi satu langkah lebih jauh: Gunakan kelas, tetapi juga menyediakan fungsi berdiri bebas, yang hanya membangun objek, memanggil metode dan mengembalikan :
Dengan itu, Anda memiliki antarmuka sekecil mungkin untuk kode Anda, dan kelas yang Anda gunakan secara internal benar-benar hanya menjadi detail implementasi dari fungsi publik Anda. Kode panggilan tidak perlu tahu tentang kelas internal Anda.
sumber
Di satu sisi, pertanyaannya entah bagaimana agnostik bahasa; tetapi di sisi lain, implementasinya tergantung pada bahasa dan paradigmanya. Dalam hal ini adalah Python, yang mendukung banyak paradigma.
Selain solusi Anda, ada juga kemungkinan, untuk melakukan operasi melengkapi stateless dengan cara yang lebih fungsional, misalnya
Semuanya bermuara pada
Tetapi -Jika basis kode Anda adalah OOP, itu melanggar konsistensi jika tiba-tiba beberapa bagian ditulis dalam gaya fungsional (lebih).
sumber
Mengapa Mendesain saat saya bisa menjadi Pengodean?
Saya menawarkan pandangan yang bertentangan dengan jawaban yang saya baca. Dari perspektif itu semua jawaban dan terus terang bahkan pertanyaan itu sendiri fokus pada mekanisme pengkodean. Tapi ini masalah desain.
Ya, karena masuk akal untuk desain Anda. Ini mungkin kelas tersendiri atau bagian dari kelas lain atau perilakunya dapat didistribusikan di antara kelas-kelas.
Desain Berorientasi Objek adalah tentang Kompleksitas
Maksud dari OO adalah untuk berhasil membangun dan memelihara sistem yang besar dan kompleks dengan merangkum kode dalam hal sistem itu sendiri. "Tepat" desain mengatakan semuanya ada di beberapa kelas.
Desain OO secara alami mengelola kompleksitas terutama melalui kelas-kelas terfokus yang mematuhi prinsip tanggung jawab tunggal. Kelas-kelas ini memberikan struktur dan fungsionalitas sepanjang napas dan kedalaman sistem, berinteraksi dan mengintegrasikan dimensi-dimensi ini.
Mengingat hal itu, sering dikatakan bahwa fungsi yang tergantung pada sistem - kelas utilitas umum yang terlalu umum - adalah bau kode yang menunjukkan desain yang tidak memadai. Saya cenderung setuju.
sumber
Mengapa tidak membandingkan kebutuhan Anda dengan sesuatu yang ada di pustaka Python standar, dan kemudian lihat bagaimana itu diterapkan?
Catatan, jika Anda tidak membutuhkan objek, Anda masih bisa mendefinisikan fungsi di dalam fungsi. Dengan Python 3 ada
nonlocal
deklarasi baru untuk memungkinkan Anda mengubah variabel di fungsi induk Anda.Anda mungkin masih merasa berguna untuk memiliki beberapa kelas privat sederhana di dalam fungsi Anda untuk mengimplementasikan abstraksi dan merapikan operasi.
sumber
nonlocal
di mana pun di libs python saya yang terinstal. Mungkin Anda dapat mengambil hati daritextwrap.py
yang memilikiTextWrapper
kelas, tetapi jugadef wrap(text)
fungsi yang hanya membuatTextWrapper
contoh, memanggil.wrap()
metode di atasnya dan kembali. Jadi gunakan kelas, tetapi tambahkan beberapa fungsi kenyamanan.