c++ class design, base class inheritance, or facad

2019-06-24 18:33发布

I have a dumb c++ design question. Is there a way for one class to have the same method names (hence, the same API) of the methods found in several classes?

My current situation is that I have a situation where I have classes

struct A
{
    void foo() { std::cout << "A::foo" << std::endl;}
    void boo() { std::cout << "A::boo" << std::endl;}
};

struct B
{
    void moo() { std::cout << "B::moo" << std::endl;}
    void goo() { std::cout << "A::goo" << std::endl;}
};
.... imagine possibly more

What I really want is another class that acts an interface for those of these functionalities. I might be misinterpreting as the facade design pattern for a simple interface that hides the complexity of instantiating classes above but still use their same interface.

struct C 
{
    void foo() { ... }
    void boo() { ... }
    void moo() { ... }
    void goo() { ... }
};

For small number of methods shown above this is feasible by either declaring structs A and B or passing them in as parameters to struct C and call the methods of A and B in C but this is impracticable if A has 40 methods and B has 30 has methods. Redeclaring 70 methods with the same name in C to call the underlying methods of A and B seemed like a lot of redundancy for no reason if I could do better.

I thought of a second solutions of using a base class

struct base
{
    void foo() { }
    void boo() { }

    void moo() { }
    void goo() { }
};

struct A : public base
{
    void foo() { std::cout << "A::foo" << std::endl;}
    void boo() { std::cout << "A::boo" << std::endl;}
};

struct B : public base
{
    void moo() { std::cout << "B::moo" << std::endl;}
    void goo() { std::cout << "A::goo" << std::endl;}
};

To try and use a shared_ptr that has all the function definitions. e.g

std::shared_ptr<base> l_var;
l_var->foo();
l_var->boo();
l_var->moo();
l_var->goo();

That still doesn't quite give me what I want because half of the methods are defined in struct A while the other half is in struct B.

I was wondering if multiple inheritance would do the trick but in school I heard it's bad practice to do multiple inheritance (debugging is hard?)

Any thoughts or recommendations? Basically it's easier to manage struct A and B (and so on as it's own class for abstraction purposes). But would like the flexibility of somehow calling their methods in some wrapper where this complexity is hidden from the user.

4条回答
SAY GOODBYE
2楼-- · 2019-06-24 19:15

Use virtual multiple inheritance. The reason why

it's bad practice to do multiple inheritance

is because it directly will lead to ambiguous calls or redundant data, so you can use virtual inheritance to avoid it.

Learn how C++ implement iostream will help a lot, I thought.

查看更多
贼婆χ
3楼-- · 2019-06-24 19:22

I second Chris Drew's answer: not only multiple iharitance is bad, any inharitance is bad, compared to composition.

The purpose of the Fascade pattern is to hide complexity. As in, given your classes A and B with 40 and 30 methods, a Fascade would expose about 10 of them - those, needed by the user. Thus is avoided the problem of "if A has 40 methods and 30 has methods" then you have a big problem – n.m.

By the way, I love how you are using struct{} instead of class{public:}. This is quite controversial and the general consensus is it constitutes bad form, but stl does it and I do it.

Back to the question. If really all the 70 methods need to be exposed (!!), I would take a more pythonistic approach:

struct Iface
{
    A _a;
    B _b;
};

If you manage to make the fields const, things get even less bad. And for the third time - you are probably violating SRP with those large classes.

查看更多
甜甜的少女心
4楼-- · 2019-06-24 19:23

I think that

Redeclaring 70 methods with the same name in C to call the underlying methods of A and B

is the right path.

It is tempting to use multiple inheritance in cases like this to avoid writing pass-through code but I think that is generally a mistake. Prefer composition over inheritance.

I would question whether your user really wants to deal with one interface with 70 methods but if that's really what you want then I don't see why it is "impractical" to write the code in C:

class C {
    A a;
    B b;
public:
    void foo() { return a.foo(); }
    void boo() { return a.boo(); }
    void moo() { return b.moo(); }
    void goo() { return b.goo(); }
    // ...
};

Live demo.

This has the advantage that you can easily change your mind in the future and replace A and B with something else without changing the interface of C.

You can hide the implementation of C further by using the PIMPL idiom or by splitting C into an abstract base class C and an implementation CImpl.

查看更多
你好瞎i
5楼-- · 2019-06-24 19:36

A Bridge Design Pattern will shine here. By decoupling abstraction from its implementation , many derived classes can used these implementations separately.

    struct base {    
    protected:
        struct impl;
        unique_ptr<impl> _impl;
    };

    struct base::impl {
        void foo() {}
        void bar() {}
    };    

    struct A :public base {
        void foo() { _impl->foo(); }
    };

    struct B:public base {
        void foo() { _impl->foo(); }
        void bar() { _impl->bar(); }
    };

Edited ( eg implementation)

#include <memory>
#include <iostream>


using namespace std;

struct base {
    base();
protected:
    struct impl;
    unique_ptr<impl> _impl;
};

struct base::impl {
    void foo() { cout << " foo\n"; }
    void bar() { cout << " bar\n"; }
    void moo() { cout << " moo\n"; }
    void goo() { cout << " goo\n"; }
};

base::base():_impl(new impl()) {}

struct A :public base {
    A():base() { }
    void foo() { _impl->foo(); }
};

struct B:public base {
    B() :base() { }
    void foo() { _impl->foo(); }
    void bar() { _impl->bar(); }
};


struct C :public base {
    C() :base() { }
    void foo() { _impl->foo(); }
    void bar() { _impl->bar(); }
    void moo() { _impl->moo(); }
    void goo() { _impl->goo(); }
};


int main()
{
    B b;
    b.foo();
    C c1;
    c1.foo();
    c1.bar();   
    c1.moo();
    c1.goo();
    return 0;
}
查看更多
登录 后发表回答