дизайн класса c ++, наследование базового класса или шаблон проектирования фасада - PullRequest
0 голосов
/ 13 мая 2018

У меня тупой вопрос о дизайне c ++. Есть ли способ для одного класса иметь одинаковые имена методов (следовательно, один и тот же API) методов, найденных в нескольких классах?

Моя текущая ситуация такова, что у меня есть ситуация, когда у меня есть классы

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

Что мне действительно нужно, так это еще один класс, который действует как интерфейс для этих функций. Я мог бы неправильно истолковать как шаблон проектирования фасада для простого интерфейса, который скрывает сложность создания экземпляров классов выше, но все еще использует их тот же интерфейс.

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

Для небольшого числа методов, показанных выше, это возможно либо объявить структуры A и B, либо передать их в качестве параметров для структурирования C и вызвать методы A и B в C, но это неосуществимо, если A имеет 40 методов и B имеет 30 методов. Перераспределение 70 методов с одинаковыми именами в C для вызова базовых методов A и B выглядело избыточным без всякой причины, если бы я мог добиться большего.

Я подумал о втором решении использования базового класса

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;}
};

Чтобы попытаться использовать shared_ptr, который имеет все определения функций. * например 1016 *

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

Это все еще не совсем дает мне то, что я хочу, потому что половина методов определена в структуре A, а другая половина в структуре B.

Мне было интересно, если бы множественное наследование сработало бы, но в школе я слышал, что делать множественное наследование - плохая практика (отладка - это сложно?)

Есть мысли или рекомендации? По сути, проще управлять структурами A и B (и так далее, поскольку это собственный класс для целей абстракции). Но хотелось бы гибкости как-то вызывать их методы в какой-то обертке, где эта сложность скрыта от пользователя.

Ответы [ 4 ]

0 голосов
/ 13 мая 2018

Я второй ответ Криса Дрю: не только многократное наследование - это плохо, но и любое наследование - плохо, по сравнению с композицией.

Цель паттерна Фаскада - скрыть сложность.Например, учитывая ваши классы A и B с 40 и 30 методами, Fascade выставит около 10 из них - те, которые нужны пользователю.Таким образом, избегается проблема "if A has 40 methods and 30 has methods" then you have a big problem – n.m.

Кстати, мне нравится, как вы используете struct{} вместо class{public:}.Это довольно спорное и общий консенсус она представляет собой плохой вид , но stl делает это и я это делаю.

1012 * Назад к вопросу.Если на самом деле нужно раскрыть все 70 методов (!!), я бы выбрал более питонистский подход:
struct Iface
{
    A _a;
    B _b;
};

Если вам удастся создать поля const, все станет еще хуже.И в третий раз - вы, вероятно, нарушаете SRP с этими большими классами.

0 голосов
/ 13 мая 2018

A Bridge Design Pattern будет светить здесь.Отделив абстракцию от ее реализации, многие производные классы могут использовать эти реализации отдельно.

    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(); }
    };

Отредактировано (например, реализация)

#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;
}
0 голосов
/ 13 мая 2018

Я думаю, что

Переопределение 70 методов с тем же именем в C для вызова базового методы А и В

- это правильный путь.

В таких случаях заманчиво использовать множественное наследование, чтобы избежать написания кода сквозного доступа, но я думаю, что это, как правило, ошибка. Предпочитаю композицию наследованию.

Я бы спросил, действительно ли ваш пользователь хочет иметь дело с одним интерфейсом с 70 методами, но если это действительно то, что вы хотите, тогда я не понимаю, почему «нецелесообразно» писать код в 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(); }
    // ...
};

Живая демоверсия .

Это имеет то преимущество, что вы можете легко изменить свое решение в будущем и заменить A и B чем-то другим, не изменяя интерфейс C.

Вы можете скрыть реализацию C далее, , используя идиому PIMPL или , разбив C на абстрактный базовый класс C и реализацию CImpl .

0 голосов
/ 13 мая 2018

Использовать виртуальное множественное наследование .Причина, по которой

является плохой практикой множественного наследования

, заключается в том, что это напрямую приведет к неоднозначным вызовам или избыточным данным, поэтому вы можетеиспользуйте виртуальное наследование , чтобы избежать этого.

Узнайте, как реализация C ++ iostream очень поможет, подумал я.

...