Scegliere tra il minore di due mali - cattivo design?

-2

Ho due classi, Bar e Baz . Entrambe le sottoclasse della classe astratta Foo che ha un metodo doThing() . Bar implementa un'interfaccia funzionale Now che contiene il metodo doThingNow() mentre Baz implementa un'interfaccia funzionale Later che contiene il metodo doThingLater() . È probabile che avrò più classi lungo la linea che sottoclasse Foo , ma mi aspetto che tutte implementino Now o Later . Sto discutendo tra due potenziali progetti, ma spero che qualcuno possa aiutarmi a capire perché uno potrebbe essere preferibile all'altro.

Progetto 1: Rendo doThing() astratto e costringo ogni sottoclasse a implementarlo. L'implementazione per ogni classe chiamerà semplicemente il metodo dell'interfaccia.

Progettazione 2: implemento doThing come segue:

public void doThing() {
    if (this instanceof Now) {
        ((Now) this).doThingNow();
    } else if (this instanceof Later) {
        ((Later) this).doThingLater();
    } else {
        throw new RuntimeException("Foo subclass must implement Now or Later!");
    }
}

La progettazione sembra richiedere più codice duplicato in sottoclassi e non garantisce che le sottoclassi implementeranno Now o Later . Disegna due modelli per il comportamento che voglio, ma a dire il vero mi sembra che mi sia sbagliato a causa del casting. Non mi sento particolarmente bene nei due approcci, il che mi fa pensare che ci sia un difetto di progettazione più fondamentale con quello che sto facendo. Uno di questi modelli è estremamente meno flessibile / manutenibile e, in tal caso, perché? Se eliminassi entrambi i pattern a favore di un'alternativa diversa, sarei felice di ascoltare i suggerimenti e la motivazione che li spinge.

    
posta chemdog95 21.11.2018 - 06:41
fonte

3 risposte

3

Il motivo per cui ti dà così tanti problemi è che è già una pessima idea. Dovresti davvero considerare il refactoring completamente in modo da non dover prendere una decisione.

Il problema è che la semantica di doThing() sembra essere molto mal definita. Una sottoclasse fa sì che faccia una cosa (un'operazione "ora") e una sottoclasse la definisce per fare un'altra cosa (fare una coda anche per essere fatto "dopo"). La ragione per cui stai lottando con il modo migliore per legare questi due insieme nella classe Foo è che loro non dovrebbero essere legati insieme.

Se è necessario eseguire uno di questi progetti, è consigliabile Design 1 a meno che non si abbia una valida ragione per scegliere Design 2. Design 2 è in genere considerato come anti-pattern. Se vedi un codice del genere, dice "Gli scrittori di Foo sapevano in ogni modo che Foo sarebbe stato usato e tutte le condizioni in cui sarebbe stato utilizzato. Conoscevano tutte le sottoclassi e come dovrebbero operare". In generale, questo è contro lo spirito della programmazione OO.

Naturalmente, ci sono delle eccezioni a ogni regola. Un caso studio è alberi. A volte hai una struttura come un grafico di scena o un albero di sintassi astratto in cui sai in anticipo tutti dei possibili nodi che potrebbero mai apparire. In casi come questo, può essere conveniente implementare funzioni come bool isNow() o Later asLater() piuttosto che fare l'approccio OO più formale usando i visitatori. Ma questo dovrebbe essere trattato come un caso speciale. Se non sei positivo sei in questa situazione, non dovresti seguire questa strada. Quindi il Design 1 funziona meglio del Design 2. Ma dovresti davvero essere cauto di avere una funzione astratta doThing() che fa due operazioni piuttosto diverse.

Se la tua funzione non è altro che doThing() o execute() , potresti farla franca. Ma se quello che hai veramente scritto era qualcosa come doConnect() , dove Now crea immediatamente una connessione con un server mentre Later fa una chiamata asincrona per effettuare la connessione in un secondo momento, probabilmente ti troverai nei guai confondendoti come a cosa sta realmente accadendo quando chiami quella funzione.

    
risposta data 21.11.2018 - 06:51
fonte
0

Se stiamo parlando di un'azione sincrona o asincrona, è possibile forzare un'azione asincrona per essere sincrona abbastanza facilmente. Non so quale lingua stai usando, non hai specificato, ma la maggior parte delle lingue fornisce un modo per far sì che un thread attenda l'altro (la terminologia è diversa, ma solitamente è come attendere / partecipare). Ricordati solo dei problemi di deadlock (l'attesa di un thread che aspetti che il thread tuo sia terminato non è l'ideale).

Poiché questo è possibile , supponendo che il tuo metodo doThing stia eseguendo la stessa azione in entrambi i casi, la soluzione è di avere una chiamata asincrona a doThing di default e lasciare che il chiamante sincronizzi come desiderato. Se preferisci, puoi fare in modo che Foo abbia un doThingAsync e un doThingSync , in cui doThingSync in Foo chiama semplicemente doThingAsync e attende che finisca.

Ma supponiamo per un minuto che questo non fosse il tuo caso e che doThingNow e doThingLater facciano cose separate. L'errore è la progettazione in modo tale che Foo sappia qualcosa su come è implementato.

Se si desidera che vengano intraprese azioni diverse quando viene chiamato doThing , ogni implementazione deve semplicemente gestirla in modo diverso. Pertanto, ogni chiamata a doThing di Foo chiama ogni volta il metodo appropriato, non sono richieste interfacce.

    
risposta data 21.11.2018 - 09:00
fonte
0

Questo non suona come una differenza che dovrebbe essere espressa tramite sottotipi. Se l'azione di cui stiamo parlando non è centrale rispetto allo scopo della classe, cioè è giusto farlo in modo sincrono o asincrono senza toccare la funzionalità di base, allora sembra più un'impostazione di configurazione che potrebbero avere istanze della classe - " lazyInitialization = true o qualcosa del genere. Quindi sarebbe necessario solo il metodo init() e diverse istanze farebbero cose diverse come configurato senza dover scrivere più percorsi di codice per questo.

    
risposta data 21.11.2018 - 09:28
fonte

Leggi altre domande sui tag