Attualmente sto studiando un corso basato su Software Design e ho avuto una discussione in classe con il mio professore e alcuni compagni di classe su un problema rappresentato dal seguente scenario:
Scenario
Imagine we have a graphic application which lets us plan the interior design of our future house using a perspective from top. We are able to add or remove some elements like furniture and change their position, but obviously, we can't move the walls of the house because it was already built.
Soluzione 1
Per risolvere questo problema, alcuni dei miei compagni di classe hanno proposto una soluzione che potrebbe essere espressa utilizzando il seguente diagramma UML:
Comepuoivedere,hannoconcordatol'usodiun'interfacciacomunechiamata"Disegnabile" che rappresenta gli oggetti grafici visualizzati sull'interfaccia utente. La "App" di classe generale gestisce un elenco di Drawables e ognuno di essi ha un insieme di metodi. Questa interfaccia è implementata da diverse classi come Furniture, Wall o Window. Il fatto è che un oggetto Mobili può essere spostato, ma non i Muri né Windows. Quindi, un mobile implementerebbe il metodo "sposta" definito in Disegnabile. Al contrario, Wall e Window lo scriveranno semplicemente vuoto.
A questo punto, altri compagni di classe e io ci siamo lamentati di questa decisione perché il design non richiede di soddisfare i vincoli (come lo spostamento o meno degli oggetti Drawable, a seconda della loro natura). In questo modo, il design consentirà di spostare alcuni nuovi oggetti, anche se probabilmente non dovrebbero esserlo. Tuttavia, il professore ha detto che questo design è buono perché è flessibile e trasparente per la classe App, perché questa classe non sa che tipo di istanza sta gestendo.
Caso esteso
Inoltre, potremmo pensare a un caso estremo in cui aggiungiamo diversi metodi all'interfaccia Drawable, come "sell", "open" e "lend". Utilizzando lo stesso approccio descritto sopra, avremo un'interfaccia che potrebbe essere in grado di fare qualsiasi cosa, come Superman. Pertanto, ritengo che questa sia una cattiva soluzione di design, poiché stiamo mescolando comportamenti che appartengono a concetti diversi (mobile, vendibile, apribile e così via). Inoltre, stiamo consentendo a un oggetto Wall di implementare il metodo "sell", che non ha assolutamente senso ... ma il mio professore ha continuato a insistere sul suo punto di vista e non ha visto il problema.
Soluzione 2 (in base al caso esteso)
Altre persone suggerivano che potremmo aggiungere quelle interfacce (Movable, Sellable, Openable) e ognuna di esse erediterebbe dall'interfaccia Drawable. In questo modo:
- Un muro implementerebbe direttamente il disegno
- Un mobile implementerebbe mobili e vendibili
- Una finestra implementerebbe solo Openable
Il seguente diagramma riassume questo approccio:
[Modifica:ilmetododispostamentodelDrawabledeveessererimosso,questoèunerroreperchéoraèinseritonell'interfacciaMovable]
Domandeedubbi
Infine,unavoltacapitoilproblema,potrestiaiutarmierispondereaquestedomande,perfavore?
Nonstiamoinfrangendoilprincipiodiresponsabilitàsingolaconquestainterfacciasuper-mega-dio?
EntrambigliapproccisonovalidirispettoalprincipiodisostituzionediLiskov?Pensochesiarottonelprimocasoperchélepost-condizionisonointerrotteacausadialcunimetodinonfannonulla.Adognimodo,nelsecondoapprocciononsonosicuroancheperviadell'alberodiereditarietàdelleinterfacce
Forse,un'altraalternativapotrebbeessereladefinizionedioggettiDrawabledibase(conimetodi"click" e "draw"). Quindi, possiamo sfruttare un qualche tipo di meccanismo come il pattern Decorator per aggiungere dinamicamente comportamenti. Ma come?
-
Se usiamo una lingua senza interfacce come JavaScript o Python, come possiamo affrontare questo problema?