2

I have several configuration structures that can be JSON serialized and deserialized as follows:

namespace config
{

struct Foo
{
    std::string   hostname  {};
    std::string   ip_address{};
    std::uint32_t port      {};
};

JSON::Object serialize( config::Foo const& foo )
{
    // ...
}

config::Foo deserialize( JSON::Object const& object )
{
    // ...
}

} // namespace config

and in the serialization::JSON namespace I have main serialize and deserialize functions that call specific config serialize and deserialize functions:

template <typename T>
Object serialize( T const& obj, Allocator& allocator )
{
    return serialize( obj, allocator );
}

// ...

Questions:

  1. Is this a good design that follows SOLID principles (specifically Single Responsibility Principle)? Would making serialize/deserialize member functions of configuration structures break SRP or any of other SOLID principles? What in case adding XML serialization/deserialization?

  2. One thing that drives me towards making serialize/deserialize member functions is that I would need to use template specializations to make deserialize function free function for each configuration structure.

  3. If I have config::App configuration structure, but I also have main App class, would naming configuration structure config::AppConfig make more sense, even though config part repeats?

Thank you very much

1 Answer 1

5

In short

You are right, there is a problem in this design. But it's about dependency inversion and not single responsibility.

More details

SRP is about having a one and only one reason to change. Changes in the hidden implementation details of another module do not matter, as long as its public interface stays stable:

  • config::Foo only needs to change if the configuration information changes.
  • In the config namespace, serialize() and serialize() only need to change if Foo changes, so if configuration information chances, assuming they use JSON::Object's public interface and do not rely on its internals.
  • In the serialization::JSON namespace, serialize() and serialize() do not need to change due to change in config thanks to templates.

Therefore, there seem to be no issue in view of a single reason to change.

The real problem in your design is that you have a concrete implementation of serialization based on JSON::Object, and you build the rest on this fundament. This is why introducing a second way of serialization (e.g. XML in addition of JSON) causes a trouble.

This dependency to a concrete implementation is both undesirable and avoidable. You therefore should consider the dependency inversion principle:

  • Define an abstract interface for a serialization object/service.
  • Then rewrite your config to depend on this abstraction.
  • And then find some way to inject the serialization service you want when you want it.

This could be at runtime using a strategy pattern (which would include a factory to instantiate the serialization objects). In C++, this could be at compile-time, using the metaprogramming equivalent to the strategy patter, the policy based design. By the way, the boost:serialization could be an interesting source of inspiration if you're looking for an example of good serialization design.

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.