0

I have an hierarchy of classes that looks like the following:


    class Critical
    {
    public:
        Critical(int a, int b) : m_a(a), m_b(b) { }
        virtual ~Critical() { }
        int GetA() { return m_a; }
        int GetB() { return m_b; }
        void SetA(int a) { m_a = a; }
        void SetB(int b) { m_b = b; }
    protected:
        int m_a;
        int m_b;
    };

    class CriticalFlavor : public Critical
    {
    public:
        CriticalFlavor(int a, int b, int flavor) : Critical(a, b), m_flavor(flavor) { }
        virtual ~CriticalFlavor() { }
        int GetFlavor() { return m_flavor; }
        void SetFlavor(int flavor) { m_flavor = flavor; }
    protected:
        int m_flavor;
    };

    class CriticalTwist : public Critical
    {
    public:
        CriticalTwist(int a, int b, int twist) : Critical(a, b), m_twist(twist) { }
        virtual ~CriticalTwist() { }
        int GetTwist() { return m_twist; }
        void SetTwist(int twist) { m_twist = twist; }
    protected:
        int m_twist;
    };

The above does not seem right to me in terms of the design and what bothers me the most is the fact that the addition of member variables seems to drive the interface of these classes (the real code that does the above is a little more complex but still embracing the same pattern). That will proliferate when in need for another "Critical" class that just adds some other property. Does this feel right to you? How could I refactor such code? An idea would be to have just a set of interfaces and use composition when it comes to the base object like the following:


    class Critical
    {
    public:
        virtual int GetA() = 0;
        virtual int GetB() = 0;
        virtual void SetA(int a) = 0;
        virtual void SetB(int b) = 0;
    };

    class CriticalImpl : public Critical
    {
    public:
        CriticalImpl(int a, int b) : m_a(a), m_b(b) { }
        ~CriticalImpl() { }
        int GetA() { return m_a; }
        int GetB() { return m_b; }
        void SetA(int a) { m_a = a; }
        void SetB(int b) { m_b = b; }
    private:
        int m_a;
        int m_b;
    };

    class CriticalFlavor
    {
    public:
        virtual int GetFlavor() = 0;
        virtual void SetFlavor(int flavor) = 0;
    };

    class CriticalFlavorImpl : public Critical, public CriticalFlavor
    {
    public:
        CriticalFlavorImpl(int a, int b, int flavor) : m_flavor(flavor), m_critical(new CriticalImpl(a, b)) { }
        ~CriticalFlavorImpl() { delete m_critical; }
        int GetFlavor() { return m_flavor; }
        void SetFlavor(int flavor) { m_flavor = flavor; }
 int GetA() { return m_critical->GetA(); }
        int GetB() { return m_critical->GetB(); }
        void SetA(int a) { m_critical->SetA(a); }
        void SetB(int b) { m_critical->SetB(b); }
    private:
        int m_flavor;
 CriticalImpl* m_critical;
    };

celavek
  • 5,575
  • 6
  • 41
  • 69
  • 1
    I would be very dubious of any design which required the use of many getters and setters. –  Mar 28 '10 at 09:44
  • @Neil: I am not sure I understand your reasons for that statement. @Marius: This decision depends on what you want to achieve. Can provide some information about that? – Björn Pollex Mar 28 '10 at 09:51
  • 1
    I agree with @Neil. A class of nothing but getters and setters is just a data bucket, and on purely pragmatic grounds is probably better off as a `struct`. In the absence of any virtual member functions, I'm not sure even inheritance is warranted. – Marcelo Cantos Mar 28 '10 at 10:04
  • I agree that if there are no other functions this is just a data structure (the term "bucket" has bad connotations for me) but remember that using struct instead of class is mainly a matter of style, not function; strictly speaking the only difference is default private vs default public. – Beta Mar 28 '10 at 10:16
  • @Neil: this is not about getters and setters (maybe I chose my example poorly). The real classes behind this example have some other functionality. I just don't feel comfortable with the initial design and I wanted to know if refactoring like I tried is a better choice. The subclasses will add other data members (not all are POD). Let me know how can I modify my example to make it more clear. @Space_C0wb0y: I don't like the fact that the base classes have protected data members and I want to achieve a cleaner and more extensible design if that's possible. – celavek Mar 28 '10 at 10:17
  • I really don't understand the improvement of your second code proposal over the first one. It already has more code, is more difficult to comprehend what is does (for me) and even doesn't include the CriticalTwist class which would make the code even longer. Why is the second code more open for extensions? Can you give an example of an extension where the first design would fail (or become too complicated and "unclean") and the second would work? – Slauma Mar 28 '10 at 11:13
  • I fail to see what breaks loose when you add another implementation of Critial in example 1 – Eric Mar 28 '10 at 11:16
  • @Slauma: that's what I'm trying to find out myself :). The real code got rejected as part of a review process and I got a set of recommendations: base classes should be abstract, protected members are not advisable in base classes, design by interface. I re-read some Myeres and Sutter articles and found also some contradictory opinions to those recommendations http://www.parashift.com/c++-faq-lite/basics-of-inheritance.html#faq-19.8 – celavek Mar 28 '10 at 12:04
  • 1
    @Marius: Here (http://stackoverflow.com/questions/56867/interface-vs-base-class) is a list of arguments about using interfaces over abstract base classes, but also the opposite! The fact that there are arguments in favor and against a certain pattern (in the end: "It depends...") should the reviewers force to give you some reasons for their recommendations, especially for your specific domain or class model. (If those recommendations are kind of a general best practice, probably every class model I've ever written would be rejected. Fortunately noone reviews my code ;)) – Slauma Mar 28 '10 at 12:40

4 Answers4

1

My suggestion: find the most patient person you work with who is familiar with this code, and ask them some of these questions. I assume you are not posting a more complete example because of IP concerns. That makes it hard to provide good advice.

Based on your first code sample, I would say just use structs with public data and no accessors. But if I saw the real code, I would probably change my tune.

For your second code sample: A benefit is that you could have another class depend on CriticalFlavor without knowing anything about Critical (could be used to implement something like the Bridge pattern, for example). But if that potential benefit isn't an actual benefit in your situation, then it's just making your code unnecessarily complicated and YAGNI (probably).


The comments from your reviewers:

base classes should be abstract

I would say, "base classes usually should have at least one virtual method, other than the destructor". If not, then it's just a means to share common code or data amongst other classes; try to use composition instead.

Most of the time, at least one of those virtual methods will be pure virtual, so the base class will be abstract. But sometimes there is a good default implementation for every virtual method and subclasses will pick and choose which to override. In that case, make the base class constructors protected to prevent instantiation of the base class.

protected members are not advisable in base classes

... and they're completely pointless in non-base classes, so this advice basically says to never use them.

When are protected member variables advisable? Infrequently. Your code example isn't realistic enough to determine what you're trying to do or how it could be best written. The members are protected, but there are public getters/setters, so they're essentially public.

design by interface

Don't get carried away by this, or you may end up with an amazingly complicated design for an amazingly simple task. Use interfaces where they make sense. It's hard to tell if they make sense without seeing some of the "calling code" that uses Critical and its subclasses. Who calls GetFlavor and GetTwist?

Does the calling code only interact with Critical subclasses via the Critical interface, or does the calling code know the specific subclass and call specific subclass methods? Have you added interface methods to Critical in order to provide access to data/functionality only present in some of the subclasses? That can be a bad smell.


Your comment:

the addition of member variables seems to drive the interface of these classes

makes me think of an item in C++ Coding Standards (Sutter/Alexandrescu): "Be clear what kind of class you're writing". Sorry, no online reference to that item (buy the book).


Let me also suggest that you honestly evaluate your skill level and that of your reviewers. Are your reviewers experienced C++ developers who really know what they are talking about (if so, listen!), or have they just returned from Code Review 101 and know to say things like "public data is bad" and "destructors should always be virtual"? If the latter, hopefully you can respond to the review comments by saying, "That's generally good advice, but does not apply in this situation because XYZ."

Dan
  • 5,929
  • 6
  • 42
  • 52
0

It's hard to say whether inheritance is the right idea here without knowing more about your real code. Do you want polymorphism (acting on a Critical and getting different effects if it's a CriticalFlavor, CriticalTwist or CriticalWhatever)? Does the order of construction of A's and B's and things matter? Do you plan to have a more complicated class tree, with things like CriticalFlavorTemperature and CriticalTwistColor?

That said, here's what I'd probably do for a composition approach:

class Critical
{
public:
    Critical(int a, int b) : m_a(a), m_b(b) { }
    ~Critical() { }
    int GetA() const { return m_a; }
    int GetB() const { return m_b; }
    void SetA(int a) { m_a = a; }
    void SetB(int b) { m_b = b; }
private:
    int m_a;
    int m_b;
};

