lundi 25 avril 2022

How to design an interface to call functions with a parameter of varying type polymorphically?

Say we want to be able to call functions run of ImplementationA and ImplementationB polymorphically (dynamically at runtime) in this example:

struct Input {};

struct MoreInputA {};
struct ImplementationA {
    void run(Input input, MoreInputA more_input);
};

struct MoreInputB {};
struct ImplementationB {
    void run(Input input, MoreInputB more_input);
};

Both take some Input in the same format, but some MoreInput in different formats. ImplementationA and ImplementationB can only do their job on their specific MoreInputA and MoreInputB respectively, i.e. those input formats are fixed. But say MoreInputA/B can be converted from a general MoreInput. Then a simple polymorphic version could look like this:

struct Input {};
struct MoreInput {};

struct ImplementationBase {
    virtual void run(Input input, MoreInput more_input) = 0;
};


struct MoreInputA {};
MoreInputA convertToA(MoreInput);

struct ImplementationA : public ImplementationBase {
    void run(Input input, MoreInputA more_input);
    void run(Input input, MoreInput more_input) override {
        run(input, convertToA(more_input));
    }
};

// same for B

However, now the more_input has to be converted in every call to run. A lot of unnecessary conversions are forced on a user of the polymorphic interface if they want to call run repeatedly with varying input but always the same more_input. To avoid this, one could store the converted MoreInput inside of the objects:

struct Input {};
struct MoreInput {};

struct ImplementationBase {
    virtual void setMoreInput(MoreInput) = 0;
    virtual void run(Input input) = 0;
};


struct MoreInputA {};
MoreInputA convertToA(MoreInput);

struct ImplementationA : public ImplementationBase {
    void run(Input input, MoreInputA more_input);

    MoreInputA more_input_a;
    void setMoreInput(MoreInput more_input) override {
        more_input_a = convertToA(more_input);
    }

    void run(Input input) override {
        run(input, more_input_a);
    }
};

// same for B

Now it is possible to do the conversion only when the user actually has new MoreInput. But on the other hand, the interface is arguably more difficult to use now. MoreInput is not a simple input parameter of the function anymore, but has become some sort of hidden state of the objects, which the user has to be aware of.

Is there a better solution that allows avoiding conversion when possible but also keeps the interface simple?

Aucun commentaire:

Enregistrer un commentaire