Showing posts with label Type inference. Show all posts
Showing posts with label Type inference. Show all posts

Saturday, November 21, 2009

Bookmark and Share

In a recent post I presented a generic DTO implementation which allows a type-safe way of accessing its contents.

The presented approach reduces the efforts of writing a lot of dedicated DTOs but comes at the cost that it is not obvious, which attributes are allowed to be put into or can be retrieved from a given DTO instance. Therefore methods using such a generic DTO as parameter or return value define a rather loose contract.

In the following I'm going to show a way to solve this problem. The basic idea is to introduce the concept of "attribute groups". Each attribute belongs to such a group, and each GenericDTO instance is created for exactly one attribute group.

Let's first define a marker interface AttributeGroup from which all concrete attribute groups will derive:

1
2
3
public interface AttributeGroup {

}

The Attribute class doesn't differ much from the version introduced in the first post. It's just parametrized with the type of group to which a given attribute belongs:

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
public class Attribute<G extends AttributeGroup, T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public Attribute(String name) {

        if (name == null) {
            throw new IllegalArgumentException();
        }

        this.name = name;
    }

    public static <F extends AttributeGroup, S> Attribute<F, S> getInstance(
            String name) {
        return new Attribute<F, S>(name);
    }

    @Override
    public String toString() {
        return name;
    }

    // equals(), hashCode() ...
}

As in the previous post Attribute instances are declared as constants on interfaces, e.g. PersonAttributes. But now each of these interfaces extends AttributeGroup, while the Attribute instances are parametrized with the attribute group:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface PersonAttributes extends AttributeGroup {

    public static final Attribute<PersonAttributes, String> FIRST_NAME = 
        Attribute.getInstance("FIRST_NAME");

    public static final Attribute<PersonAttributes, Integer> AGE = 
        Attribute.getInstance("AGE");
}

...

public class OrderAttributes implements AttributeGroup {

    public static final Attribute<OrderAttributes, Long> 
        TOTAL_PRICE = Attribute.getInstance("TOTAL_PRICE");
}

Also the GenericDTO class now has a type parameter specifying for which attribute group a given instance is declared:

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
54
55
56
57
58
59
60
61
62
63
64
public class GenericDTO<G extends AttributeGroup> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Class<G> attributeGroup;

    private Map<Attribute<G, ?>, Object> attributes = new LinkedHashMap<Attribute<G, ?>, Object>();

    private GenericDTO(Class<G> attributeGroup) {
        this.attributeGroup = attributeGroup;
    }

    public static <B extends AttributeGroup> GenericDTO<B> getInstance(
            Class<B> clazz) {
        return new GenericDTO<B>(clazz);
    }

    public <T> GenericDTO<G> set(Attribute<G, T> identifier, T value) {

        attributes.put(identifier, value);

        return this;
    }

    public <T> T get(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.get(identifier);

        return theValue;
    }

    public <T> T remove(Attribute<G, T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.remove(identifier);

        return theValue;
    }

    public void clear() {
        attributes.clear();
    }

    public int size() {
        return attributes.size();
    }

    public Set<Attribute<G, ?>> getAttributes() {
        return attributes.keySet();
    }

    public boolean contains(Attribute<G, ?> identifier) {

        return attributes.containsKey(identifier);
    }

    @Override
    public String toString() {
        return attributeGroup.getSimpleName() + " [" + attributes + "]";
    }

    // equals(), hashCode() ...
}

That way it is now clear at compile time which attributes are supported by a given GenericDTO instance.

When for instance having a GenericDTO instance at hand, only attributes of the PersonAttributes group can be put into this DTO or can be retrieved from it. Trying to insert attributes of another group will yield in a compiler error:

1
2
3
4
5
6
7
8
9
...
GenericDTO dto = GenericDTO.getInstance(PersonAttributes.class);

//that's fine
dto.set(PersonAttributes.FIRST_NAME, "Bob").set(PersonAttributes.AGE, 28);

//compiler error
dto.set(OrderAttributes.TOTAL_PRICE, 9990);
...

By introducing the group concept Attribute declaration is a bit more verbose than in the first version, but real compile-time safety and more explicit contracts should be worth the effort, which is still way below than having to implement dedicated DTOs with getters and setters for each attribute as well as proper equals()-, hashCode()- and toString()-Methods.

What do you think – is a generic DTO implemented that way a good idea? As always I'm looking forward to receiving your feedback.

Sunday, October 25, 2009

Bookmark and Share

Update (10/21/2009): There is now a second part to this post, which can be found here.

