The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


Designing flexible method calls

Posted by fabriziogiudici on March 25, 2008 at 6:54 AM PDT
In the latest months I've not blogged a lot about technical topics, yet I'm still intensively working of stuff about my opensource projects (well, of course I also work on other stuff, but I can't blog on that because of NDAs with customers).

The point is that 95% of the development of blueMarine has been focused, since the latest XMas, on the Metadata component. It is a thing to import, store and query metadata extracted from photos and other documents, and I put some demanding requirements on it, including the capability of being pretty modular and extensible. It turned out to be a meta-persistence facility on its own for JavaBeans, that will be even useful for futher developments. Of course I didn't reinvent the wheel, the thing still uses JPA + Derby, but adds a layer of services that are focused on the special kind of JavaBeans I'll be using.

The design has been changed two or three times so far, and I'm pretty happy this is the first time I went in mostly-TDD mode for blueMarine (thus having 90%+ test coverage since the beginning), since I was able to perform dramatic changes with no fear of breaking anything. After some integration tests passed yesterday, I think that the design is almost complete, so I started consolidating and documenting (and also removing some possible over-engineering).

So here it is a quick technical post about it and I like to have some feedback before I freeze the APIs.

As I said, the facility should be extensible, and this implies having stable APIs that can be used by third parties. Stable APIs must be however extensible as far as it's possible. A Metadata interface is central in the facility, as it manages a collection of metadata items associated to a given object. Among other operations, Metadata exposes a findOrCreateItem() and a storeItem(). The implementation is SPI-based, that is independent implementations of providers will be activated on request. Providers might be able to read data from a photo file, read or store from/to an XMP file or work directly with the database. I can't predict in advance which kind of providers will be able in future, including some hopefully provided by third parties, so I'd like to have an open door so the application can pass some special parameter to them.

So, everything boils down in how to construct a method whose arguments are flexible enough, yet controllable, and also keeping an eye on readability of the code. Up to a few months ago, my usual way was to avoid fixed parameters such as:

public void myOperation (int param1, String param2, float param3);

in favour of the use of a specific JavaBean and a method with a single parameter:

public class MyOperationParameters
{
   public void setParam1 (int v)...
   public int getParam1()...
}

MyOperationParameters p = new MyOperationParameters();
p.setParam1(...);
public void myOperation (p);

In this way you can add new properties to MyOperationParameters and make sure that they have the proper default values.  Not bad, but it make it worse the readability of the caller code. Of course, you could initialize MyOperationParameters properties in the constructor and/or use a fluent interface, etc... But even worse, from the design point of view, is that MyOperationParameters becomes a focal point and any eventual extension (e.g. adding new parameters) performed by added code must use subclassing, which is evil in this case.

This time I opted for a different solution. For instance, the method storeItem() is declared as:

public <Item> int storeItem (MetadataItemHolder<Item> holder, Metadata.StoreOption... options);

where holder is a fixed and mandatory parameter; all the remaining stuff is a varargs of classes implementing StoreOption, that are straigthly passed to SPI implementations. StoreOption is just a tagging interface, that is it's empty, only used to have a minimal syntax cheking from the compiler (so I can't pass arbitrary objects). Then I have some concrete classes:

public interface Metadata
  {
    public static interface StoreOption
      {
      }

    public static enum StorageType implements StoreOption
      {
        ANY_TYPE
          {
            public boolean includes (final StorageType sourceType)
              {
                return true;
              }
          },
       
        INTERNAL
          {
            public boolean includes (final StorageType sourceType)
              {
                return sourceType == INTERNAL;
              }
          },
         
        EXTERNAL
          {
            public boolean includes (final StorageType sourceType)
              {
                return sourceType == EXTERNAL;
              }
          };
               
        public abstract boolean includes (final StorageType sourceType);
      }

    public static enum ReplaceOption implements Metadata.StoreOption
      {
        DONT_REPLACE,
        REPLACE
      }

    public static class OriginFilter implements StoreOption
      {
        private final String name;
       
        public OriginFilter (final String name)
          {
            this.name = name;
          }
       
        public String getName()
          {
            return name;   
          }
      }
   ...
}

As you can see, some of the classes have their own logic, as for the includes() method in StorageType (in contrast, adding several specific pieces of logic to a central MyOperationParameters would quickly turn it into a "small" fat class, if you pardon the pun).

With the proper static import, I can call:

metadata.storeItem(holder, INTERNAL, new OriginFilter("Database"), REPLACE);

which seems pretty readable and self-documenting to me. New SPI implementations can define their own parameter types by just implementing StoreOption.  Method implementations retrieve the arguments by a special lookup static method such as findOption() in the example below (which also provides the default value, just to avoid extra checks for null values):

    public void storeMetadataItem (final MetadataItemHolder<Item> holder, final StoreOption... options)
      throws Exception
      {
        logger.fine(String.format("storeMetadataItem(%s, %s)", holder, Arrays.toString(options)));
        ReplaceOption scratchOption = MetadataSpiUtils.findOption(options, DONT_REPLACE, ReplaceOption.class);
       
        ...
      }

Last but not least, using Arrays.toString() it's easy to produce a pretty good logging information such as:
12:04:39.736 [main] FINE MetadataItemProviderSuppor - storeItem(PhotoDataObject[V7210188.JPG], [Basic@131], [INTERNAL, REPLACE])

I don't think it's new stuff, and I'm sure people is already using this style somewhere, but I could not find examples.

So, what do you think?
Comments
Comments are listed in date ascending order (oldest first)

I used this something like this in my blog while trying to simulate named parameters. I applied it to GridBagLayout. http://aberrantcode.blogspot.com/2008/01/named-paramaters-and-layout-man... I've never seen this used in real code though.

All in all this seems fine. You might want to consider a small builder-style parameter object, that gets the mandatory objects at the constructor, and whose setters return itself (this).
This would allow you to write in a DSL-like manner:
new OperationParameters(aHolder).replace(true).type(EXTERNAL)
This would be type-safe, code completion could help (since, for example, type() would get a single parameter of type StorageType) and you could set the default stuff in the constructor. You could even statically import a factory method, and avoid the new, as in: createParameters(aHolder).replace(true).type(EXTERNAL) Look at pages 5-10 here (watch out, it's a PDF): Effective Java reloaded.
I think having a full-fledged builder here is a bit too much, Mr Bloch is suggesting this as he likes the actual objects to be final, which helps with concurrency.
Michael