Come cambia il pensiero sui modelli di progettazione e sulle pratiche OOP in linguaggi dinamici e tipicamente deboli?


11

Esiste già una domanda abbastanza utile in questo senso (" Non-OOP Design Patterns? "), Ma sono più curioso di un punto di vista transitorio per qualcuno che ha appena iniziato con linguaggi dinamici e debolmente tipizzati.

Cioè: diciamo che ho programmato in C ++, C # o Java per molti anni e ho assorbito molta saggezza lungo le linee dei modelli di progettazione GoF, i modelli di architettura delle applicazioni aziendali di Fowler , i principi SOLID , ecc. Ora io ' mi diletto in Ruby, Python, JavaScript, ecc. e mi chiedo come si applicano le mie conoscenze. Presumibilmente potrei fare traduzioni dirette in molti casi, ma quasi sicuramente non trarrebbe il massimo vantaggio dalla mia nuova impostazione. La digitazione di anatre da sola trasforma molto il mio pensiero basato sull'interfaccia.

Cosa rimane uguale? Cosa cambia Esistono principi guida come SOLID o modelli canonici (forse completamente nuovi) che un principiante con linguaggio dinamico dovrebbe conoscere?

Risposte:


7

Cosa rimane uguale? Cosa cambia

I modelli sono gli stessi. Le tecniche linguistiche cambiano.

Ci sono principi guida come SOLID,

Sì. In effetti, rimangono i principi guida. Niente cambia.

o schemi canonici (forse del tutto nuovi) che un principiante con un linguaggio dinamico dovrebbe conoscere?

Alcune cose sono uniche. Principalmente l'impatto è che le tecniche di implementazione cambiano.

Un modello è - beh - un modello . Non una legge Non una subroutine. Non una macro. È solo una buona idea che si ripete perché è una buona idea.

Le buone idee non passano di moda o cambiano radicalmente.

Altre note Python non è "tipizzato debolmente". È più fortemente tipizzato di Java o C ++ perché non c'è alcuna operazione di cast. [Sì, c'è un modo per confondere la classe associata a un oggetto, ma non è il tipo di cosa che viene fatto se non per dimostrare un punto debole e legalistico.]

Anche. La maggior parte dei modelli di progettazione si basa su diversi modi di sfruttare il polimorfismo.

Guarda State o Command o Memento come esempi. Hanno gerarchie di classi per creare stati polimorfici, comandi o ricordi di cambiamenti di stato. Nulla cambia in modo significativo quando lo fai in Python. Piccoli cambiamenti includono il rilassamento della precisa gerarchia di classi perché il polimorfismo in Python dipende da metodi comuni e non da antenati comuni.

Inoltre, alcuni schemi sono semplicemente un tentativo di ottenere un'associazione tardiva. La maggior parte dei modelli relativi a Factory è un tentativo di consentire una facile modifica a una gerarchia di classi senza ricompilare tutti i moduli C ++ nell'applicazione. Questa non è l'ottimizzazione interessante in un linguaggio dinamico. Tuttavia, una fabbrica come modo per nascondere i dettagli di implementazione ha ancora un valore enorme.

Alcuni schemi sono un tentativo di guidare il compilatore e il linker. Singleton , per esempio, esiste per creare globi confusi ma almeno incapsularli. Le lezioni singleton di Python non sono una prospettiva piacevole. Ma i moduli Python sono già dei singoli, quindi molti di noi usano semplicemente un modulo ed evitano di provare a pasticciare con una classe Singleton .


Non direi che "nulla cambia" con SOLID. A seconda della lingua e del suo modello di oggetti, il principio aperto-chiuso e il principio di sostituzione di Liskov possono essere entrambi privi di significato. (JavaScript e Go vengono in mente entrambi.)
Mason Wheeler,

@Mason Wheeler. Open-Closed è indipendente dalla lingua nella mia esperienza. Dovrai fornire alcuni esempi più concreti di come il design aperto-chiuso sia "insignificante" con JavaScript o Go. La sostituzione di Liskov, forse, non si applica a JavaScript, ma il modello essenziale - il polimorfismo - sembra ancora applicarsi.
S.Lott

@ S.Lott: Nizza aggiornamenti nella modifica; erano molto più interessanti della risposta originale: P. Grazie per aver corretto il mio errore Python. In generale, gli esempi di schemi specifici e il modo in cui si legano a linguaggi dinamici, polimorfismo, associazione tardiva, ecc. Sono perfetti.
Domenic,

@ S.Lott: Perché Open / Closed riguarda l'eredità, che quelle lingue non hanno. (Inoltre, l'idea di un oggetto "chiuso per modifica" non starebbe bene con molti programmatori di Ruby ...)
Mason Wheeler,

@Mason Wheeler: Grazie per il chiarimento su Open / Closed. Penso che l'eccezione JavaScript sia importante, ma poiché la domanda è così aperta (elencando JavaScript, Python e Ruby, così come un linguaggio chiamato ETC) non sono sicuro di come affrontare il caso speciale.
S.Lott

8

Peter Norvig ha affrontato proprio questa domanda nel 1998, leggendo http://norvig.com/design-patterns/ppframe.htm per una serie di cose dettagliate che ha notato, e http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatures for ulteriore discussione sul punto.

