jeudi 30 mars 2023

Design Pattern for I/O and Data Transformations in High-Level Block Diagrams

I am working on a application that is designed as a high-level block diagram, similar to Simulink or LabView. Unlike a typical Simulink / LabView program, it does a lot of I/O and data transformations. As this situation is not completely uncommon, I suspect that various solutions may already exist.

In order to find the most suitable approach, I am seeking suggestions for a design pattern in C++. Would anyone be willing to share their thoughts and experiences, perhaps with a code example?

The following is my own draft code. Both input and output is represented as an anyobject. A block may have an unlimited number of processor functions for different types. They are indexed in a map object, which connects the actual input type to the right processor function.

Looks like it does its job, but can it be made simpler, more elegant, and easier to read? I am particularly unhappy with all the casts that I had to make.


class Block {
protected:
    map<type_index, any (Block::*)(any)> processors;

public:
    virtual any process(any in) {
        auto it = processors.find(type_index(in.type()));
        return (this->*it->second)(in);
    }
};

class MultiplyBlock : public Block {
    any multiply(any in_) {
        auto in = std::any_cast<vector<float>>(in_);
        float product{1};
        for (auto f : in) {
            product *= f;
        }
        return product;
    }

public:
    MultiplyBlock() {
        processors.emplace(
                typeid(vector<float>),
                static_cast<any (Block::*)(any)>(&MultiplyBlock::multiply)
        );
    }
};

int main() {
    MultiplyBlock m;
    cout << any_cast<float>(m.process(vector<float>{1, 2, 3})) << endl;
}

Aucun commentaire:

Enregistrer un commentaire