Architectural considerations

We created an interface and a simple implementation of it. During the implementation, we discovered that the interface needs other interfaces and methods that are needed to support the algorithm. This usually happens during the architectural design of the code, before implementation. For didactical reasons, I followed the build-up of the interfaces while we developed the code. In real life, when I created the interfaces, I created them all in one step as I have enough experience. I wrote my first quick sort code around 1983 in Fortran. However, it does not mean that I hit the bull's eye with just any problem and come out with the final solution. It just happens that sort is a too well known problem. If you need to modify the interfaces or other aspects of your design during development, do not feel embarrassed. It is a natural consequence and a proof that you understand things better and better as time goes by. If the architecture needs change, it is better to be done than not, and the sooner it is, the better. In real life enterprise environments, we will design interfaces just to learn during development that there were some aspects that we forgot. They are very true and bit more complex operations than sorting a collection.

In the case of the sorting problem, we abstracted the something we want to sort to the most possible extreme. The Java build in sort can sort arrays or lists. If you want to sort something that is not a list or an array, you have to create a class that implements the java.util.List interface with more than 24 methods it requires to wrap your sortable object to make it sortable by the JDK sort. To be honest, that is not too many, and in a real-world project, I would consider that as an option.

However, we do not, and cannot know, what methods of the interface the built-in sort uses. Those that are used should be functionally implemented and those that are not, can contain a simple return statement because they are just never invoked. A developer can consult the source code of the JDK and see what methods are actually used, but that is not the contract of the search implementation. It is not guaranteed that a new version will still use only those methods. If a new version starts to use a method that we implemented with a single return statement, the sort will magically fail.

It is also an interesting performance question how the swapping of two elements is implemented by the search using only the List interface. There is no put(int, Object) method in the List interface. There is add(int Object), but that inserts a new element and it may be extremely costly (burning CPU, disk, energy) to push all elements of the list up if the objects are stored, for example, on disk. Furthermore, the next step may be removing the element after the one we just inserted, doing the costly process of moving the tail of the list again. That is, the trivial implementation of put(int,Object) that the sort may or may not follow. Again, this is something that should not be assumed.

When developers use libraries, classes, and methods from the JDK, open source, or commercial libraries, the developers may consult the source code but they should not rely on the implementation. You should rely only on the contract and the definition of the API that the library comes with. When you implement an interface from some external library, and you do not need to implement some part of it, and create some dummy methods, feel the danger in the air. It is an ambush. It is likely that either the library is poor quality or you did not understand how to use it.

In our case, we separated the swapping and the comparison from the sort. The collection should implement these operations and provide them for the sort. The contract is the interface, and to use the sort, you have to implement all methods of the interfaces we defined.

The interface of Sort defines setters that set Swapper and Comparator. Having dependencies set that way may lead to a code that creates a new instance of a class implementing the Sort interface, but does not set Swapper and Comparator before invoking Sort. This will lead to NullPointerException the first time the Comparator is invoked (or when the Swapper is invoked in case the implementation invokes that first, which is not likely, but possible). The calling method should inject the dependencies before using the class. When it is done through setters, it is called setter injection. This terminology  is heavily used when we use frameworks such as Spring, Guice, or some other container. Creating these service classes and injecting the instance into our classes is fairly similar all the time.

Container implementations contain the functionality in a general way and provide configuration options to configure what instances are to be injected into what other objects. Usually, this leads to shorter, more flexible, and more readable code. However, dependency injection is not exclusive to containers. When we write the testing code in the next section, and invoke the setters, we actually do dependency injection.

There is another way of dependency injection that avoids the problem of dependencies not being set. This is called constructor injection. The dependencies are final private fields with no values. Remember that these fields should get their final values by the time the constructor finishes. Constructor injection passes the injected values to the constructor as arguments and the constructor sets the fields. This way, the fields are guaranteed to be set by the time the object was constructed. This injection, however, cannot be defined in an interface.

Now, we already have the code, and we know the considerations of how the interfaces were created. This is the time to do some testing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.180.43