samedi 19 mars 2016

Designing a class hierarchy based on aggregation of individual properties/accessors

I'm designing a library which contains classes that provides access to database rows. The underlying data is held in a map where value is a union type. Consider the following base class:

class dbObject
{
    protected:
    // Get and set property values using generic name for property
    union_type GetPropertyValue(string property_name);
    void SetPropertyValue(string property_name, union_type value);

    // Add mapping from generic property name to column name
    void AddPropertyMapping(string property_name, string column_name);

    private:
    map<property_name, column_name>
    map<column_name, union_type>
};

Each class that inherits from dbObject will be required to

  • add the property mapping for the columns it needs to access
  • provide accessors for its properties

So that the eventual interface class that is presented to the library user has proper accessors and hides the underlying structure/implementation. The motivation here is that most of the classes share a large percentage of the same data, so we want them to inherit the accessors and mapping setup code rather than writing them separately for each class. Originally I was structuring the derived classes to each contain some set of shared accessors, ex:

class dbDerivedObjectOne : dbObject
{
    public:
    int GetPropertyOne();
    void SetPropertyOne(int value);
    string GetPropertyTwo();
    void SetPropertyTwo(string value);
};

class dbDerivedObjectTwo : dbDerivedObjectOne 
{
    public:
    double GetPropertyThree();
    void SetPropertyThree(double value);
    int GetPropertyFour();
    void SetPropertyFour(int value);
};

// This is the API class which is presented to the library user
// It has inherited all the necessary accessors and setup code
class dbInterfaceClass : dbDerivedObjectTwo
{
};

As you can see, this hierarchy is thus based on what accessors the leaf/interface class needs rather than a real "is-a" relationship you would expect to see in proper design. It's also caused problems whereby some class needs access to some subset of properties already exposed in a parent class, but doesn't want to expose all of the properties in the parent class because some of them are nonsensical/nonexistent for the derived class.

Without a known design pattern to reference, I'm currently leaning towards a refactor that involves breaking apart the derived classes into single property classes and using multiple inheritance to build the interface classes.

class dbObjectProperty
{
    union_type GetProperty(string property_name)
    void SetProperty(string property_name, union_type value)
};

class dbPropertyOne : dbObjectProperty
{
    // calls dbObjectProperty::GetProperty() with its property name
    // returns the int from the union_type
    int GetPropertyOne();

    // wraps the int in a union_type
    // calls dbObjectProperty::SetProperty() with its property name
    int SetPropertyOne(int value);
};

class dbPropertyTwo : dbObjectProperty
{
    // calls dbObjectProperty::GetProperty() with its property name
    // returns the string from the union_type
    string GetPropertyTwo();

    // wraps the string in a union_type
    // calls dbObjectProperty::SetProperty() with its property name
    void SetPropertyTwo(string value);
};

class InterfaceClassOne : dbPropertyOne, dbPropertyTwo
{
    // exposes all of the accessors from its parent classes
}

This solution is much more granular and allows me to build each interface class as a set of (potentially shared) properties which each provide their own accessor/mutator. However, the actual implementation would involve classes inheriting from 25-30 different property classes, which seems like a scary multiple inheritance design. Also, this seems like quite an abuse of the inheritance concept. The interface classes do not really fit the "is a" relationship with dbObjectProperty, rather they're actually a collection and we're abusing multiple inheritance to allow us to avoid duplicating accessor code.

Questions:

  1. Is there an established design pattern for this kind of structure I should be looking at?
  2. Is there another/better way to solve this problem while avoiding duplication of accessor code?
  3. Is the multiple inheritance solution I'm considering poor/dangerous design, or is it simply the nature of C++ that forces me to abuse the system to achieve something that would be easier in a less rigid language?

Aucun commentaire:

Enregistrer un commentaire