A different question of mine had to do with encapsulating member data structures inside classes. In order to understand this question better please read that question and look at the approach discussed.
One of the guys who answered that question said that the approach is good, but if I understood him correctly – he said that there should be a class existing just for the purpose of wrapping the collection, instead of an ordinary class offering a number of public methods just to access the member collection.
For example, instead of this:
class SomeClass{
// downright exposing the concrete collection.
Things[] someCollection;
// other stuff omitted
Thing[] getCollection(){return someCollection;}
}
Or this:
class SomeClass{
// encapsulating the collection, but inflating the class' public interface.
Thing[] someCollection;
// class functionality omitted.
public Thing getThing(int index){
return someCollection[index];
}
public int getSize(){
return someCollection.length;
}
public void setThing(int index, Thing thing){
someCollection[index] = thing;
}
public void removeThing(int index){
someCollection[index] = null;
}
}
We’ll have this:
// encapsulating the collection - in a different class, dedicated to this.
class SomeClass{
CollectionWrapper someCollection;
CollectionWrapper getCollection(){return someCollection;}
}
class CollectionWrapper{
Thing[] someCollection;
public Thing getThing(int index){
return someCollection[index];
}
public int getSize(){
return someCollection.length;
}
public void setThing(int index, Thing thing){
someCollection[index] = thing;
}
public void removeThing(int index){
someCollection[index] = null;
}
}
This way, the inner data structure in SomeClass
can change without affecting client code, and without forcing SomeClass
to offer a lot of public methods just to access the inner collection. CollectionWrapper
does this instead.
E.g. if the collection changes from an array to a List
, the internal implementation of CollectionWrapper
changes, but client code stays the same.
Also, the CollectionWrapper
can hide certain things from the client code – from example, it can disallow mutation to the collection by not having the methods setThing
and removeThing
.
This approach to decoupling client code from the concrete data structure seems IMHO pretty good.
Is this approach common? What are it’s downfalls? Is this used in practice?
14
You are implementing a restricted set of the Collection interface, without actually giving it something that you can work with.
E.g. if the collection changes from an array to a List, the internal
implementation of CollectionWrapper changes, but client code stays the same.
This is exactly what the Collection
interface is intended to do. If you pass back a Collection, that is all the client can work from.
Also, the CollectionWrapper can hide certain things from the client code – from example, it can disallow mutation to the collection by not having the methods setThing and removeThing.
In Java, this is known as an Unmodifiable Collection, and the Collections utility has just such a method to convert a modifiable collection to an unmodifiable one.
By making the restricted wrapper you are also giving up things such as sort and iterator which come in very handy from time to time. This means code such as:
for(Thing t : someCollection) {
....
}
won’t work. Instead, the code would need to be:
for(int i = 0; i < someCollection.size(); i++) {
Thing t = someCollection.get(i);
....
}
While that is perfectly acceptable code, it also means you are failing to take advantage of the language to allow the client to write code faster (programmer time is expensive).
The way you decouple the implementation is to use an interface, preferably one that already exists in the language framework so that other things that use that same interface can use your code without jumping through additional hoops.
Just a note:
What I’m trying to do, is not expose the collection – because I don’t want the client code to break when the collection changes say from a List to a Set. If I just allow the client code to do
class.getCollection()
it’ll break when the type of the collection changes. So the idea is to make a thin layer around it (“CollectionWrapper”), so when the type of collection changes, only the wrapper has to change, but not the code using the collection (i.e. using the wrapper). Is what I’m saying clear?
If the collection is only given as a Collection interface, it won’t break. However, I must point out that the public methods that you are presenting with this class, namely public Thing getThing(int index)
, public void setThing(int index, Thing thing)
, and public void removeThing(int index)
would preclude the implementation from changing from a List to a Set, because Sets have no concept of indexes and these methods would need to change.
1
You’re describing the Encapsulated Collection pattern. I like this pattern a lot; it’s especially appropriate to domain-driven design.
The advantage you describe – being able to change the internal representation of the collection – is a good one. But I think the bigger advantage is restricting clients’ ability to modify or break the underlying collection.
For example, consider a “commenting” feature of a social network, with the requirement that you can’t delete comments after they’ve been posted. If you expose the List<Comment>
as a property, it’s easy for clients of your class to call the list’s Remove
method without realising they weren’t supposed to.
If, instead, you use a Comments
class with a private List<Comment>
and no Remove
method, it’s now impossible to break the invariant.
You also end up with a richer domain model, which is better equipped to deal with new features. (A collection of domain objects is often a new domain concept that your model is missing.) Imagine that the requirement’s changed and now it’s possible to delete comments, but the deletion has to be recorded in an audit trail. It’s easy to implement this behaviour in a new Delete
method on your Comments
class – it would have been much more complicated to override the pre-existing Remove
method on the List
.
I think you are right.
I have developed several custom collections in diferent programming languages, and, sometimes, I applied the pattern. I also check that other developers also applied.
It seems that it depends on what the developer wants to achieve, sometimes, the collection is access directly, sometimes, using the pattern.
Visual controls with several items like “Treeview”, “Listview”, “Gridview” use a lot of this technique.
You said,
What I’m trying to do, is not expose the collection – because I don’t want the client code to break when the collection changes say from a List to a Set.
I have concerns about that requirement — whether perceived or real.
The abstractions represented by those standard containers are so distinct and different, I find it hard that you will be able to substitute one for another in your interface. If SomeClass
provides an interface that is more appropriate for a Set
, you have to honor that interface regardless of how you implement the Set
. The same logic applies for List
too. In that sense, you don’t have to expose ContainerWrapper
or Container
at all. They are implementation details of SomeClass
. You could use pimpl pattern to completely hide the container.
Perhaps SomeClass
is a container of objects with additional meaning.
For example, in a CAD program, an Assembly consists of sub-components that can be Parts or sub-Assemblies. However, that is not sufficient to capture the relationship between an Assembly and one of its sub-components. The location and orientation of the sub-component in the Assembly is the key piece of data that makes this particular container-contained relationship work. For this case, whether you store the collection of Pair<sub-component, transform>
as a List or a Set is of very little importance in the interface of Assembly. You need to be able to query the Assembly for its sub-components and given a sub-component, you need to be able to query the Assembly for the transform of the sub-component in the Assembly.
Another example: A Canvas and its contained objects in a 2D painting program or an SVG program. Here again, the container-contained relationship has to be created using the 2D position of the contained objects plus a depth to
in order to arrange them in layers. Here also, you can use a Set or a List to hold the contained objects.
In conclusion, I am asking you to dig deeper to determine the kind of relationship SomeClass
has with its contained objects. Is it a matter of simple containment or is there more to the relationship? If there is more to the relationship, how would you expose the relationship that is divorced from a List or a Set.
In both the examples I presented, a List makes more sense to me. I can’t say what makes more sense for SomeClass
.