문제

I'm writing a DAL/ORM library. This library will be accessed mainly from GUIs but also from some "business level" applications. I'm still in the design phase of this library and came to a point where I'm not sure how to solve the following issue nicely.

In my current design I have a class, let's call it List for the moment, that has a container of another class, Properties. Properties come in two flavors (A and B), with mostly the same functionality, but some of their functionality is different. Furthermore both flavors of Properties store values. Values can be of different data types, including, but not limited to, PODs. Each List can contain a certain Property only once and Properties are identified by a "name", i.e. a string.

I now want to be able to do all of the following:

  • Keep the complexity of the interface as low as possible
  • Iterate over all Properties in List, and call methods that both Property flavors support.
  • When having an instance of a Property, accessing its value in a type safe way
  • If possible, avoid dynamic_cast or similar constructs

So, obviously pure polymorphism cannot do the trick here. I have done some experiments with the curiously recurring template pattern and with composition of two class hierarchies - one for Properties and one for their values (example code below). However, so far I did not succeed in getting a design that fulfilled all my requirements.

The basic design (i.e. which classes exist, how they are organized etc.) is not fixed and could be easily changed. I am still in the design phase of this project, so only test code exists. However, the basic idea has to bee like explained above (i.e. that a List has Properties which in turn have values).

Any solutions to my problems or raw ideas, thoughts, etc. are highly appreciated.


Example code for hierarchy implementation. Obviously I will not be able to access a property's value in a type-safe way here.

class PropertyValue {
public:
    virtual std::string GetAsString() const = 0;

    bool IsReadOnly() const { return m_isReadOnly; }
    void IsReadOnly(const bool val) { m_isReadOnly = val; }

protected:
    PropertyValue(PropertyValue & other) : m_isReadOnly(other.m_isReadOnly)
    {};

    PropertyValue(bool readOnly) : m_isReadOnly(readOnly)
    {};

private:
    bool m_isReadOnly;
};

class StringValue : public PropertyValue {
private:
    typedef std::string inner_type;
public:
    StringValue(const inner_type & value, bool readOnly) : PropertyValue(readOnly)
                                                         , m_value(value)
    {};

    StringValue(StringValue & other) : PropertyValue(other.IsReadOnly())
                                     , m_value(other.m_value)
    {};

    std::string GetAsString() const { return m_value; };

    inner_type GetValue() const { return m_value; };
    void SetValue(const inner_type & value) { m_value = value; };

    unsigned int GetMaxLenght() const { return m_maxLength; };

private:
    inner_type m_value;
    unsigned int m_maxLength;
};

class IntValue : public PropertyValue {
private:
    typedef int inner_type;
public:
    IntValue(const inner_type & value, bool readOnly) : PropertyValue(readOnly)
                                                      , m_value(value)
    {};

    IntValue(IntValue & other) : PropertyValue(other.IsReadOnly())
                               , m_value(other.m_value)
    {};

    std::string GetAsString() const { char tmp[((CHAR_BIT * sizeof(int)) / 3 + 1)]; return itoa(m_value, tmp, 10); };

    inner_type GetValue() const { return m_value; };
    void SetValue(const inner_type & value) { m_value = value; };

    int GetMinValue() const { return m_minValue; };
    int GetMaxValue() const { return m_maxValue; };

private:
    inner_type m_value;
    int m_minValue;
    int m_maxValue;
};

class Property {
public:
    Property(std::auto_ptr<PropertyValue> value, bool visible)
    {
        m_value = value;
        m_isVisible = visible;
    }

    bool IsVisible() const { return m_isVisible; }
    void IsVisible(const bool val) { m_isVisible = val; }

    std::string GetValueAsString() const { return m_value->GetAsString(); };

    const PropertyValue & getValue() const { return (*m_value.get()); }

private:
    std::auto_ptr<PropertyValue> m_value;
    bool m_isVisible;
};

class PropertyFlavorA : public Property {
public:
    PropertyFlavorA(std::auto_ptr<PropertyValue> value, bool visible) : Property(value, visible)
    {
        value->IsReadOnly(true);
    };
};

class PropertyFlavorB : public Property {
public:
    PropertyFlavorB(std::auto_ptr<PropertyValue> value, bool visible) : Property(value, visible) {};
};
도움이 되었습니까?

해결책 2

I finally decided to use a vector of boost::variant in my List class. My Property class is now a template class where the template parameter is instantiated with subclasses of a ValueType class. The different flavors of Properties derive from the Property class.

Right now it seems that this approach fulfills all my initial requirements:

  • It keeps the complexity of the interface low ✓
  • Iterating over all Properties in List, and calling methods that both Property flavors support is possible by using std::for_each and boost::apply_visitor
  • When having an instance of a Property, accessing its value in a type safe way is possible if my Property base class gives access to its ValueType member. ✓
  • Use of dynamic_cast or similar constructs is not needed ✓

Any comments on this approach are still appreciated.

다른 팁

You seem to have answered your own question...

Iterate over all Properties in List, and call methods that both Property flavors support.

This is polymorphism.

Both your flavours of Properties have a base class which has a virtual function(s), which your A and B Property classes override. You can then call the function from a base class pointer stored in a List.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top