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

asked Jan 31, 2021 at 11:58

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.

lennon310
3,2427 gold badges18 silver badges35 bronze badges
answered Jan 31, 2021 at 16:58

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.