This answer is written from C# perspective. Please feel free to make edits to replace C# interfaces with Java equivalents, or to mention discrepancies or lack of equivalents between the two languages where appropriate.
Evan's answer has value, in terms of keeping things simple (KISS).
Before going into Robert Bräutigam's answer, let's look at how this task is traditionally handled by Java practitioners (of which I'm not):
Card object or an
AbstractCard interface (instead of using
UserCardConsumer interface or abstract class (or
CardConsumer for short), which defines methods that accept either one card at a time, or entire collection of cards at once. This is meant to be inheritable (be implemented or extended).
- Any business logic that wants to "consume" cards from a
User will need to implement an inner class that inherits from
UserCardConsumer, which will consume the cards when its method is called.
Given the additional amount of traditional code (of which Java is famous for), Robert Bräutigam reiterates the need to think about the bare necessary needs of the application. If you can encapsulate the entire logic that operates on that
Card collection in one class, go for it.
User class needs to be. Typically, more than one needs exist.
- An interface?
- A mutable data transfer object?
- A data model (as in MVC, MVVM etc)
- An immutable data object?
- An actor?
- An actor (as in actor model) basically handles the entire business logic within itself (not necessary encapsulating the whole application; just its share of responsibilities), typically not having to reveal most of its information to outsiders.
If you expose the
getCard(int index) method, you might also need to expose an
int getCardCount() method, so that the callers can use a for-loop.
If you are unsure about exposing a mutable
List<String> member (i.e. sharing the reference to the internal member with the caller), for various reasons, here are some options:
IReadOnlyList<> (immutable; otherwise behaves like a list)
IList<> (maybe-mutable; otherwise behaves like a list)
- Why I said "maybe-mutable"? Many immutable list implementations offer an
IList interface as well, but their mutators will throw an exception.
- LSP (Liskov) and ISP (Interface Segregation) would suggest that, if the list is always going to be immutable, exposing
IReadOnlyList<> is better than
- However, if the object or interface must allow flexibility in the underlying implementation,
IList<> would still be the choice.
ICollection<> ... This one does exactly what you want:
- Has a
Count property, so that you can do
- Has an indexer, so that you can do
- Enumerable (iterable)
ICollection<> is a better choice over
- Note that
ICollection<> does not have an
- This may not be an issue for you, since you can put this method on the
User class. This is even more preferable, since this allows
User to be notified of changes to the list, so that it can perform additional actions to maintain the object invariants if necessary.
Issues with staying consistent and synchronized.
For simple applications this may not be a big issue.
For slightly more involved applications, a mutable data transfer object can return a snapshot (copied) list of items. Modification of the list afterwards would not be synchronized to the copied list that was returned earlier.
Preventing null exceptions.
My typical approach is as follows.
- Mark the internal
readonly. This means the same list will always be used (and reused), and will survive modifications and item removals. This means the reference to this list will always be the same.
- Inside the
User constructor, initialize this
List member. Combined with the
readonly keyword, this ensures the reference to this list will always be same and non-null.
- Expose this member as an
- Operations, such as destructive and non-destructive ones, are carried out on this