In che modo il modo di pensare sui modelli di progettazione e le pratiche OOP cambiano in linguaggi dinamici e debolmente tipizzati?

11

C'è già una domanda abbastanza utile in questo senso (" Modelli di progettazione non OOP? "), ma sono più curioso di un punto di vista transitorio per qualcuno che sta appena iniziando con le lingue dinamiche e debolmente tipizzate.

Cioè: diciamo che ho programmato in C ++, C # o Java per molti anni, e ho assorbito molta saggezza sulla falsariga dei modelli di design GoF, i Patterns of Enterprise Application Architecture di Fowler , principi SOLID , ecc. Ora mi sto dilettando in Ruby, Python, JavaScript, ecc. ., e chiedendosi come si applica la mia conoscenza. Presumibilmente, potrei fare traduzioni dirette in molti casi, ma quasi sicuramente non mi avvantaggerebbe del mio nuovo setting. Duck digitando da solo rende molte delle mie idee basate sull'interfaccia.

Che cosa rimane uguale? Cosa cambia? Ci sono dei principi guida come SOLID o schemi canonici (forse del tutto nuovi) che un newbie del linguaggio dinamico dovrebbe conoscere?

    
posta Domenic 19.04.2011 - 01:02
fonte

4 risposte

7

What stays the same? What changes?

I motivi sono gli stessi. Le tecniche linguistiche cambiano.

Are there guiding principles like SOLID,

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

or canonical patterns (perhaps entirely new ones) that a dynamic language newbie should know?

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

Un pattern è - beh - un pattern . Non una legge. Non una subroutine. Non una macro. È solo una buona idea che viene ripetuta perché è una buona idea.

Le buone idee non passano di moda o cambiano radicalmente.

Altre note. Python non è "debolmente tipizzato". È più strongmente digitato di Java o C ++ perché non ci sono operazioni di cast. [Sì, c'è un modo per fudge la classe associata ad un oggetto, ma non è il tipo di cosa che viene fatta se non per dimostrare un punto pignolo e legalistico.]

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

Guarda come Stato o Comando o Memento come esempi. Hanno gerarchie di classi per creare uno stato polimorfico, comandi o ricordi di cambiamenti di stato. Nulla cambia in modo significativo quando lo fai in Python. I cambiamenti minori includono il rilassamento della precisa gerarchia di classi perché il polimorfismo in Python dipende da metodi comuni non antenati comuni.

Inoltre, alcuni modelli sono semplicemente un tentativo di ottenere un legame tardivo. La maggior parte dei pattern associati a Factory sono un tentativo di consentire una facile modifica a una gerarchia di classi senza ricompilare ogni modulo C ++ nell'applicazione. Questa non è un'ottimizzazione interessante in un linguaggio dinamico. Tuttavia, una fabbrica come un modo per nascondere i dettagli di implementazione ha ancora un valore enorme.

Alcuni pattern sono un tentativo di guidare il compilatore e il linker. Singleton , ad esempio, esiste per creare confusi globals ma almeno incapsularli. Le classi di pitone singleton non sono una prospettiva piacevole. Ma i moduli Python sono già singleton, quindi molti di noi usano solo un modulo ed evitano di provare a fare confusione con una classe Singleton .

    
risposta data 19.04.2011 - 01:10
fonte
8

Nel 1998 Peter Norvig ha affrontato questa domanda, leggi link per una serie di cose dettagliate che ha notato, e link per ulteriori discussioni intorno al punto.

La versione breve è che quando la tua lingua ha più funzioni, i pattern di progettazione ripetitivi tendono a diventare più semplici - spesso fino al punto di essere invisibili. Ha scoperto che questo era vero per la maggior parte degli schemi di progettazione identificati dal GoF.

    
risposta data 19.04.2011 - 02:35
fonte
8

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

Sostituisci interfacce con Duck Typing - Dove la Gang of Four ti direbbe di usare una classe base astratta con pure funzioni virtuali, e useresti 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 di documentarlo , in modo che sia chiaro cosa è effettivamente richiesto.

Anche le funzioni sono oggetti - Esistono molti schemi che separano la decisione 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 metodi .doIt() . Questi schemi si trasformano in "usa una funzione di ordine superiore".

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

"... nel mio particolare ... Idiom!" - Ogni lingua ha buone pratiche e cattive pratiche, e dovrai impararle e seguirle, se tu voglio il miglior codice in quelle lingue. Per esempio, un pattern iteratore perfettamente scritto può essere deriso in una lingua con le comprensioni delle liste incorporate.

    
risposta data 22.09.2011 - 20:22
fonte
3

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

Invece di creare un Pattern Creazionale è spesso sufficiente passare un callable attorno a ciò che crea oggetti. Potrebbe trattarsi di una funzione, un oggetto con un metodo __call__ o anche una classe, poiché 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)

Lo stato e il modello di strategia condividono una struttura molto simile in linguaggi come C ++ e Java. Meno in Python. Il Pattern di strategia rimane più o meno lo stesso, ma State Pattern diventa per lo più inutile. State Pattern nei 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 runtime. Finché lo fai in modo controllato e incapsulato, dovresti stare 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 pattern che fanno affidamento su Static Type Dispatch non funzioneranno, o funzionano in modo abbastanza diverso. Non è necessario scrivere tanto codice della piastra della caldaia, ad es. Visitor Pattern: in Java e C ++ devi scrivere un metodo accept in ogni classe visitabile, mentre in Python puoi ereditare quella funzionalità attraverso una classe di 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 tanto in Python. Molte cose possono essere risolte con altre tecniche, come le funzioni di ordine superiore (decoratori, fabbriche di funzioni) o le meta-classi.

    
risposta data 19.04.2011 - 02:29
fonte