mercredi 18 avril 2018

How to pass information through a "pipeline" of immutable types

I have some code that deals with receiving messages, parsing them, and handling them. I'm interested in making the types immutable to make it easier to reason about them. Here's a stripped down example:

class ReceivedMessage {
public:
  ReceivedMessage(const string& message)
    : m_message(message)
  { }
  const string& GetReceivedMessage() const { return m_message; }
  chrono::steady_clock::time_point GetReceivedAt() const { return m_receivedAt; }

private:
  const string m_message;
  const chrono::steady_clock::time_point m_receivedAt = chrono::steady_clock::now();
}

class ParsedMessage {
public:
  ParsedMessage(const ReceivedMessage& rm)
    : m_json(ParseMessage(rm.GetReceivedMessage()))
  { }
  const string& GetJson() const { return m_json; }

private:
  const json_t m_json;
}

class HandledMessage {
public:
  HandledMessage(const ParsedMessage& pm)
    : m_result(HandleMessage(pm))
  { }
  const string& GetResult() const { return m_result; }

private:
  const string m_result;
}

... and so on. This works fine, but at the end of the "pipeline", a response needs to be sent. To form the response, attributes from the first type (e.g., m_receivedAt) are needed.

I'm not sure of the best/right way to pass those early attributes through to the final type. Here are my ideas:

  • Inheritance seems simple, but my peers say I should favor composition over inheritance.
  • Each type could hold a copy of the preceding message type (i.e., ParsedMessage would hold a ReceivedMessage) and delegate calls for information from the earlier types to it, so ParsedMessage would have an additional method like chrono::steady_clock::time_point GetReceivedAt() const { return m_parentMessage.GetReceivedAt(); }. This feels verbose, and there's a lot of repetition if information needs to be passed from the first type to the last.
  • Each message type could provide access to each of its parent message types, so to get m_receivedAt from a HandledMessage, it might look like handledMessage.GetReceivedMessage().GetReceivedAt();. This puts the burden of knowing which message type contains a particular attribute on the caller, though. I'm not sure that's something they need to deal with.

Aucun commentaire:

Enregistrer un commentaire