views:

116

answers:

5

I have a class called "DataModel" or something, which is basically a unit of data which can be either a string or a number or a date or a boolean with various (identical) attributes.

What is the best way to write this model?

  1. Have the value be of type Object

    interface DataModel {
       Object getValue();  // cast to whatever is needed
       int getValueType(); // uses four constants
    }
    
  2. Have four different implementations "StringModel", "NumberModel", etc., each with their own typed "getValue()" method. This means if you had a DataModel, you'd have to cast to the correct Model to get to the value.

    interface DataModel {
       int getValueType();
    }
    interface NumberDataModel extends DataModel {
      Integer getValue();
    }
    ...
    
  3. Have four different methods, each throwing an exception if called for a wrong value type:

    interface DataModel {
      String getStringValue();
      Integer getIntegerValue();
      ...
      int getValueType();
    }
    
  4. Use generics. This has the downside that I theoretically could have any object of any type...on the other hand I could just throw an IllegalStateException in the constructor if T was not one of the 4 allowed types...

    interface DataModel<T> {
      T getValue();
    }
    
  5. It doesn't matter. Any of the above. ;)

+3  A: 

4 seems the best - even if you don't want to implement any old type there's no particular reason why you shouldn't theoretically allow it - it won't interfere with anything else you're doing.

cyborg
+2  A: 

1 is fine if you don't plan to add many/any new types AND you don't need to allow 3rd parties to add their own types. I would probably use an enum rather than an int.

I don't see much advantage of doing 2 over 4. 4 is generic, tho you may want to include the getValueType() even in the generic case so that you can have code interrogate the type at runtime, often useful.

I don't think 3 is the right way to go unless the content of your model supports being retrieved in different ways (like JDBC does somewhat) but I don't think that is the case here.

From past experience I would do 4 and add the getValueType() to it.

Mike Q
Is this your final answer? :)
Epaga
+2  A: 

None of the above.

Since you need to know the class to do something sensible with the result of getValue(), there is nothing to be gained from having a common interface containing getValue()-like methods in this case.

Have a base class with the common parts, then subclass and add what you need. You can use instanceof instead of having getValueType(), but if you really want a getValueType, it can be added to the base class as an abstract method. If you go with the getValueType, i'd suggest it returns an enum, not int.

Buhb
there are plenty of use cases for grouping them with a common interface. anywhere where the operations need to be aggregated over the entire set of values as in validating a bunch of data items, where the type may differ.
Anurag
You can refer to the superclass when doing common stuff. i've edited the answer slightly to make my intent clear. You can of course add an interface that includes all public methods of the base class, but unless you want to implement the interface with a new class altogether, I see little use in doing so.
Buhb
i think that's where the generics version works great as the whole point of generics is to avoid having a class for each acceptable data type, and enforcing type-safety at compile time.
Anurag
if you go with the generics version, you can't have several instances of DataModel in a collection anyway.
Buhb
@Buhb: nobody talks about DataModel in a collection here
nanda
Certainly, but the discussion in the comments to this answer is about grouping together several objects implementing a given interface / extending a given base class. I was referring to the collection merely as an example, but the argument holds in other circumstances as well. You can't (without getting a compiler warning) refer to a DataModel object if it is defined as DataModel<T>. You need to refer to it as DataModel<Number> for instance and then you've already lost the possibility to operate on the parts of DataModel that is common for any DataModel instance.
Buhb
@Buhb, Why can't I define the generic type as something like `<? extends DataModel>` or `<? implements DataModel>` for example? I haven't used Java Generics much, but it does cover a lot. As far as storing them in a collection goes, yes you are right - we cannot store multiple DataModel<T> types like Number, String etc. into a collection unless the generic definition `implements` or `extends` an interface or class.
Anurag
I think this discussion is growing a bit too long for having among comments. Feel free to ask about this in a new question and I'll be happy to try to answer it, and I'm sure we'll get input from other smart people as well. I'm not sure the 600 characters for a comment is enough for me to make the point I want.
Buhb
+2  A: 

4 is the best answer. You'll get flexibility and convenience.

On the other hand, if you really want to restrict the type, you can do with combination of 4 and 2. Like this:

interface DataModel<T> {
   T getValue();
}
interface NumberDataModel extends DataModel<Number> {
   // empty
}
class NDM implements NumberDataModel {
   Number getValue() { return ... }
}

Then you can make the DataModel interface protected/default.

nanda
A: 

As a generic alternative, you can use Class Literals as Runtime-Type Tokens and use newInstance() to get type-safe instances of your data elements. This allows compile-time checking via generic parameters and runtime checking via isAssignableFrom().

trashgod