L'ereditarietà di Python è uno stile di ereditarietà "is-a" o uno stile compositivo?

10

Dato che Python consente l'ereditarietà multipla, che aspetto ha l'ereditarietà idiomatica in Python?

Nelle lingue con ereditarietà singola, come Java, l'ereditarietà verrebbe utilizzata quando si potrebbe dire che un oggetto "è-a" di un altro oggetto e si desidera condividere il codice tra gli oggetti (dall'oggetto padre all'oggetto figlio ). Ad esempio, potresti dire che Dog è un Animal :

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

Ma poiché Python supporta l'ereditarietà multipla, possiamo creare un oggetto componendo molti altri oggetti insieme. Considera l'esempio seguente:

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

È un uso accettabile o buono dell'ereditarietà multipla in Python? Invece di dire che l'ereditarietà è quando un oggetto "è-a" di un altro oggetto, creiamo un modello User composto da UserService e LoggingService .

Tutta la logica per le operazioni di database o di rete può essere mantenuta separata dal modello User inserendole nell'oggetto UserService e mantenendo tutta la logica per la registrazione in LoggingService .

Vedo che alcuni problemi con questo approccio sono:

  • Questo crea un oggetto divino? Dal momento che User eredita da, o è composto da, UserService e LoggingService sta davvero seguendo il principio della responsabilità singola?
  • Per accedere ai metodi su un oggetto principale / next-in-line (ad esempio, UserService.validate_credentials dobbiamo usare super . Ciò rende un po 'più difficile vedere quale oggetto gestirà questo metodo e non è chiaro come, diciamo, istanziando UserService e facendo qualcosa come self.user_service.validate_credentials

Quale sarebbe il modo Python per implementare il codice sopra?

    
posta Iain 02.01.2016 - 01:59
fonte

2 risposte

9

Is Python's inheritance an “is-a” style of inheritance or a compositional style?

Python supporta entrambi gli stili. Stai dimostrando la relazione has-a di composizione, in cui un utente ha funzionalità di registrazione da una fonte e convalida delle credenziali da un'altra fonte. Le basi LoggingService e UserService sono mixine: forniscono funzionalità e non sono intese per essere istanziate da sole.

Compilando i mixins nel tipo, hai un Utente che può registrare, ma che deve aggiungere la propria funzionalità di istanziazione.

Questo non significa che non si possa attaccare all'eredità singola. Python supporta anche questo. Se la tua capacità di sviluppo è ostacolata dalla complessità dell'ereditarietà multipla, puoi evitarla finché non ti senti più a tuo agio o raggiungi un punto in cui ritieni che valga la pena.

Does this create a God object?

La registrazione sembra in qualche modo tangenziale - Python ha il proprio modulo di registrazione con un oggetto logger e la convenzione è che esiste un logger per modulo.

Ma metti da parte il modulo di registrazione. Forse questo viola una singola responsabilità, ma forse, nel tuo contesto specifico, è cruciale definire un utente. La responsabilizzazione della responsabilità può essere controversa. Ma il principio più ampio è che Python consente all'utente di prendere una decisione.

È super meno chiaro?

super è necessario solo quando è necessario delegare a un genitore nell'ordine di risoluzione dei metodi (MRO) dall'interno di una funzione con lo stesso nome. Usandolo invece di hard-coding una chiamata al metodo dei genitori è una best practice. Ma se non hai intenzione di codificare hard il genitore, non hai bisogno di super .

Nel tuo esempio qui, devi solo fare self.validate_credentials . self non è più chiaro, dal tuo punto di vista. Entrambi seguono l'MRO. Vorrei semplicemente utilizzare ciascuno dove appropriato.

Se avessi chiamato authenticate , validate_credentials , avresti avuto bisogno di usare super (o il codice sorgente del genitore) per evitare un errore di ricorsione.

Suggerimento per codice alternativo

Quindi, supponendo che la semantica sia OK (come la registrazione), quello che farei è, in classe 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
    
risposta data 02.01.2016 - 02:29
fonte
-1

Oltre al fatto che consente più superclassi, l'ereditarietà di Python non è sostanzialmente diversa da quella di Java, cioè i membri di una sottoclasse sono anche membri di ciascuno dei loro supertipo [1]. Il fatto che Python usi la tipizzazione anatra non fa alcuna differenza: la sottoclasse ha tutti i membri delle sue superclassi, quindi può essere usata da qualsiasi codice che possa usare quelle sottoclassi. Il fatto che l'ereditarietà multipla sia implementata efficacemente usando la composizione è una falsa pista: la copia automatizzata delle proprietà di una classe in un'altra è il problema, e non importa se usa la composizione o semplicemente indovina in modo magico come si suppone che i membri lavorare: averli è sbagliato.

Sì, questo viola una singola responsabilità, perché dai ai tuoi oggetti la possibilità di eseguire azioni che non sono logicamente parte di ciò che sono progettate per fare. Sì, crea oggetti "dio", che è essenzialmente un altro modo di dire la stessa cosa.

Quando si progettano sistemi orientati agli oggetti in Python, si applica anche la stessa massima predicata dai libri di progettazione Java: preferisci la composizione all'ereditarietà. Lo stesso vale per (la maggior parte [2]) altri sistemi con ereditarietà multipla.

[1]: potresti chiamarla una relazione "è-a", anche se personalmente non mi piace il termine perché suggerisce l'idea di modellare il mondo reale, e la modellazione orientata agli oggetti non è la stessa del vero mondo.

[2]: Non sono così sicuro di C ++. C ++ supporta "ereditarietà privata" che è essenzialmente composizione senza dover specificare un nome di campo quando si desidera utilizzare i membri pubblici della classe ereditata. Non ha alcun effetto sull'interfaccia pubblica della classe. Non mi piace come , ma non vedo alcun motivo valido per non farlo.

    
risposta data 23.05.2016 - 20:23
fonte