Is this the optimal way to bundle additional data structures into a value object?

I have a value ( Test2

) object that will contain several optional data structures (implementations PropertySet

). I cannot compose them all in Test2

, because there will be many options and implementing all permutations will result in the proliferation of classes. I came up with the following solution:

public class Test2
{

    static interface PropertySet
    {

    }

    static class LocationInfo implements PropertySet
    {
        String lat;
        String lng;

        public LocationInfo(String lat, String lng)
        {
            this.lat = lat;
            this.lng = lng;
        }

    }

    private Map<Class<? extends PropertySet>, PropertySet> propertySets = new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T extends PropertySet> T fetchPropertySet(Class<? extends T> propertySetType)
    {
        T result = (T) propertySets.get(propertySetType);
        return result;
    }

    public LocationInfo getLocationInfo()
    {
        return this.<LocationInfo> fetchPropertySet(LocationInfo.class);
    }

    public static void main(String[] args)
    {
        Test2 test = new Test2();
        test.propertySets.put(LocationInfo.class, new LocationInfo("1", "1"));

        LocationInfo locationInfo = test.<LocationInfo> fetchPropertySet(LocationInfo.class);
        System.out.println(locationInfo.lat + ", " + locationInfo.lng);

        LocationInfo locationInfo2 = test.getLocationInfo();
        System.out.println(locationInfo2.lat + ", " + locationInfo2.lng);
    }
}

      

My question is, is this solution considered good practice for this kind of problem or not?

Note that I cannot use external libraries like Guava, but java 8 is used.

+3


source to share


3 answers


If you have a type with a lot of additional properties, using a map is sufficient. However, it is doubtful that you are using the property type as the key. Compare with regular fields / properties which differ in name and have a type i.e. Where you can have multiple properties of the same type. Your example LocationInfo

shows this. It represents a location with no specific meaning. It is quite possible to imagine that there could be an object that has two type properties LocationInfo

, for example. startLocation

and endLocation

.

Therefore, you should not mix up the use of type to define a property and a type that represents a value. However, there is no reason for this interface PropertySet

. It does not add values ​​for types, but only an unnecessary constraint.

To fix this, use a key containing both the name and type of the property:



public class Test2
{
    static class LocationInfo
    {
        String lat;
        String lng;

        public LocationInfo(String lat, String lng)
        {
            this.lat = lat;
            this.lng = lng;
        }
    }

    private static final class PropKey {
        final Class<?> type;
        final String name;

        public PropKey(Class<?> type, String name) {
            this.type = Objects.requireNonNull(type);
            this.name = Objects.requireNonNull(name);
        }
        @Override
        public int hashCode() {
            return Objects.hash(name, type);
        }
        @Override
        public boolean equals(Object obj) {
            if(obj==this) return true;
            if (obj == null || !(obj instanceof PropKey)) return false;
            final PropKey other = (PropKey) obj;
            return type==other.type && name.equals(other.name);
        }
    }
    private final Map<PropKey, Object> properties = new HashMap<>();

    public <T> T fetchProperty(Class<T> type, String name)
    {
        return type.cast(properties.get(new PropKey(type, name)));
    }
    // your decision whether this should be public
    <T> void putProperty(Class<T> type, String name, T value)
    {
        Objects.requireNonNull(value);
        properties.put(new PropKey(type, name), value);
    }

    public LocationInfo getPosition()
    {
        return fetchProperty(LocationInfo.class, "position");
    }

    public static void main(String[] args)
    {
        Test2 test = new Test2();
        test.putProperty(LocationInfo.class, "position", new LocationInfo("1", "1"));

        LocationInfo locationInfo = test.fetchProperty(LocationInfo.class, "position");
        System.out.println(locationInfo.lat + ", " + locationInfo.lng);

        LocationInfo locationInfo2 = test.getPosition();
        System.out.println(locationInfo2.lat + ", " + locationInfo2.lng);

        test.putProperty(String.class, "debugInfo", "hello world");
        System.out.println(test.fetchProperty(String.class, "debugInfo"));
    }
}

      

As hinted already, there shouldn't be a hard connection between an entity and its property types, so you can turn the class LocationInfo

into a top-level type for all use cases involving latitude / longitude pair. It is highly recommended to use the immutable type template for this class, as with all property types that you intend to store in the map, since the general method of placing them is not capable of creating defensive copies to protect against false exchange of mutable objects between multiple instances Test2

.

Another thing to consider, since properties are optional, getter methods can return Optional<PropertyType>

instead of encoding the absence of a property with null

.

+2


source


Guava has a ClassToInstanceMap that you can use, which stores a class map -> an instance of that class:



ClassToInstanceMap<PropertySet> map = MutableClassToInstanceMap.create();
map.putInstance(LocationInfo.class, new LocationInfo("1", "1"));

LocationInfo li = map.getInstance(LocationInfo.class);

      

0


source


I would suggest

public <T extends PropertySet> T fetchPropertySet(Class<T> propertySetType)

      

(wildcards).

This way, you don't need to explicitly specify the generics argument, and you may not need a wrapper like getLocationInfo

. And you can still use the returned instance T

as the superclass of class T.

Some questions about your intent: Do you need multiple instances of the same class? What are you doing in subclasses? Superclasses? Currently, each class will have its own instance. You may want one instance of most of the derived classes available to all of their superclasses (or some of its superclasses).

0


source







All Articles