The basic idea behind OOP is that data and behavior (upon that data) are inseparable and they are coupled by the idea of an object of a class. Object have data and methods that work with that (and other data). Obviously by the principles of OOP, objects that are just data (like C structs) are considered an anti-pattern.
So far so good.
The problem is I have noticed that my code seems to be going more and more in the direction of this anti-pattern lately. Seems to me that the more I try to achieve information hiding between classes and loosely coupled designs, the more my classes get to be a mix of pure data no behavior classes and all behavior no data classes.
I generally design classes in a way which minimizes their awareness of other classes' existence and minimizes their knowledge of other classes' interfaces. I especially enforce this in a top-down fashion, lower level classes don't know about higher level classes. E.g.:
Suppose you have a general card game API. You have a class Card
. Now this Card
class needs to determine visibility to players.
One way is to have boolean isVisible(Player p)
on Card
class.
Another is to have boolean isVisible(Card c)
on Player
class.
I dislike the first approach in particular as it grants knowledge about higher level Player
class to a lower level Card
class.
Instead I opted for the third option where we have a Viewport
class which, given a Player
and a list of cards determines which cards are visible.
However this approach robs both Card
and Player
classes of a possible member function. Once you do this for other stuff than visibility of cards, you are left with Card
and Player
classes which contain purely data as all functionality is implemented in other classes, which are mostly classes with no data, just methods, like the Viewport
above.
This is clearly against the principal idea of OOP.
Which is the correct way? How should I go about the task of minimizing class interdependencies and minimizing assumed knowledge and coupling, but without winding up with weird design where all the low level classes contain data only and high level classes contain all the methods? Does anyone have any third solution or perspective on class design which avoids the whole problem?
P.S. Here's another example:
Suppose you have class DocumentId
which is immutable, only has a single BigDecimal id
member and a getter for this member. Now you need to have a method somewhere, which given a DocumentId
returns Document
for this id from a database.
Do you:
- Add
Document getDocument(SqlSession)
method toDocumentId
class, suddenly introducing knowledge about your persistence ("we're using a database and this query is used to retrieve document by id"
), the API used to access DB and the like. Also this class now requires persistence JAR file just to compile. - Add a some other class with method
Document getDocument(DocumentId id)
, leavingDocumentId
class as dead, no behavior, struct-like class.