Biorąc pod uwagę, że Python pozwala na wielokrotne dziedziczenie, jak wygląda dziedziczenie idiomatyczne w Pythonie?
W językach z pojedynczym dziedziczeniem, takich jak Java, dziedziczenie byłoby stosowane, gdy można by powiedzieć, że jeden obiekt „jest-a” innego obiektu i chcesz współdzielić kod między obiektami (od obiektu nadrzędnego do obiektu podrzędnego). Na przykład można powiedzieć, że Dog
jest to Animal
:
public class Animal {...}
public class Dog extends Animal {...}
Ale ponieważ Python obsługuje wielokrotne dziedziczenie, możemy utworzyć obiekt, tworząc wiele innych obiektów razem. Rozważ poniższy przykład:
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
Czy jest to dopuszczalne lub dobre wykorzystanie wielokrotnego dziedziczenia w Pythonie? Zamiast mówić, że dziedziczenie ma miejsce, gdy jeden obiekt „jest-a” innego obiektu, tworzymy User
model złożony z UserService
i LoggingService
.
Całą logikę operacji bazy danych lub sieci można trzymać osobno od User
modelu, umieszczając je w UserService
obiekcie i zachowując całą logikę logowania LoggingService
.
Widzę pewne problemy z tym podejściem:
- Czy to tworzy obiekt Boga? Skoro
User
dziedziczy lub składa się z niegoUserService
iLoggingService
czy rzeczywiście przestrzega zasady pojedynczej odpowiedzialności? - Aby uzyskać dostęp do metod na obiekcie nadrzędnym / następnym w linii (np.
UserService.validate_credentials
Musimy użyćsuper
. To sprawia, że nieco trudniej jest zobaczyć, który obiekt będzie obsługiwał tę metodę i nie jest tak jasny, jak powiedzmy , tworzenie instancjiUserService
i robienie czegoś takiegoself.user_service.validate_credentials
Jaki byłby Pythoniczny sposób na implementację powyższego kodu?
User
obiekty mają w swoich interfejsach nie tylko elementy zdefiniowane wUser
klasie, ale także elementy zdefiniowane wUserService
iLoggingService
. Nie jest to relacja typu „ nie ma”, ponieważ interfejs publiczny jest kopiowany (choć nie przez bezpośrednie kopiowanie, ale raczej przez pośrednie wyszukiwanie interfejsów superklas).Poza faktem, że pozwala na wiele nadklas, dziedziczenie Pythona nie różni się zasadniczo od Java, tzn. Członkowie podklasy są również członkami każdego z ich nadtypów [1]. Fakt, że Python używa pisania kaczego, również nie ma znaczenia: twoja podklasa ma wszystkie elementy swoich nadklas, więc może być używana przez dowolny kod, który mógłby używać tych podklas. Fakt, że wielokrotne dziedziczenie jest skutecznie wdrażane przy użyciu kompozycji, jest czerwonym śledziem: problemem jest automatyczne kopiowanie właściwości jednej klasy do drugiej, i nie ma znaczenia, czy używa ona kompozycji, czy po prostu magicznie zgaduje, w jaki sposób członkowie powinni do pracy: posiadanie ich jest złe.
Tak, narusza to pojedynczą odpowiedzialność, ponieważ dajesz swoim przedmiotom możliwość wykonywania działań, które nie są logicznie częścią tego, do czego zostały zaprojektowane. Tak, tworzy obiekty „boga”, co jest zasadniczo innym sposobem na powiedzenie tego samego.
Podczas projektowania systemów obiektowych w Pythonie obowiązuje również ta sama maksyma, którą głoszą podręczniki projektowe Java: wolą kompozycję niż dziedziczenie. To samo dotyczy (większości [2]) innych systemów z wielokrotnym dziedziczeniem.
[1]: można nazwać to relacją „jest-a”, chociaż osobiście nie podoba mi się ten termin, ponieważ sugeruje ideę modelowania świata rzeczywistego, a modelowanie obiektowe nie jest tym samym co świat rzeczywisty.
[2]: Nie jestem pewien co do C ++. C ++ obsługuje „prywatne dziedziczenie”, które jest zasadniczo kompozycją bez konieczności określania nazwy pola, gdy chcesz użyć publicznych elementów odziedziczonej klasy. W ogóle nie wpływa to na publiczny interfejs klasy. Nie lubię go używać, ale nie widzę żadnego dobrego powodu, aby tego nie robić .
źródło