开发者

"Type safe" UUIDs?

We use make heavy usage of java.util.UUID in our projects to identify ojbects and do operations on them like in this interface:

List<UUID> searchPerson(String text);
Person fetchPerson(UUID personUUID);

List<UUID> searchAdress(String text);
Person fetchAdress(UUID adressUUID);

But what can happen now, and is a source of Runtime errors, is t开发者_开发百科hat a developer accidently passes a personUUID to the fetchAdress method, which should not happen.

Is there any way to make this kind of "type safe" so hat he can't pass the fetchAdress method a personUUID? Maybe there is a way to do this using generics?


Build a class that includes UUID functionality by composition, and then subclass it for each individual "type" of UUID you need.

If you don't need/want the full UUID API on your subclasses, you could be extra lazy and just wrap it. Something like this:

public class MyUUID {
    private UUID uuid;
    public MyUUID() {
         uuid = UUID.randomUUID();
    }

    public UUID getUUID() {
        return uuid;
    }
}

public class PersonUUID extends MyUUID { }
public class AddressUUID extends MyUUID { }

If manually unwrapping to get the UUID object out annoys you, just implement the full UUID API on MyUUID and delegate each call to the uuid member.


Well since all you need is a typed UUID you can simple create an interface for that using generics.

package com.stackoverflow.q1747780;

public interface UUIDTyped<T>
{
    public UUID value();
}

Now assuming that Person and Address UUIDs are created from different sources you can have a class for each, implementing that interface.

package com.stackoverflow.q1747780;

import java.util.UUID;

public class UUIDFactory
{
    public static class PersonUUID implements UUIDTyped<Person>{

        /* (non-Javadoc)
         * @see com.stackoverflow.q1747780.UUIDTyped#value()
         */
        @Override
        public UUID value() {
        return UUID.randomUUID();
        }};


    public static class AddressUUID implements UUIDTyped<Address>{


        /* (non-Javadoc)
         * @see com.stackoverflow.q1747780.UUIDTyped#value()
         */
        @Override
        public UUID value() {
        return UUID.randomUUID();
        }};


    public <T> UUIDTyped<T> newUUID() {
    return new UUIDTyped<T>()
    {        
        /**
         * There is no difference on how Person and Address UUIDs are generated
         */
        @Override
        public UUID value() {
        return UUID.randomUUID();
        }
    };        
    }

    public UUIDTyped<Person> newPersonUUID(){
    return new PersonUUID();
    }

    public UUIDTyped<Address> newAddressUUID(){
    return new AddressUUID();
    }
}

Proof of concept

package com.stackoverflow.q1747780;

import junit.framework.Assert;

import org.junit.Test;


public class UUIDFactoryTest
{
    @Test
    public void testPersonUUID()
    {
        UUIDFactory uuidFactory = new UUIDFactory();

        UUIDTyped<Person> newUUID = uuidFactory.newPersonUUID();

        Assert.assertNotNull(newUUID.value());
    }

    @Test
    public void testAddressUUID()
    {
        UUIDFactory uuidFactory = new UUIDFactory();

        UUIDTyped<Address> newUUID = uuidFactory.newAddressUUID();

        Assert.assertNotNull(newUUID.value());
    }
}

Else you can get away with just dummy interfaces.

Proof of concept

package com.stackoverflow.q1747780;

import junit.framework.Assert;

import org.junit.Test;

public class UUIDFactoryTest
{
    @Test
    public void testNewUUID()
    {
        UUIDFactory uuidFactory = new UUIDFactory();

        UUIDTyped<Person> newUUID = uuidFactory.newUUID();        
        UUIDTyped<Address> addressUUID = uuidFactory.newUUID();

        Assert.assertNotNull(newUUID.value());
        Assert.assertNotNull(addressUUID.value());
    }    
}

Finally your service will be like

package com.stackoverflow.q1747780;

import java.util.List;

public interface Service<T>
{
    public List< UUIDTyped<T> > search(String text);

    public T fetch( UUIDTyped<T> uuid);
}

with a PersonService class

package com.stackoverflow.q1747780;

import java.util.List;

public class PersonService implements Service<Person>
{

    /* (non-Javadoc)
     * @see com.stackoverflow.q1747780.Service#fetch(com.stackoverflow.q1747780.UUIDTyped)
     */
    @Override
    public Person fetch(UUIDTyped<Person> uuid) {
    return null;
    }

    /* (non-Javadoc)
     * @see com.stackoverflow.q1747780.Service#search(java.lang.String)
     */
    @Override
    public List< UUIDTyped<Person> > search(String text) {
    return null;
    }
}

and an AddressService class

package com.stackoverflow.q1747780;

import java.util.List;


public class AddressService implements Service<Address>
{

    /* (non-Javadoc)
     * @see com.stackoverflow.q1747780.Service#fetch(com.stackoverflow.q1747780.UUIDTyped)
     */
    @Override
    public Address fetch(UUIDTyped<Address> uuid) {
        // TODO Auto-generated method stub
        return null;
    }

    /* (non-Javadoc)
     * @see com.stackoverflow.q1747780.Service#search(java.lang.String)
     */
    @Override
    public List<UUIDTyped<Address>> search(String text) {
        // TODO Auto-generated method stub
        return null;
    }

}

Testing correct type for PersonService

package com.stackoverflow.q1747780;

import java.util.List;

import org.junit.Test;



public class PersonServiceTest
{
    @Test
    public void testFetch()
    {
        UUIDFactory uuidFactory = new UUIDFactory();
        PersonService service = new PersonService();

        Person person = service.fetch( uuidFactory.newPersonUUID() );        
    }

    @Test
    public void testSearch()
    {
        PersonService service = new PersonService();

        List< UUIDTyped<Person> > uuidList = service.search("foo");        
    }
}

Testing correct type for AddressService

package com.stackoverflow.q1747780;

import java.util.List;

import org.junit.Test;

public class AddressServiceTest
{
    @Test
    public void testFetch()
    {
        UUIDFactory uuidFactory = new UUIDFactory();
        AddressService service = new AddressService();

        Address address = service.fetch( uuidFactory.newAddressUUID() );        
    }

    @Test
    public void testSearch()
    {
        AddressService service = new AddressService();

        List< UUIDTyped<Address> > uuidList = service.search("foo");        
    }
}


As far as I know, there is no real way to use a 'type safe' UUID. One thing to try would be to create an empty person object and set its UUID value. So your signature could be something like:

Person fetchAdress(Person personWithOnlyUUIDSet);

The fetch mechanism would be responsible for populating the Person object sent in with the correct values from the data store.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