Wednesday, 15 October 2014

Immutable datastructures vs information hiding

One generally accepted OO-concept Rich Hickey challenges with Clojure is the wisdom of information hiding. In rich object models (one proposals of DDD for example) aggregates are encouraged to eschew exposing their internal structure.
As a simple example we can imagine we are to write a card game with multiple players.  Any card game actually, the point is that the players have hole cards which they don't share with others. Not even with the rest of the code. This project implements a simplified Blackjack game what perfectly serves the purpose of this post. This is the class for the participating players.

class Player extends Entity<PlayerID> {
private final List<Card> cards;
private boolean stopped;
public Player(PlayerID playerID, List<Card> cards) {
super(playerID);
Validate.notNull(playerID);
Validate.notNull(cards);
this.cards = cards;
}
public boolean notStopped() { return !stopped();}
public boolean stopped() { return stopped;}
public void stand() { this.stopped = true;}
/**
* Important that the logic should be where the data is. This yields rich
* object model.
*
* @return
*/
public int score() {
int score = 0;
int aceCounter = 0;
for (Card card : cards) {
score += card.value();
if (card.rank == Rank.ACE) {
aceCounter++;
}
}
// if player has two ACES, then one has a value of 1
if (aceCounter > 1) {
score -= 10;
}
return score;
}
}
The player doesn't expose her cards, which is fine for us. Now imagine we want to spice up our game with some fantasy elements. Some player can cast Spying Spells to peek into their opponents' hands. This is orthogonal to the existing functionality, so the code change preferably shouldn't touch the existing code. But it does, because we have to modify Player some way.
//version one - simple getter
class Player {
...
List<Cards> getCards() { return Lists.newArrayList(this.cards);}
...
}
//version two - a new immutable object for that aspect of the Player
// so the Player internals are not exposed directly
class PlayerView {
private final PlayerID id;
private final List<Cards> cards;
//constructor and getters
}
class Player {
...
PlayerView getView() {...}
...
}
Thus the Player class now serves two independent features of the game.

In Clojure classes are not used, instead functions operate on associative information models (@Copyright Rich Hickey), namely maps, lists, vectors, sets. There is no information hiding, the player is represented something like this

;; a player representation
{:id 123
:cards [[2 :spades] [:king :clubs] [10 :diamonds]]
:stopped false}
;; a function calculating the score
(defn score-hand [cards]
"Calculates the score for the hand"
(let [sum (reduce + (map card-value cards))
ace? #(= :ace (last %))
ace-count (count (filter ace? cards))]
(if (> ace-count 1)
(- sum 10)
sum)))
The logic is decoupled from the data, thus we can avoid awkward things like creating a new View object, but because Clojure uses immutable data structure there is no harm exposing their details either. We can easily add new functionality too without touching existing code.

No comments :

Post a Comment