My program models a sport tournament which has one or many events or categories. The class Event
has members like players or courts as a List
, as well as some dictionaries using the class Map
.
So far accessing publicly those members can be done through their respective getters, but I realized this is a bad practice because it allows modifications through the collection object retrieved with the getter. And I wouldn’t want this.
I would like to limit the set of operations allowed on these members and avoid the situation described above.
One might think just declaring these members as final
would do, but it doesn’t. These members can still be modified, just instead of freely doing that directly with the native List
or Map
methods, it’s done via my classes’ methods.
So let’s say I clamp the collections set of operations allowed on these collections and I make wrapping classes for my lists and maps, so instead of having this:
private List<Player> players;
private List<Court> courts;
private Map<Player, List<Timeslot>> playersUnavailable;
I could have something like:
private CustomList<Player> players;
private CustomList<Court> courts;
private CustomMap<Player, List<Timeslot>> playersUnavailable;
The name of the classes would probably need to be different, I just can’t think of something better at the moment.
Say we just want reader methods for these classes. So we could have a method to get the size and another to get a particular element of the internal collection:
public class CustomList<T> {
private final List<T> list;
public CustomList<T>(List<T> l) { list = l; }
public int size() { return list.size(); }
public int get(int i) { return list.get(i); }
}
However, now I can’t iterate through items of a collection like I normally would:
for (Player player : players)
System.out.println(player);
Although I could use get()
and size()
but it feels a bit rudimentary.
What could I do to make these collections iterable but preserving the immutability rules my design has? (As I said it’s not strictly immutability, since you can do it using the pertinent methods)
Overall, should I rethink my design differently? Does what I’m trying to do make sense? Could I achieve it differently, in a better way?
I guess I could say what I am looking for are read-only collections, am I?
2
It appears you have two requirements here:
-
Event
exposes collections, but callers cannot modify those collections. -
Event
can modify its collections, but only through theEvent
class.
It sounds like you need to use immutable collections, for example, java.util.Collections.unmodifiableList(List list). However, you do not want to store an immutable collection as the state in Event
. It could work like this:
public class Event {
private List<Player> players;
public List<Player> getPlayers() {
return Collections.unmodifiableList(players);
}
public void addPlayer(Player p) {
players.add(p);
}
}
This allows modifying the collections through Event
, while still allowing callers to get an immutable reference suitable for iteration. The call to Collections.unmodifiableList(players)
wraps the list players
and does not copy it.
If this needs to be thread-safe then there are additional concerns to address here, but that was not a requirement. It sounds more like your Event
class needs to handle invariants and data integrity between multiple collections that model the same objects. That concern should easily be addressable using this technique.
7
You have two choices, other than rolling your own — slap a facade over a modifiable list (like UnmodifiableList
) that doesn’t allow modification through that interface, or use one of the available third party libraries that is truly immutable. The problem with the facade approach is that someone (say, a subclass) could modify the underlying list, breaking the contract implied by presenting an UnmodifiableList
. An example of the latter is Guava’s ImmutableList which is truly immutable, inherently making a defensive copy when initialized from a List.