简体   繁体   中英

Java Map Object, Objects represented as a map

I'm considering using a different representation of an object in Java to allow more flexibility. The biggest need I have is to dynamically pass data through the system to be handled with script engine at some point in the system.

My question is whether it's a bad idea to represent an object purely as a Map<String, Object> For example if I was to go forward with it am I creating some serious issues for myself in the future? Is it inherently a bad idea to do this and why? etc...

For Example I could implement it kind of like this

public class Foo {
   private final Map<String, Object> data;

   public Foo ()
   {
      this.data = new HashMap(); 
   }

   public String getProperty1() { return this.data.get("property1"); }
   public int getProperty2() { return this.data.get("property2"); }
   public Object getDynamicData(String key) { return this.data.get(key); }

   //Use builder to set properties in data and create the Foo object...
}

There's other things I've done in playing with this like using a second object called FooFields that holds the types of known properties and keys to make things a little bit better for lookup. But I've already seen some issues with doing this in general.

  • It definitely adds complexity vs POJO
  • It's more difficult to transition the object to a UI layer or DB layer
  • Despite building it with FooFields to give me compile time error checking there's still some cases where I can get runtime errors

Another solution could be

public class Foo {
   private final String property1;
   private final int property2;
   private final Map<String, Object> dynamicData;

   public Foo (String property1, int property2, Map<String, Object> data)
   {
      this.property1 = property1;
      this.property2 = property2;
      this.dynamicData = data;
   }

   public String getProperty1() { return this.property1; }
   public int getProperty2() { return this.property2; }
   public Object getDynamicData(String key) 
   { 
       return this.dynamicData.get(key); 
   }
}

The biggest need I have is to dynamically pass data through the system to be handled with script engine at some point in the system ... My question is whether it's a bad idea to represent an object purely as a Map<String, Object> For example if I was to go forward with it am I creating some serious issues for myself in the future? Is it inherently a bad idea to do this and why?

I think this is a perfectly legal approach to your problem, and for far more information processing problems than many realise. Maps model information sets for what they are ... information sets.

  • Information means "data with meaning" (that gives "form" to the mind). A single value with a meaningfully named key (in a map) is literally a piece of information.

  • (Open) sets are collections of distinct elements (eg key-value pairs), and that's it.

Firstly, business people think of information sets as literally information sets - often in spreadsheets, or paper forms or screens. They do not think of them as living breathing objects, with "behaviour", that should somehow be "cohesively located together with the information." (If you really think you have a business axiom or invariant that spans information items that you think is worth implementing in an stateful object - fill your boots - however that same invariant is just as easily applied in a stateless object method that is passed a map, and is easier to name.)

Also it is far easier to find good long-lived domain-based names for individual information items - like "customerForename", or "productId", or "lineItemQuantity" - than it is to find long-lived names and contracts for business "entities". For example - lets say you named your information set "Customer" at the outset but the information set coming in now has both Customer and some Orders information in it. Should you break apart the info into separate Customer and Order objects in the hope that one day the separation will pay off. In theory the Order have a list of LineItem objects, but shouldn't LineItems contain both "productId" and the "productTitle" to be reusable in other contexts, and yet my information set doesn't have "productTitle"s so perhaps they should be Optional? How do I validate that? Etc etc etc .... Now my information set is spread over three DTOs (perhaps in different packages) just for capturing this single information set idiomatically. The modelling exercise never completely fits changing requirements, gets more and more coupled, and becomes harder and harder to maintain - for very little gain.

Alternatively you could just stuff both Customer and Orders information into the same map as was used for just the Customer information before - again using well chosen long lived key names to specify the new information held in the map. They arrived together - why not keep them together. If you need to split them into separate maps - do so. Maps (really sets) are easy to split, merge, grow and shrink without becoming schizophrenic about what they represent - because unlike POJO/DTOs they don't make hard choices about what they are supposed to include that makes life difficult for the code that uses them. No longer requiring a field in your information set should be a non-breaking change, but try removing a the 3rd argument of a POJO's constructor without causing compilation problems all across your production and test code.

Also, if you want to transform from one information set to another - try accumulating the transformed data in the same map . After all aren't you simply adding new information on top of the old? If it needs separating later it can be. In the olden days customers' paper application forms were kept and any consequential internal letters/decisions were appended to it (with a paper clip), rather than replacing it. This means that processing rules can be applied step by step in a pipeline that only references the data in the map that it cares about and the map slowly accumulates more information. That makes it far easier to subdivide and add (and test) rules without carrying about extra information unnecessarily, or breaking contracts everywhere when the information set grows or shrinks.

It is not OO - but that is not a reason alone to avoid it. It is however the idiomatic approach in many other languages, OO ones or otherwise. Javascript's objects are simply maps. Groovy, Ruby and Python all see ubiquitous use of native map/hash/dictionary type structures. Clojure consciously embraces maps (lista and sets, with a unifying "seq" abstraction) as the ubiquitous aggregates for all information passing through functions, for these good reasons and others. True these are all dynamic languages, and the humble java.util.Map can be painful to use for these kinds of patterns as class casting is often required (unless you know ahead of time what your map will contain always) and there is no optionalGet() etc. More importantly you can no longer simply use IDE autocomplete to tell you what information is available in the class (yikes!). Nevertheless, the pay offs are there.

Most of these points are well versed topics in the Clojure community particularly, but I wish there were more discussion along these lines in the Java community also :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM