I have been working on a project recently where many classes have collections (specifically Lists) of related information contained within them, but expose the full functionality of the list to consumers of the class. The issue with this, is that any consuming class has to be aware of any rules or restrictions that need to be applied to the child elements before adding to the collection. It also makes it more difficult for the class to validate the items being put into the collection and ensure that they are valid.
As an example, consider a pile of cards used in a card game.
public class CardPile
{
public CardPile()
{
Cards = new List<Card>();
}
public List<Card> Cards { get; private set; }
}
In this scenario, I can add, insert, remove, or clear the entire pile of cards. Maybe that is exactly the scenario you want, but usually there are rules associated with adding or removing cards from a pile. Consider a game like Solitare. In order to play a card, the face value needs to be 1 lower than the previous card in the pile, and of the opposite color. So, anywhere I want to add a card to the pile, I need logic like this:
var addCard = false;
var last = cardPile.Cards.LastOrDefault();
if (last != null && last.FaceValue - card.FaceValue == 1
&& last.Color != card.Color) addCard = true;
if (last == null && card.FaceValue == FaceValue.King) addCard = true;
if (!addCard) return;
cardPile.Cards.Add(card);
It's all about Tell don't Ask here. We have to ask the CardPile about the last card, and do a lot of checking before we can add the card. I don't want to deal with all of that. I just want to tell the CardPile, here is a card, I am adding it to you. Then the CardPile can take it, or tell me to buzz off. The CardPile knows best. I really want to have an AddCard() method here that encapsulates the logic above. The result is less code duplication, and good encapsulation.
At this point you might have a red flag. In our example of Solitare, we need to have two types of card piles. The lower staging piles build downwards from King to Ace while alternating colors. The top home piles count up from Ace to King and must have the same suit throughout. The CardPile class is a very basic class that should be able to function for any CardPile needed by a game. So, for this example, it does make sense to just expose the List, and place the logic outside of the class. Right?! Wrong, I don't want to create repeated validation logic around when a card can and can not be added to a pile, and I don't want different CardPile classes for each implementation of AddCard(). The best solution in my mind is to use the Strategy pattern, and encapsulate the card adding validation logic into a CardPilePolicy class.
public interface ICardPilePolicy
{
bool CanAdd(Card card, CardPile pile);
}
Now that I have the policy class, I need to implement the validation logic for my two types of card piles, the play area, and the home row.
public class PlayFieldPilePolicy: ICardPilePolicy
{
public bool CanAdd(Card card, CardPile pile)
{
var addCard = false;
var last = pile.Cards.LastOrDefault();
if (last != null && last.FaceValue - card.FaceValue == 1
&& last.Color != card.Color) addCard = true;
if (last == null && card.FaceValue == FaceValue.King) addCard = true;
return addCard;
}
}
public class HomePilePolicy: ICardPilePolicy
{
public bool CanAdd(Card card, CardPile pile)
{
var addCard = false;
var last = pile.Cards.LastOrDefault();
if (last != null && card.FaceValue - last.FaceValue == 1
&& last.Suit == card.Suit) addCard = true;
if (last == null && card.FaceValue == FaceValue.Ace) addCard = true;
return addCard;
}
}
Now let's look at what the new CardPile class would look like.
public class CardPile
{
private readonly List<Card> _cards;
public CardPile(ICardPilePolicy policy): this(policy, null) {}
public CardPile(ICardPilePolicy policy, IEnumerable<Card> initalCards)
{
Policy = policy;
if (initalCards == null) return;
_cards = new List<Card>(initalCards);
}
public bool IsEmpty { get { return !_cards.Any(); }}
public bool AddCard(Card card)
{
var canAdd = true;
if((canAdd = Policy.CanAdd(card, this))) _cards.Add(card);
return canAdd;
}
private ICardPilePolicy Policy { get; set; }
public IEnumerable<Card> Cards
{
get { return _cards.ToArray(); }
}
}
The idea here, is that we don't want consumers of our class to worry about the logic required to add to our collections. If we do let them worry about it, we might accidentally allow the consumer to do something bad with our card piles. Notice in the last version of the class, I do still allow the consumer to get the list of cards from the pile. This might be necessary for the game to draw the pile to the screen for instance. However, I expose it as an IEnumerable, and return an array, not the internal collection. In this example, Cards are an immutable object, so no harm can be done by giving the consumer direct access to the cards, they couldn't change the values or anything like that. If you see a class with an exposed collection, I hope you slow down and contemplate whether this is what you want or not.
These code samples are provided openly and freely for any use without warranty.