L'idea alla base di OOP è che i dati e il comportamento (su quei dati) sono inseparabili e sono accoppiati all'idea di un oggetto di una classe. L'oggetto ha dati e metodi che funzionano con quello (e altri dati). Ovviamente secondo i principi di OOP, gli oggetti che sono solo dati (come le strutture C) sono considerati un anti-modello.
Fin qui tutto bene.
Il problema è che ho notato che il mio codice sembra andare sempre più nella direzione di questo anti-pattern ultimamente. Mi sembra che più cerco di ottenere informazioni che nascondono tra classi e disegni liberamente accoppiati, più le mie classi diventano un mix di dati puri, nessuna classe di comportamento e tutti i comportamenti non classi di dati.
Generalmente disegno le classi in un modo che minimizza la loro consapevolezza dell'esistenza di altre classi e riduce al minimo la loro conoscenza delle interfacce di altre classi. In particolare, lo impongo in modo top-down, le classi di livello inferiore non conoscono le classi di livello superiore. Per esempio:.
Supponiamo che tu abbia un'API generale di giochi di carte. Hai una classe Card
. Ora questa classe Card
deve determinare la visibilità dei giocatori.
Un modo è avere boolean isVisible(Player p)
su Card
class.
Un altro è avere boolean isVisible(Card c)
su Player
class.
Non mi piace il primo approccio in particolare in quanto garantisce la conoscenza della classe di livello superiore Player
a una classe di Card
di livello inferiore.
Ho optato invece per la terza opzione in cui abbiamo una classe Viewport
che, dato un Player
e un elenco di carte determina quali carte sono visibili.
Tuttavia questo approccio ruba entrambe le classi Card
e Player
di una possibile funzione membro. Una volta fatto questo per altre cose oltre alla visibilità delle carte, rimani con Card
e Player
classi che contengono puramente dati poiché tutte le funzionalità sono implementate in altre classi, che sono per lo più classi senza dati, solo metodi, come il Viewport
sopra.
Questo è chiaramente contro l'idea principale di OOP.
Qual è il modo corretto? Come dovrei andare sul compito di ridurre al minimo le interdipendenze di classe e ridurre al minimo la conoscenza e l'accoppiamento presunte, ma senza finire con il design strano in cui tutte le classi di basso livello contengono solo dati e le classi di alto livello contengono tutti i metodi? Qualcuno ha una terza soluzione o una prospettiva sulla progettazione della classe che evita l'intero problema?
P.S. Ecco un altro esempio:
Supponiamo di avere classe DocumentId
che è immutabile, ha solo un membro BigDecimal id
e un getter per questo membro. Ora devi avere un metodo da qualche parte, che ha dato un DocumentId
restituisce Document
per questo ID da un database.
Fai:
- Aggiungi il metodo
Document getDocument(SqlSession)
alla classeDocumentId
, introducendo improvvisamente conoscenza sulla tua persistenza ("we're using a database and this query is used to retrieve document by id"
), l'API utilizzata per accedere a DB e simili. Anche questa classe ora richiede il file JAR di persistenza solo per la compilazione. - aggiungi un'altra classe con metodo
Document getDocument(DocumentId id)
, lasciandoDocumentId
class come dead, no behavior, struct-like class.