Modelli di design da evitare [chiuso]


105

Molte persone sembrano concordare sul fatto che il pattern Singleton ha una serie di inconvenienti e alcuni suggeriscono addirittura di evitare del tutto il pattern. C'è un'ottima discussione qui . Si prega di indirizzare qualsiasi commento sul modello Singleton a questa domanda.

La mia domanda : ci sono altri modelli di design che dovrebbero essere evitati o usati con grande cura?


Devo solo notare l'ottimo elenco di Design Antipatterns deviq.com/antipatterns
Developer

@casperOne perché hai chiuso la domanda? La domanda era legittima.
bleepzter

Risposte:


149

I modelli sono complessi

Tutti i modelli di design dovrebbero essere usati con cura. A mio parere , dovresti eseguire il refactoring verso i modelli quando c'è una ragione valida per farlo invece di implementare un modello immediatamente. Il problema generale con l'utilizzo dei pattern è che aggiungono complessità. L'uso eccessivo di pattern rende una data applicazione o sistema difficile da sviluppare e mantenere.

La maggior parte delle volte esiste una soluzione semplice e non è necessario applicare alcun modello specifico. Una buona regola pratica è usare un pattern ogni volta che parti di codice tendono a essere sostituite o devono essere cambiate spesso ed essere pronti ad accettare l'avvertenza del codice complesso quando si usa un pattern.

Ricorda che il tuo obiettivo dovrebbe essere la semplicità e utilizzare un modello se vedi una necessità pratica di supportare il cambiamento nel tuo codice.

Principi sui modelli

Può sembrare discutibile usare schemi se possono evidentemente portare a soluzioni troppo ingegnerizzate e complesse. Tuttavia è invece molto più interessante per un programmatore documentarsi sulle tecniche di progettazione e sui principi che gettano le basi per la maggior parte dei modelli. In effetti, uno dei miei libri preferiti sui "design patterns" lo sottolinea ribadendo quali principi sono applicabili al pattern in questione. Sono abbastanza semplici da essere utili dei modelli in termini di rilevanza. Alcuni dei principi sono abbastanza generali da comprendere più della programmazione orientata agli oggetti (OOP), come il principio di sostituzione di Liskov , purché tu possa costruire moduli del tuo codice.

Ci sono una moltitudine di principi di progettazione ma quelli descritti nel primo capitolo del libro GoF sono abbastanza utili per iniziare.

  • Programma su una "interfaccia", non su una "implementazione". (Gang of Four 1995: 18)
  • Preferisci "composizione oggetto" rispetto a "eredità di classe". (Gang of Four 1995: 20)

Lascia che ti affondino per un po '. Va notato che quando GoF è stato scritto un'interfaccia significa tutto ciò che è un'astrazione (che significa anche super classi), da non confondere con l'interfaccia come tipo in Java o C #. Il secondo principio deriva dall'osservato uso eccessivo dell'eredità che purtroppo è ancora comune oggi .

Da lì puoi leggere i principi SOLID che sono stati resi noti da Robert Cecil Martin (alias. Uncle Bob) . Scott Hanselman ha intervistato lo zio Bob in un podcast su questi principi :

  • S Responsabilità ingle Principio
  • O pen Closed Principle
  • Principio di sostituzione di L iskov
  • I nterface segregazione Principio
  • D ependency Inversion Principle

Questi principi sono un buon inizio per leggere e discutere con i tuoi colleghi. Potresti scoprire che i principi si intrecciano tra loro e con altri processi come la separazione delle preoccupazioni e l' iniezione di dipendenza . Dopo aver fatto TDD per un po ', potresti anche scoprire che questi principi vengono naturalmente nella pratica, poiché devi seguirli in una certa misura per creare test unitari isolati e ripetibili .


7
+1 Risposta molto buona. Sembra che ogni programmatore (principiante) oggi conosca i suoi schemi di progettazione o almeno sappia che esistono. Ma moltissimi non hanno mai sentito parlare, figuriamoci applicare, alcuni dei principi assolutamente essenziali come la Responsabilità Unica per gestire la complessità del loro codice.
eljenso

21

Quello di cui gli stessi autori di Design Patterns si preoccupavano maggiormente era il pattern "Visitor".

