0

I am trying to modify an opensource project (json serialization one: gson, I want to let it serialize/deserialize objects with circular references, which is not allowed now. ) to do it I have to change an abstract class widely extended.

I want to change the behaviour of that class with a strategy object so every child classes will be using it without needing to know it, so I have add the strategy object to the abstract class. As draft, I have done this:

...
public abstract class TypeAdapter<T> {

  CircularReferenceStrategy<T> circularStrategy = (new CircularStrategyFactory()).create();

  public void write(JsonWriter out, T value) throws IOException{
    circularStrategy.write(this, out, value);
  }
/**
   * Writes one JSON value (an array, object, string, number, boolean or null)
   * for {@code value}.
   *
   * @param value the Java object to write. May be null.
   */
 public abstract void doWrite(JsonWriter out, T value) throws IOException;
... 

That circularStrategy can be, p.e: let it fail by throwing a StackOverflowException (actual behaviour), or substitute circular references with 'null'/NullObjects or, as is done in Jackson (another serialization library), mark each object with an 'id' and add a reference to that id in the serialized json so the serialized/deserialized objects will have the circular references... whatever, the point is that they can be many strategies and that one of them must be selected before starting the serialization.

So my question is:

How should I tell the factory which strategy must be used?

I would set the info about which strategy must be used up in the GsonBuilder* and inject it to the abstract CLASS TypeAdapter (as a static field, to the class, not to the instances) but there is something that stops me from doing that, a spider-alert... Is it ok to inject things to a class? from where?

How would you do this?

* That is the main builder of the library, it builds a Gson object that has toJson and fromJson methods that are what you use

1 Answer 1

2

Responding to your questions

How should I tell the factory which strategy must be used?

Usually, you straight up tell the factory what you want, there's not much intricacy here. Whoever calls the factory is expected to either know what they're asking for (and they have a way to explicitly ask for what they want), or they don't know at all and rely on the factory to tell them what they must use.

I assume you're in the former scenario since you're trying to tell the factory what to use, which means I'd expect you to either have a method for every strategy you can create:

circularStrategyFactory.createFooStrategy();
circularStrategyFactory.createBarStrategy();
circularStrategyFactory.createBazStrategy();

Or you have a single method with a way to explicitly ask for what you want, e.g. using an enum:

circularStrategyFactory.createStrategy(StrategyTypes.Foo);
circularStrategyFactory.createStrategy(StrategyTypes.Bar);
circularStrategyFactory.createStrategy(StrategyTypes.Baz);

Is it ok to inject things to a class? from where?

Injecting things into a class is perfectly fine. This may sound obvious, but you inject something into a class from whever you're handling the class. That's the whole point of injection, it allows the caller to decide what they want to do.

I would set the info about which strategy must be used up in the GsonBuilder* and inject it to the abstract CLASS TypeAdapter (as a static field, to the class, not to the instances) but there is something that stops me from doing that, a spider-alert...

Injecting something into a static field make my spidey sense flare up as well. Injection tends to imply that different possible strategies can be used, but a static field is a global value. This seems to contradict itself.

What happens if you have two parts of your application which both use this adapter, but want to use different strategies? Using a static field, when one caller sets their preferred strategy, everyone now has to (unexpectedly) deal with this strategy instead the one they had set for themselves beforehand.

I'm not quite sure what it is you're trying to achieve here. Based on your intention to inject, I would avoid using statics. But maybe your particular use case makes sense for statics (e.g. a largely fixed and inherently global strategy, such as an application-wide setting).


My suggested answer

This depends on whether you want to set the strategy when initializing the object, or just before you actually serialize some data. The latter is relevant if you want the same adapter to use different strategies on a per-request basis.


If you want to do it during initialization, this is a prime example of dependency injection. Pass the strategy as a constructor argument:

public abstract class TypeAdapter<T> 
{
    private readonly CircularReferenceStrategy<T> circularStrategy;

    public TypeAdapter(CircularReferenceStrategy<T> circularStrategy)
    {
        this.circularStrategy = circularStrategy;
    }
}

This means your derived class (which you'll always have since this is an abstract class) can decide what the strategy is. The choice of strategy can either be dynamic or fixed.

public class DynamicStrategyTypeAdapter<T> : TypeAdapter<T>
{
    //Uses whatever strategy it's told to use.

    public DynamicStrategyTypeAdapter(CircularReferenceStrategy<T> circularStrategy) : base(circularStrategy)
    {
    }
}

public class FixedStrategyTypeAdapter<T> : TypeAdapter<T>
{
    //Always uses FooStrategy<T> as a strategy

    public FixedStrategyTypeAdapter() : base(new FooStrategy<T>())
    {
    }

    //You could also use a factory method here:

    public FixedStrategyTypeAdapter() : base(new CircularStrategyFactory().CreateCircularStrategyFor<T>())
    {
    }
}

If you want to set the strategy just before writing, then that suggests that either the strategy decision is made externally to the class, or that the strategy decision hinges on the internal state of the class.

If it's an external decision, then you simply need a method parameter. This is still a form of dependency injection, albeit on a method level.

public void write(JsonWriter out, T value, CircularReferenceStrategy<T> circularStrategy) throws IOException 
{
    circularStrategy.write(this, out, value);
}

Note: you could also pass a parameter of a different type (e.g. an enum) which you then use to fnd the correct strategy via e.g. a factory). The principle is the same.

If it hinges on the internal state of the class, create a private method that chooses the correct strategy (or, alternatively, a factory. If it fits your use case):

public void write(JsonWriter out, T value) throws IOException 
{
    var circularStrategy = chooseStrategy();

    circularStrategy.write(this, out, value);
}

private CircularReferenceStrategy<T> chooseStrategy()
{
    // ...
}

Your Answer

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

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