class CriticalFlavor
{
public:
    CriticalFlavor(int a, int b, int flavor) : m_flavor(flavor), m_critical(a, b) { }
    ~CriticalFlavor() {}
    int GetFlavor() const { return m_flavor; }
    void SetFlavor(int flavor) { m_flavor = flavor; }
    int GetA() const { return m_critical.GetA(); }
    int GetB() const { return m_critical.GetB(); }
    void SetA(int a) { m_critical.SetA(a); }
    void SetB(int b) { m_critical.GetB(b); }
private:
    int m_flavor;
    Critical m_critical;
};
Beta
  • 96,650
  • 16
  • 149
  • 150
  • That wouldn't allow me to pass a CriticalFlavor pointer to a method taking a Critical pointer as argument. I need polymorphic behavior with those objects. – celavek Mar 28 '10 at 10:33
  • 1
    All right, so you do want polymorphic behavior. What about the class tree? Is every new class derived directly from Critical, or will you combine properties, as in CriticalFlavorTwist? If the former, then it's hard to improve on your first example --I don't see what's wrong with protected members in a base class, and the "proliferation" of the interface is just a few lines per property, O(n). If the latter, then proliferation will be a problem, O(2^n), and I reluctantly suggest multiple inheritance to bring it back to O(n). – Beta Mar 29 '10 at 03:25
  • For the time being is just the former ... but I should be aware of the latter (even if it's quite improbable). – celavek Mar 29 '10 at 07:33
0

@Marius all I can understand from your post is that you want to separate access to data from actual data structure.

class Critical
{
public:
    virtual int GetA() = 0;
    virtual int GetB() = 0;
    virtual void SetA(int a) = 0;
    virtual void SetB(int b) = 0;
};

class Flavor
{
public:
    virtual int GetFlavor() = 0;
    virtual void SetFlavor(int a) = 0;
};

template<class T>
class AccessCritical : public Critical
{
public:
    AccessCritical(T &ref) : data(ref) {}

    int GetA() { return (data.m_a); }
    int GetB() { return (data.m_b); }
    void SetA(int a) { data.m_a = a; }
    void SetB(int b) { data.m_b = b; }
private:
    T &data;
};

template<class T>
class AccessFlavor : public Flavor
{
public:
    AccessFlavor(T &ref) : data(ref) {}
    int GetFlavor() { return (data.m_flavor); }
    void SetFlavor(int flavor) { data.m_flavor = flavor; }
private:
    T &data;
};

struct MyStructA {
    int m_a, m_b;

    AccessCritical<MyStructA> critical;
    MyStructA() : critical(*this) {}
};

struct MyStructB {
    int m_flavor;

    AccessFlavor<MyStructB> flavor;
    MyStructB() : flavor(*this) {}
};

struct MyStructC {
    int m_a, m_b;
    int m_flavor;

    AccessCritical<MyStructC> critical;
    AccessFlavor<MyStructC> flavor;
    MyStructC() : critical(*this), flavor(*this) {}
};
void processFlavor(Flavor *flavor)
{
    flavor->SetFlavor(flavor->GetFlavor() * 3);
}
void processCritical(Critical *critical)
{
    critical->SetA(critical->GetA() + critical->GetB());
}
int main() {
    MyStructA a;
    a.critical.SetA(2);
    a.critical.SetB(3);
    processCritical(& a.critical);

    MyStructB b;
    b.flavor.SetFlavor(4);
    processFlavor(& b.flavor);

    MyStructC c;
    c.critical.SetA(20);
    c.critical.SetB(30);
    c.flavor.SetFlavor(40);
    processFlavor(& c.flavor);
    processCritical(& c.critical);

    cerr << a.critical.GetA() << ' ' << a.critical.GetB() << endl;
    cerr << b.flavor.GetFlavor() << endl;
    cerr << c.critical.GetA() << ' ' << c.critical.GetB() << endl;
    cerr << c.flavor.GetFlavor() << endl;

    return 0;
}
ony
  • 12,457
  • 1
  • 33
  • 41
0

It seems to me that this hierachy of classes are no more than data buckets, that they don't actually do anything other than to act as an accessor to some underlying data model. Maybe just restrict your design to a single class with your critical accessor, and another more generic named property method (ie. std::string GetStringProperty( std::string key ){} )?

MauriceL
  • 445
  • 5
  • 13