Good question, I've been thinking along similar lines. Historically, the OO paradigm arose from the need for computer simulation - see the history of Simula - and despite early OO languages like Smalltalk being made by people who knew what they were doing (i.e. Alan Kay), OO is now arguably over-used and brings in far too much accidental complexity.
Generally, FP style programs will be shorter, easier to test, and easier to modify than OO programs. As Rob Harrop put it in his talk, Is the Future Functional?, you can never get simpler than functions and data; the two compose infinitely, to build up whatever abstractions are needed. So one way to answer your question (or am I just restating it? :) is to ask, What's the highest level function, and the highest-level input-data --> output-data look like? Then you can start breaking down those "alpha" functions and data types into the next layer of abstractions, and repeat as necessary.
Another perspective (not quite answers) on your question is to look at this thread (disclaimer, I started it) on StackOverflow, some of the answers are very interesting: https://stackoverflow.com/questions/3431654/how-does-functional-programming-apply-to-simulations
My own opinion at this point is, unless you're modeling a situation where there really are discrete objects that only interact in definite ways (e.g. a model of a computer network) - and thus map directly to the capabilities of a clean, message-passing-paradigm OO language - it's simpler to go FP. Note that even in the games-programming community - where simulations are very prevalent and performance requirements are paramount - experienced developers are moving away from the OO-paradigm and/or using more FP, e.g. see this HN discussion or John Carmack's comments on FP
What you describe is known as an anemic domain model. As with many OOP design principles (like Law of Demeter etc.), it's not worth bending over backwards just to satisfy a rule.
Nothing wrong about having bags of values, as long as they don't clutter the entire landscape and don't rely on other objects to do the housekeeping they could be doing for themselves.
It would certainly be a code smell if you had a separate class just for modifying properties of Card
- if it could be reasonably expected to take care of them on its own.
But is it really a job of a Card
to know which Player
it is visible to?
And why implement Card.isVisibleTo(Player p)
, but not Player.isVisibleTo(Card c)
? Or vice versa?
Yes, you can try to come up with some sort of a rule for that as you did - like Player
being more high level than a Card
(?) - but it's not that straightforward to guess and I'll have to look in more than one place to find the method.
Over time it can lead to a rotten design compromise of implementing isVisibleTo
on both Card
and Player
class, which I believe is a no-no. Why so? Because I already imagine the shameful day when player1.isVisibleTo(card1)
will return a different value than card1.isVisibleTo(player1).
I think - it's subjective - this should be made impossible by design.
Mutual visibility of cards and players should better be governed by some sort of a context object - be it Viewport
, Deal
or Game
.
It's not equal to having global functions. After all, there may be many concurrent games. Note that the same card can be used simultaneously on many tables. Shall we create many Card
instances for each ace of spade?
I might still implement isVisibleTo
on Card
, but pass a context object to it and make Card
delegate the query. Program to interface to avoid high coupling.
As for your second example - if the document ID consists only of a BigDecimal
, why create a wrapper class for it at all?
I'd say all you need is a DocumentRepository.getDocument(BigDecimal documentID);
By the way, while absent from Java, there are struct
s in C#.
See
for reference. It's a highly object-oriented language, but noone makes a big deal out of it.
Best Answer
Sometimes it's appropriate to add constructor to a struct and sometimes it is not.
Adding constructor (any constructor) to a struct prevents using aggregate initializer on it. So if you add a default constructor, you'll also have to define non-default constructor initializing the values. But if you want to ensure that you always initialize all members, it is appropriate.
Adding constructor (any constructor, again) makes it non-POD, but in C++11 most of the rules that previously applied to POD only were changed to apply to standard layout objects and adding constructors don't break that. So the aggregate initializer is basically the only thing you lose. But it is also often a big loss.