Using data transfer objects (DTO's) is a common technique for transferring data between different modules of an application. Especially within distributed systems DTO's are of great help for transporting data over remote boundaries.

Classic DTO's are basically standard JavaBeans, having a getter/setter pair for each attribute. In larger applications usually quite a lot of DTO's are required, and implementing them is a tedious and time consuming task.

That's why the idea of a generic DTO was born – basically a key/value map, which can store any sort of attribute. This approach saves a lot of time for writing dedicated DTO's, but it comes at the cost of losing type safety at compile time.

A type-safe generic DTO

This problem can be solved by identifying attributes not with plain strings, but with unique parametrized identifiers. To do so, let's define a parametrized class Attribute as follows:

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
public class Attribute<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private final String name;

    private Attribute(String name) {

        if (name == null) {
            throw new IllegalArgumentException();
        }
        this.name = name;
    }

    public static <S> Attribute<S> getInstance(String name) {
        return new Attribute<S>(name);
    }

    @Override
    public String toString() {
        return name;
    }

    //equals(), hashCode() ...

}

The type parameter <T> defines the data type of a given attribute. For each required attribute a constant identifier should be created, e.g. in form of static members of an interface:

1
2
3
4
5
6
7
public interface CustomerAttributes {

    public final static Attribute<String> NAME = Attribute.getInstance("CustomerAttributes.Name");

    public final static Attribute<Integer> ID = Attribute.getInstance("CustomerAttributes.Id");

}

Note, that Attribute instances are retrieved using a static factory method instead of directly invoking the constructor. This allows for the type of <T> of the created Attribute being determined automatically by the compiler by using type argument inference.

Now let's have a look a the generic DTO itself:

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
54
55
56
57
58
59
60
public class GenericDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Map<Attribute<?>, Object> attributes = new LinkedHashMap<Attribute<?>, Object>();

    public <T> GenericDTO set(Attribute<T> identifier, T value) {

        attributes.put(identifier, value);

        return this;
    }

    public <T> T get(Attribute<T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.get(identifier);

        return theValue;
    }

    public <T> T unset(Attribute<T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.remove(identifier);

        return theValue;
    }

    public void clear() {
        attributes.clear();
    }

    public int size() {
        return attributes.size();
    }

    public Set<Attribute<?>> getAttributes() {
        return attributes.keySet();
    }

    public boolean contains(Attribute<?>... identifiers) {

        for (Attribute<?> oneIdentifier : identifiers) {
            if (!attributes.containsKey(oneIdentifier)) {
                return false;
            }
        }

        return true;
    }

    @Override
    public String toString() {
        return "GenericDTO [" + attributes + "]";
    }

    //equals(), hashCode() ...

}

As with the "classic" generic DTO the attributes are stored in a map (by using a LinkedHashMap the toString() method returns the attributes in order of insertion), but the set() and get() methods are parametrized with <T>.

That way for an Attribute<T> only a value of type T can be stored in or retrieved from the DTO. For instance the call

1
dto.set(CustomerAttributes.ID, "Bob"); 

would yield in a compiler error, as "Bob" is of type String instead of Integer.

Note that the unchecked casts within the get() and unset() methods are actually safe, as attributes can be put into the map only by using the type-safe set() method.

As convinience the set() method returns the DTO itself. That allows for chaining multiple invocations:

1
dto.set(CustomerAttributes.ID, 123).set(CustomerAttributes.NAME, "Bob");

Summary

By using unique parametrized attribute identifiers a generic DTO can be created, that ensures type safety when putting attributes into it or retrieving them from it.

But one problem remains: the contract of methods that use GenericDTO as parameter or return value is rather loose. It is not obvious, which attributes are allowed/can be expected within one given GenericDTO instance.

This might be documented using JavaDoc, but actually there is another interesting approach for tackling this problem, which is shown in this post.

Sunday, January 18, 2009

Bookmark and Share

With the advent of generics in Java 5, also generic methods were introduced. Generic methods have one ore more type parameters, which can be used to specify the parameters or return type of a method. For example the JDK's collection utility class java.util.Collections declares a wide range of generic methods.

When using those methods, the concrete types to be used as parameters or return value are derived from the invocation context using type inference (Angelika Langer goes into detail on this topic in her great Generics FAQ here).

So for instance you can use the method emptyMap() of the mentioned class Collections as follows to initialize an empty Map<String, Long>:

1
2
3
4
5
6
7
8
9
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        Map<String, Long> map = Collections.emptyMap();
        assertTrue(map.isEmpty());
    }
}

Unfortunately, this doesn't work, if the return value of the generic method isn't assigned to a variable, but rather is used as parameter for another method invocation. So the following code fails with a compiler error at line 6 saying "The method doSomething(Map<String,Long>) in the type GenericMethodsTest is not applicable for the arguments (Map<Object,Object>)":

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        doSomething(Collections.emptyMap()); //compiler error
    }

    private void doSomething(Map<String, Long> map) {
        assertTrue(map.isEmpty());
    }
}

What I did till today in such a case was to assign the result of the generic method to a local variable and using this variable as parameter. This is somewhat ugly and actually not neccessary at all, as it is possible to specify the type parameters explicitely as follows:

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        doSomething(Collections.<String, Long> emptyMap());
    }

    private void doSomething(Map<String, Long> map) {
        assertTrue(map.isEmpty());
    }
}

Actually a practical but rather unknown feature of the Java language, which makes the code shorter while more readable at the same time. More information on explicitely specifying type parameters when calling generic methods can again be found at Angelika Langer's FAQ here.