4

I have an interface with 6 methods used to manage datasets. The only method that differs between implementations is getSerializedVersion() and the constructor that is able to parse the serialization string.

public interface DataSets {
  public void addEntry(...);
  public void  removeEntry(...);
  public void manipulateEntry(...);
  public SomeType getEntry(...);
  public List<SomeType> getAllEntries();
  // This differs:
  public String getSerializedVersion()
}

I can't change the Interface.

My first idea was to generate an abstract class and implement the first five methods. For the concrete implementations (e.g. DataSetsXML, DataSetsYAML, ...) I only have to implement getSerializedVersion() and the constructor that that is able to read the String and initialize the object.

To make it more testable a different design might be better (https://stackoverflow.com/a/7569581) but which one?

Answers might be subjective, but I think there are some general rules or a least (objective) advantages and disadvantages of the different approaches,...

Community
  • 1
  • 1
Edward
  • 4,453
  • 8
  • 44
  • 82
  • 4
    You could use a single class, and delegate to a Serializer, passed as argument to the constructor, to implement getSerializedVersion(). Have N implementations of Serializer (YamlSerializer, XmlSerializer, etc.) – JB Nizet Mar 30 '16 at 15:10
  • @JBNizet's answer is the way to go. From a class responsibility perspective, serializing seems like a different job than managing the dataset. No need to use an abstract class and further encode this design mistake, imo. – Cardano Mar 30 '16 at 15:14
  • @JBNizet: Can you write an answer from your comment. – Edward Mar 30 '16 at 15:42
  • @Edward It wouldn't be much different from Jack's answer. – JB Nizet Mar 30 '16 at 15:52

2 Answers2

9

From what you explain the difference is something that is not related to the behavior of the class but just how it is serialized and unserialized. What I mean is that the DataSetsXML and DataSetsYAML would have the same identical funcionality but they would be serialized into different formats.

This means that there is no benefit in keeping getSerializedVersion() coupled with the DataSets class. You should totally decouple them.

You could have a serialization interface sort of:

interface DataSetsSerializer
{
  public DataSets unserialize(String string);
  public String serialize(DataSets sets);
}

and then take care of differente implementations just in this class, eg:

class YAMLDataSetsSerializer implements DataSetsSerializer
{
  public DataSets unserialize(String string) {
    DataSets sets = new DataSets();
    ...
  }

  public String serialize(DataSets sets) {
    ...
  }
}

By elaborating on JB Nizet comment, if you have to keep a DataSetsSerializer inside a DataSets instance (which IMHO makes no sense since they should be decoupled in any case, as a specific way of serialization shouldn't be bound to the data to be serialized) then the approach would be the following:

class DataSets {
  final private DataSetsSerializer serializer;

  public DataSets(DataSetsSerializer serializer, String data) {
    this.serializer = serializer;
    serializer.unserialize(this, data);
  }

  @Override
  public String getSerializedVersion() {
    return serializer.serialize(this);
  }
}

This requires a slight change in the proposed interface and it's not a clever design but it respects your requirements.

Jack
  • 131,802
  • 30
  • 241
  • 343
  • Since he can't change the interface I would also add that he should throw an `UnsupportedOperationException` with a message about using these serializers when `getSerializedVersion` is called. – Daniel Moses Mar 30 '16 at 15:16
  • I agree with decoupling the serialization. But then the API user needs to hold two references - one for the `DataSets` and one for the serialization. (Please correct me if I'm wrong). I guess that's why the Interface as been defined as it is. So probably the solution from `JB Nizet`'s comment (Delegate to a serializer added via the constructor) is the way to go in my case. Or do you see any disadvantage of that approach? – Edward Mar 30 '16 at 15:48
  • Would be great if you discuss `JB Nizet`'s comment in your answer, so that I can accept it. – Edward Mar 30 '16 at 16:24
  • @Edward: I added a specific description of his solution, which is basically mine but with a tighter coupling. – Jack Mar 30 '16 at 16:30
  • @Jack: Thanks,... So in you preferred solution, complete decoupling requires references to the `serializer` and to the `dataset`, am I right? – Edward Mar 30 '16 at 16:35
  • Yes, which allows you, for example to unserialize a data set with a format, modify it and the serialize it with another format, since the two things are unrelated. – Jack Mar 30 '16 at 17:10
  • @Jack: You're right. I haven't thought about that (since in my case it is not needed) I'll add this to you answer. – Edward Mar 31 '16 at 05:54
1

I think it is reasonable to use an abstract class. You can test the concrete implementations of the abstract class (which indirectly tests the abstract class as well).

J Fabian Meier
  • 33,516
  • 10
  • 64
  • 142