Apakah pewarisan Python merupakan gaya pewarisan "is-a" atau gaya komposisi?

10

Mengingat bahwa Python memungkinkan pewarisan berganda, seperti apa pewarisan idiomatik dalam Python?

Dalam bahasa dengan pewarisan tunggal, seperti Java, pewarisan akan digunakan ketika Anda bisa mengatakan bahwa satu objek "adalah-a" dari objek lain dan Anda ingin berbagi kode antara objek (dari objek induk ke objek anak). Misalnya, Anda dapat mengatakan bahwa Dogini adalah Animal:

public class Animal {...}
public class Dog extends Animal {...}

Tetapi karena Python mendukung banyak pewarisan, kita dapat membuat objek dengan menyusun banyak objek lain bersamaan. Perhatikan contoh di bawah ini:

class UserService(object):
    def validate_credentials(self, username, password):
        # validate the user credentials are correct
        pass


class LoggingService(object):
    def log_error(self, error):
        # log an error
        pass


class User(UserService, LoggingService):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        if not super().validate_credentials(self.username, self.password):
            super().log_error('Invalid credentials supplied')
            return False
         return True

Apakah ini merupakan pewarisan berganda dari Python yang dapat diterima atau digunakan dengan baik? Alih-alih mengatakan warisan adalah ketika satu objek "adalah-a" dari objek lain, kita membuat Usermodel yang terdiri dari UserServicedan LoggingService.

Semua logika untuk operasi basis data atau jaringan dapat disimpan terpisah dari Usermodel dengan meletakkannya di UserServiceobjek dan menyimpan semua logika untuk masuk LoggingService.

Saya melihat beberapa masalah dengan pendekatan ini adalah:

  • Apakah ini menciptakan objek Tuhan? Karena Usermewarisi dari, atau terdiri dari, UserServicedan LoggingServiceapakah itu benar-benar mengikuti prinsip tanggung jawab tunggal?
  • Untuk mengakses metode pada objek induk / objek berikutnya (misalnya, UserService.validate_credentialskita harus menggunakan super. Ini membuatnya sedikit lebih sulit untuk melihat objek mana yang akan menangani metode ini dan tidak sejelas, katakanlah , Instantiating UserServicedan melakukan sesuatu sepertiself.user_service.validate_credentials

Apa yang akan menjadi cara Pythonic untuk mengimplementasikan kode di atas?

Iain
sumber

Jawaban:

9

Apakah pewarisan Python merupakan gaya pewarisan "is-a" atau gaya komposisi?

Python mendukung kedua gaya. Anda menunjukkan hubungan komposisi has-a, di mana seorang Pengguna memiliki fungsi pencatatan dari satu sumber dan validasi kredensial dari sumber lain. Basis LoggingServicedan UserServiceadalah mixin: mereka memberikan fungsionalitas dan tidak dimaksudkan untuk dipakai sendiri.

Dengan menyusun mixin dalam tipe, Anda memiliki Pengguna yang dapat Masuk, tetapi yang harus menambahkan fungsionalitas instantiasinya sendiri.

Ini tidak berarti bahwa seseorang tidak dapat berpegang pada warisan tunggal. Python juga mendukung itu. Jika kemampuan Anda untuk berkembang terhambat oleh kompleksitas multiple inheritance, Anda dapat menghindarinya sampai Anda merasa lebih nyaman atau sampai pada suatu titik dalam desain di mana Anda yakin itu layak untuk diimbangi.

Apakah ini menciptakan objek Tuhan?

Penebangan tampaknya agak tangensial - Python memang memiliki modul logging sendiri dengan objek logger, dan konvensi adalah bahwa ada satu logger per modul.

Tapi sisihkan modul logging. Mungkin ini melanggar tanggung jawab tunggal, tetapi mungkin, dalam konteks spesifik Anda, sangat penting untuk mendefinisikan Pengguna. Tanggung jawab melecehkan bisa kontroversial. Tetapi prinsip yang lebih luas adalah bahwa Python memungkinkan pengguna untuk membuat keputusan.

Apakah super kurang jelas?

superhanya diperlukan ketika Anda perlu mendelegasikan kepada orang tua di Metode Resolution Order (MRO) dari dalam fungsi dengan nama yang sama. Menggunakannya alih-alih mengkode panggilan ke metode induk adalah praktik terbaik. Tetapi jika Anda tidak akan melakukan hard-code pada induknya, Anda tidak perlu super.

Dalam contoh Anda di sini, Anda hanya perlu melakukannya self.validate_credentials. selftidak lebih jelas, dari perspektif Anda. Mereka berdua mengikuti MRO. Saya hanya akan menggunakan masing-masing sesuai kebutuhan.

Jika Anda telah menelepon authenticate, validate_credentialssebagai gantinya, Anda harus menggunakan super(atau kode-keras induknya) untuk menghindari kesalahan rekursi.

Saran Kode Alternatif

Jadi, dengan asumsi semantiknya OK (seperti logging), yang akan saya lakukan adalah, di kelas User:

    def validate_credentials(self): # changed from "authenticate" to 
                                    # demonstrate need for super
        if not super().validate_credentials(self.username, self.password):
            # just use self on next call, no need for super:
            self.log_error('Invalid credentials supplied') 
            return False
        return True
Aaron Hall
sumber
1
Saya tidak setuju. Warisan selalu membuat salinan antarmuka publik kelas dalam subkelasnya. Ini bukan hubungan "memiliki". Ini subtyping, sederhana dan sederhana, dan karena itu tidak pantas untuk aplikasi yang dijelaskan.
Jules
@ Jules Apa yang tidak Anda setujui? Saya mengatakan banyak hal yang dapat dibuktikan, dan membuat kesimpulan yang secara logis mengikuti. Anda adalah salah ketika Anda mengatakan, "Warisan selalu membuat copy dari antarmuka publik dari kelas di subclass." Dalam Python, tidak ada salinan - metode-metode tersebut dilihat secara dinamis sesuai dengan urutan resolusi metode algoritma C3 (MRO).
Aaron Hall
1
Intinya bukan tentang detail spesifik tentang bagaimana implementasinya bekerja, ini tentang bagaimana antarmuka publik dari kelas itu terlihat. Dalam kasus contoh, Userobjek memiliki antarmuka mereka tidak hanya anggota yang ditentukan di Userkelas, tetapi juga yang didefinisikan dalam UserServicedan LoggingService. Ini bukan hubungan "has-a", karena antarmuka publik disalin (meskipun tidak melalui penyalinan langsung, tetapi lebih oleh pencarian tidak langsung ke antarmuka superclasses ').
Jules
Memiliki-a berarti Komposisi. Mixin adalah bentuk Komposisi. Kelas Pengguna adalah bukan UserService atau LoggingService, tetapi memiliki fungsi yang. Saya pikir warisan Python lebih berbeda dari Jawa daripada yang Anda sadari.
Aaron Hall
@ AaronHall Anda terlalu menyederhanakan (ini cenderung bertentangan dengan jawaban Anda yang lain , yang saya temukan secara kebetulan). Dari sudut pandang hubungan subtipe, Pengguna adalah UserService dan LoggingService. Sekarang, semangat di sini adalah untuk menyusun fungsi sehingga Pengguna memiliki fungsi ini dan itu. Mixin secara umum tidak perlu diimplementasikan dengan multiple-inheritance. Namun ini adalah cara yang biasa dilakukan dengan Python.
coredump
-1

Selain fakta bahwa itu memungkinkan beberapa superclasses, pewarisan Python tidak jauh berbeda dengan Java, yaitu anggota subkelas juga anggota dari masing-masing supertipe mereka [1]. Fakta bahwa Python menggunakan pengetikan bebek juga tidak ada bedanya: subclass Anda memiliki semua anggota superclasses, sehingga dapat digunakan oleh kode apa pun yang dapat menggunakan subclass tersebut. Fakta bahwa pewarisan berganda diterapkan secara efektif dengan menggunakan komposisi adalah herring merah: bahwa penyalinan otomatis properti dari satu kelas ke kelas lain adalah masalahnya, dan tidak masalah apakah itu menggunakan komposisi atau hanya menebak secara ajaib bagaimana anggotanya seharusnya untuk bekerja: memiliki mereka salah.

Ya, ini melanggar tanggung jawab tunggal, karena Anda memberi objek Anda kemampuan untuk melakukan tindakan yang bukan bagian logis dari apa yang mereka dirancang untuk dilakukan. Ya, itu menciptakan objek "dewa", yang pada dasarnya adalah cara lain untuk mengatakan hal yang sama.

Saat merancang sistem berorientasi objek dengan Python, pepatah yang sama yang diberitakan oleh buku desain Java juga berlaku: lebih suka komposisi daripada warisan. Hal yang sama berlaku untuk (kebanyakan [2]) sistem lain dengan banyak pewarisan.

[1]: Anda dapat menyebut bahwa hubungan "is-a", meskipun saya pribadi tidak suka istilah itu karena ini menyarankan gagasan pemodelan dunia nyata, dan pemodelan berorientasi objek tidak sama dengan dunia nyata.

[2]: Saya tidak begitu yakin tentang C ++. C ++ mendukung "warisan pribadi" yang pada dasarnya adalah komposisi tanpa perlu menentukan nama bidang ketika Anda ingin menggunakan anggota publik kelas yang diwariskan. Sama sekali tidak memengaruhi antarmuka publik kelas. Saya tidak suka menggunakannya, tapi saya tidak bisa melihat alasan bagus untuk tidak melakukannya.

Jules
sumber