简体   繁体   中英

How to manage different types of data in the base class?

My goal is to separate data from various implementations. I don't want my things to know what actual subclass it is they are working with, either way around. To make things perform only a single task with minimal information.

I'll throw some code in your eyes first.

// Example program
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <functional>

class Model
{
    public:
    
    virtual bool set(int p_attrId, int p_value) {return false;}; 
    virtual bool get(int p_attrId, int & p_value) const {return false;};

};

class Derived: public Model
{
    static constexpr int c_classId = 1;
    int value = 1;
    public:
    
    enum EAttrs
    {
        eAttr1 = c_classId * 1000
    };
    
    virtual bool set(int p_attrId, int p_value) override
    {
        switch(p_attrId)
        {
            case eAttr1:
                value = p_value;
                return true;
            
            default:
                return Model::set(p_attrId, p_value);
        }
    }

    virtual bool get(int p_attrId, int & p_value) const override
    {
        switch(p_attrId)
        {
            case eAttr1:
                p_value = value;
                return true;
            
            default:
                return Model::get(p_attrId, p_value);
        }
    }
};

// GuiTextBoxComponent.h

// no includes to any class derived from model

class GuiTextBoxComponent
{
        std::weak_ptr<Model> m_model;
        int m_attrId;
    public:
        void draw()
        {
            auto locked = m_model.lock();
            if(locked)
            {
                int value;
                bool result = locked->get(m_attrId, value);
                if(!result)
                {
                    std::cout << "Failed to get attribute " << m_attrId << "\n";
                    return;
                }
            
                std::cout << "AttrID: " << m_attrId << " Value: " << value << "\n";
            }
            else
            {
                std::cout << "Model is dead\n";
            }
        }
        
        void setSource(std::weak_ptr<Model> p_model, int p_attrId)
        {
            m_model = p_model;
            m_attrId = p_attrId;
        }
};


int main()
{
    std::shared_ptr<Model> model (new Derived);
    GuiTextBoxComponent textbox;
    textbox.setSource(model, Derived::eAttr1);
    textbox.draw();
}

The motivation behind this is acquisition of all data from a single interface. I need to be able to add functionality like the GuiTextBoxComponent, without #include "Derived1.h" in its header.

The challenge with this design is that the Model interface needs to implement all types required from anywhere in the program.

How would you extend the types provided?

Is there some other design that could be used to achieve similar results?

Generally, I think this is an XY problem but here is how you can beautify your code a bit. First, I implemented two interfaces: Getter and Setter like:

enum class EAttrs {
    eAttr1
};

template <typename GetterImpl>
struct Getter {
    bool get(EAttrs const attrId, int& value) {
        switch (attrId) {
        case EAttrs::eAttr1:
            return static_cast<GetterImpl*>(this)->get(value);
        default:
            return false;
        }
    }
};

template <typename SetterImpl>
struct Setter {
    bool set(EAttrs const attrId, int value) {
        switch (attrId) {
        case EAttrs::eAttr1:
            return static_cast<SetterImpl*>(this)->set(value);
        default:
            return false;
        }
    }
};

Here I used CRTP, ie static polymorphism. Then implementation of your derived classes is a bit simpler:

class Derived1 : public Getter<Derived1>, Setter<Derived1> {
    int value = 1;
public:    
    bool set(int p_value) {
        value = p_value;
        return true;
    }

    bool get(int & p_value) {
        p_value = value;
        return true;
    }
};

class Derived2 : public Getter<Derived1>, Setter<Derived1> {
    int value = 2;
public:
    bool set(int p_value) {
        value = p_value;
        return true;
    }

    bool get(int & p_value) {
        p_value = value;
        return true;
    }
};

Finally, since we were using CRTP, there is no need for creating std::unique_ptr . Code that's using above classes could look like:

template <typename T>
void printInt(Getter<T>& model, EAttrs p_attrId) {
    int value;
    bool result = model.get(p_attrId, value);
    if (!result)
    {
        std::cout << "Failed to get attribute " << static_cast<int>(p_attrId) << "\n";
        return;
    }
    
    std::cout << "AttrID: " << static_cast<int>(p_attrId) << " Value: " << value << "\n";
}

int main()
{
    Derived1 derived1;
    Derived2 derived2;
    
    printInt(derived1, EAttrs::eAttr1);
    printInt(derived2, EAttrs::eAttr1);
}

Check out the DEMO .

PS Note the usage of enum class instead of plain enum .

Take a look at this CppCon's talk about Solid principles. Your code might be a good example to apply those principles to.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM