3

The problem is the following: I have to download a set of JSON files and convert them to a certain format. There are 5 output formats (Let's call them A, B, C, D, E) and all of the downloaded json files are going to be in of of two formats (call them J1, and J2).

So once I decide whether I am downloading J1 or J2 type of files, I should be able to convert them into one of the 5 formats. Basically this means that there are potentially 10 different conversions possible.

I was wondering how to better design this application the current setup is the following:

  1. Converter class - which implements common behaviors.
  2. An interface which all strategies should have
  3. 10 different strategies. from J1 to (A,B,C,D,E) and J2 to (A,B,C,D,E)

All 10 different strategies implement the interface of converting to necessary format, a Converter object contains a strategy which is set at run time when I understand exactly which one is required (based on user input)

This seems unbelievably sloppy, maybe I am not understanding the Design Pattern as necessary. If I have all the strategies implemented in different classes, they all have a convert function, why should I bother to create a Converter object and initialize it with my chosen strategy, when I could simply make a ConcreteStrategy type of object and call the convert function on them.

6
  • @Erno Can you explain your design in detail? I am curious to understand your way of doing it. Are you suggesting to have two concrete converters for J1 and J2 respectively each having five functions depending on the output format? Feb 13, 2020 at 12:35
  • @Erno I think you flipped the direction of conversion, but otherwise I agree with your suggestion.
    – slepic
    Feb 13, 2020 at 20:22
  • 1
    As J1 and J2 can be mapped to A, B, C, D and E, it might be possible and useful to define a common structure in the middle. Translate J1 and J2 to the Common structure (2 conversions) and translate the common structure to A, B, C, D and E (5 conversions) makes 7 conversions. Easier to maintain.
    – Emond
    Feb 14, 2020 at 5:59
  • 1
    I am suggesting to have a converter from J1 to I and a converter from J2 to I, from I to A, from I to B, from I to C, from I to D and from I to E. I being the new internal format. This way you only need one converter per format (to or from the I format). I am not adding this as an answer because it doesn't answer your question about the strategy pattern but instead proposes a different approach altogether.
    – Emond
    Feb 14, 2020 at 6:01
  • 1
    @Erno has the right idea. I don't think the Strategy pattern really helps much here. It's useful when you want a given object to be able to change it's implementation after it is created. You don't really need that here, you just need to select a mapping. By building a 'canonical' representation, you go from 10 mappings to 2 (J1, J2 to canonical) and plus 5 (canonical to A,B,C,D, and E), as you add more formats on either side, this really starts to pay off.
    – JimmyJames
    Feb 14, 2020 at 16:15

3 Answers 3

1

I would solve this problem not with strategy but visitor pattern. I would structure the data formats J1 and J2 in a hierarchy as:

public abstract class JsonFormat {
  public string Text {get; set;}
  public abstract void Accept(IVisitor visitor);
}

public class J1 : JsonFormat {
  public override void Accept(IVisitor visitor) {
    visitor.Visit(this);
  }
}

public class J2 : JsonFormat {
  public override void Accept(IVisitor visitor) {
    visitor.Visit(this);
  }
}

Now, we will have a visitor for each possible conversion format. Basically, the visitor is the converter.

public interface IVisitor {
  void Visit(J1 firstFormat);
  void Visit(J2 secondFormat);
}

public class AFormatConverter : IVisitor {
  public string J1Conversion {get;}
  public string J2Conversion {get;}

  public void Visit(J1 firstFormat) {
    //implement the logic of conversion of J1 to A here
    //set J1Conversion string to the conversion after logic
  }

  public void Visit(J2 secondFormat) {
    //implement the logic of conversion of J2 to A here
    //set J2Conversion string to the conversion after logic
  }
}

In the same manner you can write Visitors for each possible conversion format like B,C,D,E. With Visitor pattern you will have only 5 converters. This is one way of solving problem and I agree that Visitor pattern is not easy to understand. Also, there are downsides and upsides of every solution.

0

I think you are over-thinking it. Depending on your language's level of support for generics and/or lambdas, an implementation could look like this:

using System;

public interface IConverter<T1, T2>
{
    T2 Convert(T1 source);
}

public class StringStringConverter: IConverter<string, string>
{
    public string Convert(string source)
    {
        return source.ToLower();
    }
}

public static class Stackoverflow
{
    public static T2 Convert<T1, T2>(T1 source, IConverter<T1, T2> converter)
    {
        return converter.Convert(source);
    }
}

public class Program
{
    public static void Main()
    {
        // in C#, can use reflection to find class which implements the desired converter
        var func = new StringStringConverter();
        var input = "Hello";
        var result = Stackoverflow.Convert<string, string>(input, func);
        Console.WriteLine(result);
    }
}

Now we have removed the decision which converter to pick and how to instantiate it from the actual conversion.

3
  • Who's overthinking what? You basically implemented code by OPs description which He thinks Is wrong And should be improved.
    – slepic
    Feb 14, 2020 at 5:58
  • Also you Are forgetting one thing. Nobody ever said those formáts Are anything more than a string. All ten of them may be string-string converters. Making it template solves nothing.
    – slepic
    Feb 14, 2020 at 6:03
  • It's a code sample ... it's for demonstration ... don't get hung up on the types
    – Martin K
    Feb 14, 2020 at 7:07
0

The point of the strategy pattern is to isolate the choice of what to do from the doing of it. Let's say you have a UI with a pair of dropdowns, InputType and OutputType, and a button DoConversion.

You can have UI code like

partial class UI {   
    public Converter { get; set; }

    void onInputTypeCombo_Changed(InputType inputType) {
        Converter.setInputStrategy(inputType);
    }

    void onOutputTypeCombo_Changed(OutputType outputType) {
        Converter.setOutputStrategy(outputType);
    }

    void onDoConversion_Clicked() {
        Converter.doConversion();
    }
};

And the Converter will instantiate concrete strategy objects based on the passed InputType and OutputType. But it won't do the conversion until the button is pressed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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