È un "male necessario", ma viene spesso utilizzato in modo eccessivo e la necessità di esso spesso rivela un difetto più fondamentale nel tuo design.

Un nome alternativo per il pattern "Visitor" è "Multi-dispatch", perché il pattern Visitor è ciò che si ottiene quando si desidera utilizzare un linguaggio OO di invio di un solo tipo per selezionare il codice da utilizzare in base al tipo di due (o più) oggetti diversi.

L'esempio classico è che hai l'intersezione tra due forme, ma c'è un caso ancora più semplice che spesso viene trascurato: confrontare l'uguaglianza di due oggetti eterogenei.

Ad ogni modo, spesso ti ritrovi con qualcosa del genere:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

Il problema con questo è che hai accoppiato tutte le tue implementazioni di "IShape". Hai insinuato che ogni volta che desideri aggiungere una nuova forma alla gerarchia dovrai cambiare anche tutte le altre implementazioni "Forma".

A volte, questo è il design minimale corretto, ma pensaci bene. Il tuo progetto richiede davvero che tu debba spedire su due tipi? Sei disposto a scrivere ciascuna delle esplosioni combinatorie dei multi-metodi?

Spesso, introducendo un altro concetto puoi ridurre il numero di combinazioni che dovrai effettivamente scrivere:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Certo, dipende - a volte è davvero necessario scrivere codice per gestire tutti questi casi diversi - ma vale la pena fare una pausa e riflettere prima di fare il grande passo e utilizzare Visitor. Potrebbe farti risparmiare molto dolore in seguito.


2
Parlando di visitatori, lo zio Bob lo usa "sempre" butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike

3
@ Paul Hollingsworth Puoi fornire un riferimento a dove si dice che gli autori di Design Patterns sono preoccupati (e perché sono preoccupati)?
m3th0dman

16

Singleton: una classe che utilizza singleton X ha una dipendenza da essa difficile da vedere e difficile da isolare per i test.

Sono usati molto spesso perché sono convenienti e facili da capire, ma possono davvero complicare i test.

Vedi I single sono bugiardi patologici .


1
Possono anche semplicemente eseguire test in quanto possono darti un unico punto per iniettare un oggetto Mock. Sta tutto nel trovare il giusto equilibrio.
Martin Brown,

1
@ Martin: Se ovviamente è possibile cambiare un singelton per il test (se non si utilizza l'implementazione del singelton standard), ma com'è più facile che passare l'implementazione del test nel costruttore?
orip

14

Credo che il modello Metodo modello sia generalmente un modello molto pericoloso.

  • Molte volte utilizza la tua gerarchia ereditaria per "ragioni sbagliate".
  • Le classi base hanno la tendenza a diventare disseminate di tutti i tipi di codice non correlato.
  • Ti costringe a bloccare il design, spesso all'inizio del processo di sviluppo. (Blocco prematuro in molti casi)
  • Cambiare questo in una fase successiva diventa sempre più difficile.

2
Vorrei aggiungere che ogni volta che usi il metodo del modello probabilmente sei meglio usare la strategia. Il problema con TemplateMethod è che c'è un rientro tra la classe base e la classe derivata, che è spesso eccessivamente accoppiata.
Paul Hollingsworth,

5
@Paul: il metodo Template è ottimo se usato correttamente, cioè quando le parti che variano devono sapere molto sulle parti che non lo fanno. La mia opinione è che la strategia dovrebbe essere utilizzata quando il codice di base chiama solo il codice personalizzato e il metodo del modello dovrebbe essere utilizzato quando il codice personalizzato ha intrinsecamente bisogno di conoscere il codice di base.
dsimcha

sì dsimcha, sono d'accordo ... fintanto che il progettista della classe è a conoscenza di questo.
Paul Hollingsworth

9

Non penso che dovresti evitare Design Patterns (DP) e non penso che dovresti costringerti a usare DP quando pianifichi la tua architettura. Dovremmo usare i DP solo quando emergono naturalmente dalla nostra pianificazione.

Se definiamo fin dall'inizio che vogliamo utilizzare un dato DP, molte delle nostre future decisioni di progettazione saranno influenzate da quella scelta, senza alcuna garanzia che il DP che abbiamo scelto sia adatto alle nostre esigenze.

Una cosa che inoltre non dovremmo fare è trattare un DP come un'entità immutabile, dovremmo adattare il modello alle nostre esigenze.

Quindi, riassumendo, non credo che dovremmo evitare i DP, dovremmo abbracciarli quando stanno già prendendo forma nella nostra architettura.


7

Penso che Active Record sia un pattern abusato che incoraggia a mescolare la logica aziendale con il codice di persistenza. Non fa un ottimo lavoro nel nascondere l'implementazione dell'archiviazione dal livello del modello e lega i modelli a un database. Ci sono molte alternative (descritte in PoEAA) come Table Data Gateway, Row Data Gateway e Data Mapper che spesso forniscono una soluzione migliore e certamente aiutano a fornire una migliore astrazione allo storage. Inoltre, non dovrebbe essere necessario archiviare il modello in un database; che dire di archiviarli come XML o accedervi utilizzando i servizi web? Quanto sarebbe facile cambiare il meccanismo di archiviazione dei tuoi modelli?

Detto questo, Active Record non è sempre negativo ed è perfetto per applicazioni più semplici in cui le altre opzioni sarebbero eccessive.


1
Piuttosto vero, ma dipende in una certa misura dall'implementazione.
Mike Woodhouse,

6

È semplice ... evita modelli di design che non ti sono chiari o quelli in cui non ti senti a tuo agio .

Per citarne alcuni ...

ci sono alcuni schemi non pratici , come ad esempio:

  • Interpreter
  • Flyweight

ce ne sono anche alcuni più difficili da comprendere , come ad esempio:

  • Abstract Factory - Il modello di fabbrica astratto completo con famiglie di oggetti creati non è così facile come sembra
  • Bridge - Può diventare troppo astratto, se l'astrazione e l'implementazione sono divise in sottoalberi, ma in alcuni casi è un modello molto utilizzabile
  • Visitor - La comprensione del meccanismo di doppio invio è davvero un MUST

e ci sono alcuni modelli che sembrano terribilmente semplici , ma non sono una scelta così chiara a causa di vari motivi legati al loro principio o implementazione:

  • Singleton - schema non proprio del tutto cattivo, solo TROPPO abusato (spesso lì, dove non è adatto)
  • Observer - ottimo modello ... rende il codice molto più difficile da leggere e da eseguire il debug
  • Prototype - commercia i controlli del compilatore per il dinamismo (che può essere buono o cattivo ... dipende)
  • Chain of responsibility - troppo spesso solo forzatamente / artificialmente spinto nel design

Per quelli "poco pratici", si dovrebbe davvero pensare prima di usarli, perché di solito c'è una soluzione più elegante da qualche parte.

Per quelli "più difficili da afferrare" ... sono davvero di grande aiuto, quando vengono utilizzati in luoghi adatti e quando sono implementati bene ... ma sono un incubo, se usati in modo improprio.

Ora, qual è il prossimo ...


Il modello Flyweight è un must in qualsiasi momento quando usi una risorsa, spesso un'immagine, più di una volta. Non è uno schema, è una soluzione.
Cengiz Kandemir

5

Spero di non essere picchiato troppo per questo. Christer Ericsson ha scritto due articoli ( uno , due ) sull'argomento dei modelli di progettazione nel suo blog sul rilevamento delle collisioni in tempo reale . Il suo tono è piuttosto aspro, e forse un po 'provocatorio, ma l'uomo sa il fatto suo, quindi non lo liquiderei come delirio di un pazzo.


Letture interessanti. Grazie per i link!
Bombe

3
I deficienti producono un codice cattivo. Gli idioti con schemi producono un codice peggiore degli idioti che non hanno mai visto schemi? Non credo che lo facciano. Per le persone intelligenti, i modelli forniscono un vocabolario ben noto, che facilita lo scambio di idee. La soluzione: imparare i modelli e trattare solo con programmatori intelligenti.
Martin Brown,

Non credo che sia davvero possibile per un vero idiota produrre codice peggiore, indipendentemente dallo strumento che sta usando
1800 INFORMAZIONI

1
Penso che il suo esempio con il suo test universitario dimostri solo che le persone che disprezzano il loro dominio del problema e la riluttanza a studiarlo per più di poche ore in un singolo fine settimana produrranno risposte errate quando tenteranno di risolvere i problemi.
scriptocalypse

5

Alcuni dicono che il localizzatore di servizi sia un anti pattern.


È anche importante notare che a volte è necessario il localizzatore di servizi. Ad esempio, quando non si dispone del controllo appropriato della creazione di istanze dell'oggetto (ad esempio attributi con parametri non costanti in C #). Ma è anche possibile utilizzare il localizzatore di servizi CON l'iniezione di ctor.
Sinaesthetic

2

Credo che il pattern dell'osservatore abbia molto di cui rispondere, funziona in casi molto generali, ma man mano che i sistemi diventano più complessi diventa un incubo, che richiede notifiche OnBefore (), OnAfter () e spesso la pubblicazione di attività asincrone per evitare ri- entrancy. Una soluzione molto migliore è sviluppare un sistema di analisi automatica delle dipendenze che strumenti tutti gli accessi agli oggetti (con barriere di lettura) durante i calcoli e crei automaticamente un bordo in un grafico delle dipendenze.


4
Ho capito tutto nella tua risposta fino alla parola "A"
1800 INFORMAZIONI

Potrebbe essere necessario espandere o creare un collegamento a questa analisi automatica delle dipendenze di cui parli. Anche in .NET vengono utilizzati delegati / eventi al posto del modello osservatore.
Spoike

3
@Spoike: i delegati / gli eventi sono un'implementazione del modello di osservazione
orip

1
Il mio rancore personale nei confronti di Observer è che può creare perdite di memoria nelle lingue raccolte dai rifiuti. Quando hai finito con un oggetto, devi ricordare che l'oggetto non verrà ripulito.
Martin Brown,

@orip: sì, ecco perché usi delegati / eventi. ;)
Spoike

2

A complemento del post di Spoike, Refactoring to Patterns è una buona lettura.


In realtà mi sono collegato al catalogo del libro su Internet. :)
Spoike

Oh! Non mi sono preoccupato di passarci sopra. In realtà, subito dopo aver finito la domanda, mi è venuto in mente questo libro e poi ho visto la tua risposta. Non potevo impedirmi di postarlo. :)
Adeel Ansari

0

Iterator è un altro pattern GoF da evitare, o almeno da usarlo solo quando nessuna delle alternative è disponibile.

Le alternative sono:

  1. per ogni ciclo. Questa costruzione è presente nella maggior parte delle lingue tradizionali e può essere utilizzata per evitare iteratori nella maggior parte dei casi.

  2. selettori à la LINQ o jQuery. Dovrebbero essere usati quando for-each non è appropriato perché non tutti gli oggetti dal contenitore dovrebbero essere elaborati. A differenza degli iteratori, i selettori consentono di manifestare in un unico punto il tipo di oggetti da elaborare.


Sono d'accordo con i selettori. Foreach è un iteratore, la maggior parte dei linguaggi OO fornisce un'interfaccia iterabile che puoi implementare per consentire un foreach.
Neil Aitken

In alcuni linguaggi la costruzione for-each può essere implementata tramite iteratori, ma il concetto di essa è in realtà di più alto livello e più vicino ai selettori. Quando si utilizza for-each sviluppatore dichiara esplicitamente che tutti gli elementi del contenitore devono essere elaborati.
Volodymyr Frolov

Gli iteratori sono un ottimo modello. L'anti-pattern implementerebbe IEnumerable / IEnumerator senza un iteratore. Credo che LINQ sia stato reso possibile tramite l' yielditeratore. Eric White ha discusso molto su questo in C # 3.0: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Inoltre, controlla la discussione di Jeremy Likness sulle coroutine con iteratori: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .

@ Ryan Riley, gli iteratori sono oggetti di basso livello e quindi devono essere evitati nella progettazione e nel codice di alto livello. I dettagli sull'implementazione di iteratori e diversi tipi di selettori non sono importanti qui. I selettori, a differenza degli iteratori, consentono al programmatore di esprimere esplicitamente ciò che desidera elaborare e in modo che siano di alto livello.
Volodymyr Frolov

Fwiw, la sintassi F # alternativa, simile a LINQ, è `List.map (fun x -> x.Value) xs`, che è lunga quanto la comprensione dell'elenco.
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.