3

I'm trying a set of converters which will convert an object to some older form. And there are different objects which are converted. By older form I mean changing the values of some fields and in case of file objects making changes such that the new file complies to older schema, etc.

A convertObjectAToOldFormat(A a);
B convertObjectBToOldFormat(B b);

Should I create class for each of this converters? Or Can I put all of them in a single class and expose the interface to clients? Or is there any design which will handle this better?

Language used is Java

1
  • 1
    Could you provide an example? May 18, 2015 at 12:48

3 Answers 3

5

Which class should know how to convert a new A to a legacy A?

Maybe it's the A class itself which has this knowledge. It may know how new and legacy values are mapped, which fields should be added or removed, etc. In this case the following approach seems quite natural:

A legacyA = a.toLegacy();

The benefit of this approach is consistency: it's similar to toString() method which converts, say, an integer or a double to a string. The interesting part here is that while toString of an int creates an instance of a completely different type (string), toLegacy returns an instance of the same type.

The drawback is the violation of SRP principle, as well as the fact that the conversion code (especially to some legacy forms) is polluting the class. If the conversion logic is complex and has few things to do with A itself, it makes sense to move this code to a separate class.

This situation is very similar to factory pattern and abstract factory pattern: when the logic required for creating instances of a class becomes too complex to remain in the class itself (in a form of a static method), then factory pattern is used. If there are many similar entities, an abstract factory pattern starts to be valuable.

In a similar way, you can have your abstract converter class, and the concrete classes which deal with the conversions of different business entities:

abstract class ConverterToLegacy<T>
{
    public T Convert(T businessEntity);
}

class ConverterToLegacyA implements ConverterToLegacy<A>
{
    public A Convert(A a)
    {
        ...
    }
}

class ConverterToLegacyB implements ConverterToLegacy<B>
{
    public B Convert(B b)
    {
        ...
    }
}
5
  • 2
    Part of me wants to up vote this answer, but I don't see a reason why A should ever convert itself to any format. It seems to be a violation of the SRP. It's also not really good for future versions (although that may not be a consideration here), since is A supposed to convert itself to everything?
    – Thomas Owens
    May 18, 2015 at 13:01
  • @ThomasOwens: Same reason as int or double has toString(): why would it convert itself to a string? By having a toLegacy(), consistency is ensured, making it easier to find the relevant method. Of course, there are cases when the slight benefit of consistency is not worth it, in which case one would probably use the pattern I described in the second part of my answer. May 18, 2015 at 13:22
  • After writing my comment, I thought about toString(). I'm not convinced that a having toString() is even good practice. However, that is part of the language of convention that every object has a few methods, such as hashCode(), equals(Object), and toString(). Just because the language designers did it doesn't mean it's the right thing to do. Maybe you can add your reasoning to the answer to make it easier for me to up vote, but I'm still not convinced that it's a good idea to ever do.
    – Thomas Owens
    May 18, 2015 at 13:27
  • @ThomasOwens: you're right, toString() is far from being a good example of OOP and SRP (especially since conversion from an integer or a double to a string is usually quite complex). I edited my answer to highlight the drawbacks you've noted. May 18, 2015 at 13:30
  • Thanks. I feel much better giving this answer a +1 now. I'm much more in favor of the second approach, personally, but depending on the complexity of the project, the first approach may be fine.
    – Thomas Owens
    May 18, 2015 at 13:31
1

You could have a factory which provides the correct adapted object.

Exposing a series of classes or a single class with multiple methods assumes that you have some knowledge before hand of the system. Another (problematic, I think) issue is that by exposing all the adapters you are giving the caller (client) complete control over which adapter gets called.

This, in my opinion, could cause some issues since someone might try and find a way (on purpose of by accident) around some internal mechanisms you might have (validation comes to mind) which could be problematic.

Having 1 endpoint which is exposed to the user and having some logic which does everything which needs doing provides you with more control.

0

You seem to be asking for two different things here:

  1. changing the values of some fields

Are you referring to how you need to normalize values' formattings ("yyyy-MM-dd" to "ddMMyy") or possibly even data types here (BigDecimal to int)? If so, I'll suggest creating a conversion class to handles these, so that you can safely eliminate both the conversion and API pollutions within your class.

  1. making changes such that the new file complies to older schema

Wells, serialization libraries and how they handle schema resolution came to my mind immediately, such as how Apache Avro does it... If you can consider your objects/data files (hmms a bit of a stretch though for the latter) to be handled as a form of serialization, then these libraries are definitely worth a look here, because the problem is most likely solved for you. For the full low-down on schema resolution comparisons, this article is a great, if slightly overwhelming, read too.

edit Given the risk of link rot, I'll just quote the relevant parts of the article here...

  1. Then you find that people are stuffing all sorts of random fields into their objects, using inconsistent types, and you’d quite like a schema and some documentation... hey, if you had a schema, you could avoid storing objects’ field names, and you could save some more bytes!

Once you get to the fourth stage, your options are typically Thrift, Protocol Buffers or Avro. All three provide efficient, cross-language serialization of data using a schema, and code generation for the Java folks.

In real life, data is always in flux. The moment you think you have finalised a schema, someone will come up with a use case that wasn’t anticipated, and wants to “just quickly add a field”. Fortunately Thrift, Protobuf and Avro all support schema evolution: you can change the schema, you can have producers and consumers with different versions of the schema at the same time, and it all continues to work. That is an extremely valuable feature when you’re dealing with a big production system, because it allows you to update different components of the system independently, at different times, without worrying about compatibility.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.