La versione breve è che quando il tuo linguaggio ha più funzionalità, i modelli di design ripetitivi tendono a diventare più semplici, spesso al punto di essere invisibili. Ha scoperto che questo era vero per la maggior parte dei modelli di progettazione identificati dal GoF.


8

La programmazione in un linguaggio dinamico orientato agli oggetti utilizza molti degli stessi schemi e principi, ma ci sono alcune modifiche e differenze dovute all'ambiente:

Sostituisci le interfacce con Duck Typing - Laddove la banda di quattro ti dicesse di usare una classe base astratta con pure funzioni virtuali e tu dovessi usare un'interfaccia in Java, in un linguaggio dinamico, hai solo bisogno di una comprensione. Poiché è possibile utilizzare qualsiasi oggetto ovunque e funzionerà perfettamente se implementa i metodi effettivamente chiamati, non è necessario definire un'interfaccia formale. Potrebbe valere la pena documentarne uno, in modo che sia chiaro ciò che è effettivamente richiesto.

Anche le funzioni sono oggetti : ci sono molti schemi che riguardano la separazione delle decisioni dall'azione; Comando, strategia, catena di responsabilità, ecc. In un linguaggio con funzioni di prima classe, è spesso ragionevole passare semplicemente una funzione invece di creare oggetti con .doIt()metodi. Questi schemi si trasformano in "usa una funzione di ordine superiore".

VENDUTO - Il principio di segregazione dell'interfaccia ha il maggior successo qui, a causa della mancanza di interfacce. Dovresti comunque considerare il principio, ma non puoi reinserirlo nel tuo codice. Solo la vigilanza personale ti proteggerà qui. Sul lato positivo, il dolore causato dalla violazione di questo principio è molto ridotto negli ambienti dinamici comuni.

"... nel mio particolare ... idioma!" - Ogni lingua ha buone pratiche e cattive pratiche, e dovrai impararle e seguirle, se vuoi il codice migliore in quelle lingue. Un modello di iteratore perfettamente scritto può essere deriso in una lingua con una comprensione dell'elenco integrata, per esempio.


3

Nella mia esperienza, alcuni Pattern sono ancora utili in Python e ancora più facili da configurare rispetto a linguaggi più statici. Alcuni modelli OTOH non sono necessari o addirittura disapprovati, come il modello Singleton. Utilizzare invece una variabile o una funzione a livello di modulo. Oppure usa il modello Borg.

Invece di impostare un modello di creazione è spesso sufficiente passare un callable intorno che crea oggetti. Potrebbe essere una funzione, un oggetto con un __call__metodo o persino una classe, dal momento che non esiste new()in Python, solo un'invocazione della classe stessa:

def make_da_thing(maker, other, stuff):
    da_thing = maker(other + 1, stuff + 2)
    # ... do sth
    return da_thing

def maker_func(x, y):
     return x * y

class MakerClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
...
a = make_da_thing(maker_func, 5, 8)
b = make_da_thing(MakerClass, 6, 7)

Il modello di stato e strategia condivide una struttura molto simile in linguaggi come C ++ e Java. Meno così in Python. Il modello di strategia rimane più o meno lo stesso, ma il modello di stato diventa per lo più inutile. Pattern di stato in linguaggi statici simula il cambio di classe in fase di esecuzione. In Python, puoi fare proprio questo: cambiare la classe di un oggetto in fase di esecuzione. Fintanto che lo fai in modo controllato e incapsulato, dovresti andare bene:

class On(object):
    is_on = True
    def switch(self):
        self.__class__ = Off

class Off(object):
    is_on = False
    def switch(self):
        self.__class__ = On
...

my_switch = On()
assert my_switch.is_on
my_switch.switch()
assert not my_switch.is_on

I modelli che si basano sulla spedizione di tipi statici non funzioneranno o funzioneranno in modo diverso. Non è necessario scrivere il codice della piastra della caldaia, ad esempio il modello visitatore: in Java e C ++ è necessario scrivere un metodo di accettazione in ogni classe visitabile, mentre in Python è possibile ereditare tale funzionalità attraverso una classe mixin, come Visitable:

class Visitable(object):
    def accept(self, visitor):
        visit = getattr(visitor, 'visit' + self.__class__.__name__)
        return visit(self)
...

class On(Visitable):
    ''' exactly like above '''

class Off(Visitable):
    ''' exactly like above '''

class SwitchStatePrinter(object): # Visitor
    def visitOn(self, switch):
         print 'the switch is on'
    def visitOff(self, switch):
         print 'the switch is off'

class SwitchAllOff(object): # Visitor
    def visitOn(self, switch):
         switch.switch()
    def visitOff(self, switch):
         pass
...
print_state = SwitchStatePrinter()
turn_em_off = SwitchAllOff()
for each in my_switches:
    each.accept(print_state)
    each.accept(turn_em_off)

Molte situazioni che richiedono l'applicazione di un Pattern in un linguaggio statico non lo fanno altrettanto in Python. Molte cose possono essere risolte con altre tecniche, come funzioni di ordine superiore (decoratori, fabbriche di funzioni) o meta-classi.


Mi rendo conto ora che la tua risposta copre principalmente la domanda che ho appena fatto: la sovrascrittura __class__per implementare una fabbrica in Python è una buona idea?
rd
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.