The question of how good class interfaces should be is not easy. What methods to include in the interface, what should be their parameters, is it not necessary to break this interface into several? What will happen to the interface as the project develops? Will it be necessary to change it? Surely, such questions were asked by many. I will share my thoughts on interfaces providing access to collections.
Suppose you have an interface for some collections that, among other things, allow you to extract sets of ordered rows by key. So you need a method like
List<String> getElements(String key);
But you decided that sometimes these sets are huge, or it is difficult to get all the lines at once (for example, some implementations request them from some slow web service with a stupid protocol). And you apply them, for example, displaying on the screen with page navigation or loading parts. Here, some developers will have the idea to expand the interface in some way:
public interface MyCollection { List<String> getElements(String key); String getElement(String key, int index); List<String> getElementsRange(String key, int fromIndex, int toIndex); int getElementsCount(String key); }
I encounter such interfaces from time to time in commercial or free code. For example, the
UserProvider interface in the
OpenFire Jabber server (there are getUsers, getUsers with parameters and getUserCount; a similar example with two methods findUsers). If you think about it, none of the additional methods are needed. They can be trivially expressed through getElements:
String getElement(String key, int index) { return getElements(key).get(index); } List<String> getElementsRange(String key, int fromIndex, int toIndex) { return getElements(key).subList(fromIndex, toIndex); } int getElementsCount(String key) { return getElements(key).size(); }
And the implementations are so simple that even for the sake of syntactic sugar, it is doubtful to introduce new methods. An inexperienced developer says here: “How is that, the first method returns the entire list, and if it is large and does not fit into the memory? And in general, if we need just one element? ”That's because the List interface is strongly associated with some with a specific implementation (usually ArrayList) and people forget that you can do as lazy List methods as you are lazy with your own interface.
')
It can be argued that auxiliary methods, although they do not have any obvious benefit, do not interfere either. However, experience shows that they interfere. Having such an interface with additional unnecessary methods, developers will consider themselves entitled to actually return an ArrayList with a complete list of elements in getElements, without bothering to create a lazy list. As a result, for example, getElements (key) .size () no longer works, since a huge number of elements will be loaded into memory, and it will end. Therefore, the user will be required to use your implementation of getElementsCount (key).
Let, for example, you have a utility method that prints a string containing the number of elements, the first few and ellipsis (if there are more elements). For example:
[100500] First, second, third...
The implementation could be:
public static String getSummary(List<String> list) { StringBuilder sb = new StringBuilder(); int size = list.size(); sb.append('[').append(size).append("] "); int maxElements = Math.min(3, size); for(int i=0; i<maxElements; i++) sb.append(i==0?"":", ").append(list.get(i)); if(maxElements < size) sb.append("..."); return sb.toString(); }
And you would calmly call such a method getSummary (myCollection.getElements (key)). But here we recall that there are many non-lazy implementations, and life becomes more complicated. As a result, methods appear with malformed parameter sets. The first approach is to create a getSummary method (MyCollection collection, String key). From an excellent independent method that could be reused, we get a method that depends on your collection interface, which you can’t apply to something else. The second approach is to create a getSummary method (int count, List firstThreeElements) and call it via getSummary (myCollection.getElementsCount (key), myCollection.getElementsRange (key, 0, 3)). Although now the method is not tied to your interface, this solution is even worse, because the method interface is tied to its implementation. Imagine if the user wanted the last element to also be displayed:
[100500] First, second, third... last
Instead of adding one line to the implementation of the method, you will have to change its interface, that is, all the places where the method is called. It's horrible.
A good solution would be to create an adapter that implements the List interface. Then the call would look like getSummary (new MyCollectionListAdapter (myCollection, key)). But this is still an unnecessary complication, because it was possible to do well from the very beginning. In addition, such an adapter is initially limited. For example, you cannot implement optimally List.contains or List.indexOf, since the MyCollection interface does not have the necessary method. For example, if in a specific implementation of MyCollection, the list is actually loaded from the SQL database, then the contains list is easily optimized: you do not need to iterate over all the elements.
If the interface MyCollection has only getElements, then when it is implemented, the programmer will have no choice but to do everything well and implement a lazy list (of course, for obviously small collections, this can be ignored).
In general, I want to note that you should not duplicate those good interfaces that have already been created for you. You are clogging up your interface in this way and you can end up doing more harm than good. Do not be afraid to also implement the interfaces of standard collections. Even for Map, this is not at all difficult, and even for List even more so. Java.util has helper classes like AbstractList to help you.