Blog-Archiv

Montag, 21. November 2016

Framework as seen from Java, Part 2

This is the continuation of my recent article about frameworks where I showed the problems when trying to reuse the three lines of Swap code with different types of containers.

In this Blog, Swap will be rewritten to become a real framework with abstraction and code-reusage.

Swap Abstraction

Here comes an abstract Swap that will work for any type of Java container. This base class is abstract because it makes no sense to use it standalone. It defines the responsibilities of the outer world using an inner interface Container. Every sub-class needs to implement that interface to be able to reuse Swap. There will be a sub-class per container type, like array, list, linked set.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class Swap<T>
{
    /** Clients of Swap must implement this. */
    public interface Container<E>
    {
        E get(int index);
        
        void set(int index, E element);
        
        int size(); // needed just for index-assertions
        
        void close();   // extra for LinkedHashSet that must reorganize completely
    }
    
    protected final void performSwap(Container<T> container, int index1, int index2)  {
        if (index1 == index2)   // fast and easy: nothing to do
            return;
        
        // uncover mistakes early
        if (index1 < 0 || index2 < 0 || index1 >= container.size() || index2 >= container.size())
            throw new IllegalArgumentException("Swap-indexes out of bounds!");
        
        // perform swapping
        final T element1 = container.get(index1);
        container.set(index1, container.get(index2));
        container.set(index2, element1);

        container.close();
    }
}

The Container interface requires get() to retrieve an element at a specified index, set() to put an element to a specified index, size() to assert given indexes, and it also demands close() to give problematic containers like LinkedSet a chance to reorganize completely.

The method performSwap() is there to be called by sub-classes, not to be overridden, thus it is final. It provides nice assertions that are done here once-and-only-once. Then it performs the famous three swap statements. Finally it calls close() on the container interface.

Now let's see if we can derive swaps for all container types from this with acceptable effort.

Array Swap


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SwapInArray<T> extends Swap<T>
{
    private class ArrayContainer implements Container<T>
    {
        private T [] array;
        
        public ArrayContainer(T [] array) {
            this.array = array;
        }
        
        @Override
        public int size() {
            return array.length;
        }
        @Override
        public T get(int index)   {
            return array[index];
        }
        @Override
        public void set(int index, T element)   {
            array[index] = element;
        }
        @Override
        public void close() {
        }
    }
    
    public void swap(T [] array, int index1, int index2)  {
        performSwap(new ArrayContainer(array), index1, index2);
    }
}

The ArrayContainer implementation adapts an array to be a Swap.Container. Accessing indexes is easy, size also, close is not needed. All this class does is wrapping the given array into an ArrayContainer and call super.performSwap().

List Swap


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.List;

public class SwapInList<T> extends Swap<T>
{
    private class ListContainer implements Container<T>
    {
        private List<T> list;
        
        public ListContainer(List<T> list) {
            this.list = list;
        }
        
        @Override
        public int size() {
            return list.size();
        }
        @Override
        public T get(int index)   {
            return list.get(index);
        }
        @Override
        public void set(int index, T element)   {
            list.set(index, element);
        }
        @Override
        public void close() {
        }
    }
    
    public void swap(List<T> list, int index1, int index2)  {
        performSwap(new ListContainer(list), index1, index2);
    }
}

The ListContainer implementation was also very easy to implement. Nearly the same as array, but using get() and set() methods. You can see at a glance that there are no errors in that code.

Linked Set Swap


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class SwapInLinkedSet<T> extends Swap<T>
{
    private class LinkedSetContainer implements Container<T>
    {
        private Set<T> linkedSet;
        private List<T> clone;
        
        public LinkedSetContainer(Set<T> linkedSet) {
            this.linkedSet = linkedSet;
            this.clone = new ArrayList<T>(linkedSet);
        }
        
        @Override
        public int size() {
            return linkedSet.size();
        }
        @Override
        public T get(int index)   {
            return clone.get(index);
        }
        @Override
        public void set(int index, T element)   {
            clone.set(index, element);
        }
        @Override
        public void close() {
            linkedSet.clear();
            
            for (T element : clone)
                linkedSet.add(element);
        }
    }
    
    public void swap(Set<T> linkedSet, int index1, int index2)  {
        performSwap(new LinkedSetContainer(linkedSet), index1, index2);
    }
    
}

Here we have the problematic LinkedHashSet that does not support access to indexes, it just provides iteration. But all the loops and conditions from its recent implementation are gone, replaced by a backing ArrayList that performs the index access. The ArrayList constructor receiving the Set adds all elements of the set, in their iteration-order. This implementation can not work without the close() call, because it must clear and rebuild the entire collection after swapping. But it does this elegantly by looping the backing list.

Mind that this solution is safe just for one swap. When the Set would be changed, the backing list would be out-of-date.

Demo Code

To close the swap story, here is some code that you can use to test above classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class Demo
{
    public static void main(String[] args) {
        new Demo();
    }

    private Demo() {
        arrayDemo();
        listDemo();
        linkedSetDemo();
    }

    private void arrayDemo() {
        final Integer[] integerArray = new Integer[] { 0, 1, };
        System.out.println("Before array swap: "+Arrays.asList(integerArray));
        
        new SwapInArray<Integer>().swap(integerArray, 0, 1);
        
        System.out.println("After array swap: "+Arrays.asList(integerArray));
    }

    private void listDemo() {
        final List<String> stringList = new ArrayList<>();
        stringList.add("A");
        stringList.add("B");
        stringList.add("C");
        stringList.add("D");
        stringList.add("E");
        System.out.println("Before list swap: "+stringList);
        
        new SwapInList<String>().swap(stringList, 1, 3);
        
        System.out.println("After list swap: "+stringList);
    }

    private void linkedSetDemo() {
        final Set<Float> floatList = new LinkedHashSet<>();
        floatList.add(1.0f);
        floatList.add(100.0f);
        floatList.add(10000.0f);
        System.out.println("Before linkedSet swap: "+floatList);
        
        new SwapInLinkedSet<Float>().swap(floatList, 1, 2);
        
        System.out.println("After linkedSet swap: "+floatList);
    }
}

This outputs:

Before array swap: [0, 1]
After array swap: [1, 0]
Before list swap: [A, B, C, D, E]
After list swap: [A, D, C, B, E]
Before linkedSet swap: [1.0, 100.0, 10000.0]
After linkedSet swap: [1.0, 10000.0, 100.0]

Resume

Defining the responsibilities of the outer world as an inner interface or abstract class is a really useful technique. That way the responsibilities are tightly coupled to the class. Any implementation is then free to fulfill them in its own way.

None of the shown classes contains long and hacky methods with complicated loops and conditions. Thus they will contain no mistakes. I call this a clean solution, easy to read and simple to verify.


So, I was able to create a Swap framework with real abstraction and reusage. But there is more to frameworks, because not everything can be done so simply. Frameworks also provide access to the sometimes complex interactions between classes.

My next example framework will contain several classes playing together. It will demonstrate the importance of a fundamental framework technique called Factory Method. There is no real framework without this pattern, because the object of one class allocates objects of other classes, and you want to determine those classes by overrides. This is possible only by encapsulating the new operator into a protected factory method.

You guessed right, I will do this in my next Blog article about frameworks as seen by Java (end titles music playing :-).




Keine Kommentare: