1

I'm creating an interface to record navigation history.

First thing is, we have three types of records: Recordable (the default behaviour), Revisitable (a user can look at their history and revisit that item), and Traversible (the user can simply "go back"). Traversible records are, by definition, Revisitable which are, in turn, Recordable. These types are encoded in an enum.

Each record contains the information needed to restore the view to a particular state. Our design is modular and action based - some actions take parameters. So, the history needs to record the module and action at a minimum - and sometimes the action parameters, and sometimes the recordType (if not the default), and sometimes both.

This leads me to:

void recordAction(const QString &moduleName, const QString &actionName);
void recordAction(const QString &moduleName, const QString &actionName, QVariantMap parameters);
void recordAction(const QString &moduleName, const QString &actionName, QVariantMap parameters, int recordType);
void recordAction(const QString &moduleName, const QString &actionName, int recordType);

Is this a good pattern? Or should I implement something more like:

void recordAction(const QString &moduleName, const QString &actionName, QVariantMap parameters = QVariantMap());
void recordRevisitableAction(const QString &moduleName, const QString &actionName, QVariantMap parameters = QVariantMap());
void recordTraversibleAction(const QString &moduleName, const QString &actionName, QVariantMap parameters = QVariantMap());

I guess, ultimately, these methods will simply call an internal:

void recordAction(const QString &moduleName, const QString &actionName, QVariantMap parameters, int recordType);

So, which is the better pattern to follow? While I don't see a need yet, there may be another type of record I haven't thought of...

2 Answers 2

7

Always start with readability. From the perspective of someone whose detailed understanding of your API is minimal, what does your API say?

In the overloading case, the user sees the same function name, with different parameters. Unless the parameters give the user reason to expect otherwise, the user is therefore invited to assume that the functions all do more or less the same thing.

In the non-overloaded case, the user sees 3 different function names. The functions have similar names so the user would expect similar yet different behavior.

So the only question is this: are these the same function with different parameters, or functions that do similar yet different things? Is recording a revisitable or traversible action logically distinct from recording a recordable action?

Only you can determine how distinct these concepts are in your system.

1
  • Actually, I tend to pick one to start with. I get it working and refactor it to read better and be to have clearer logic. May 5, 2017 at 4:35
3

I am speaking from a point view of ignorance about your domain. Take what I have to say with a grain of salt.

The overloads with the currently posted parameter names don't give a clue as to what type of action a particular overload is suitable for. Hence, the names recordRevisitableAction and recordTraversibleAction make more sense to me.

However, another option is to use tag dispatch mechanism.

struct GenericAction {};
struct RevisitableAction {};
struct TraversibleAction {};

void recordAction(const QString &moduleName,
                  const QString &actionName,
                  GenericAction tag,
                  QVariantMap parameters = QVariantMap());

void recordAction(const QString &moduleName,
                  const QString &actionName,
                  RevisitableAction tag,
                  QVariantMap parameters = QVariantMap());

void recordAction(const QString &moduleName,
                  const QString &actionName,
                  TraversibleAction tag,
                  QVariantMap parameters = QVariantMap());

This has couple of advantages:

  1. These functions are ready to be used from function templates and/or class templates if any of the tag structs can be used as template parameters.
  2. The type of Action that a particular overload can be used for is very clear from the declarations.

If you already have classes that conflict with any of the tag structs, you can use a helper class template.

template <typename T> tag {};
using GenericActionTag = tag<GenericAction>;
using RevisitableActionTag = tag<RevisitableAction>;
using TraversiblebleActionTag = tag<TraversibleAction>;

and then use GenericActionTag, etc. to overload the functions.

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.