Обосновывает ли этот пример использование наследования publi c - PullRequest
0 голосов
/ 09 мая 2020

Я пытаюсь реализовать библиотеку, в которой Class1 предоставляет около пяти c опубликованных методов от Method1 до Method5. Class2 предоставляет два метода - Methods6 и Method7. А Class3 предоставляет один метод - Method8. Теперь, для конечного пользователя, я хочу предоставить методы из комбинации этих классов. Например, если конечный пользователь создает экземпляр класса с именем Class1Class2, он должен иметь доступ от Method1 до Method7, если они создают экземпляр класса с именем Class1Class3, они должны иметь доступ к Method1 по Method5 и Method8.

Есть 3 разных подхода, которые я мог бы придумать (пожалуйста, предложите и другие):

  1. Множественное наследование: оставьте каждый из Class1, Class2 и Class3 как есть . Затем создайте новый класс Class1Class2, который публично множественно наследует от Class1 и Class2. Точно так же я могу создать класс Class1Class3, который публично множественно наследуется от Class1 и Class3.

  2. Многоуровневое наследование: я мог бы унаследовать Class2 от Class1 и назвать это Class1Class2. И Class3 из Class1 и назовите это Class1Class3. И если нам нужен Class1Class2Class3, мы наследуем этот класс от Class2 и Class3, которые оба являются производными от Class1. Здесь мы будем использовать виртуальное наследование для решения проблемы ромба. Я не собираюсь использовать Class2Class3, так что здесь проблем быть не должно.

  3. Состав: оставьте каждый из Class1, Class2 и Class3 как есть. Создайте Class1Class2, который реализует каждый из методов от Method1 до Method7, и внутренне делегируйте их объектам Class1 и Class2 соответственно. Точно так же Class1Class3 будет составлять объекты Class1 и Class3. При таком подходе нам необходимо предоставить реализации для всех методов и делегировать их составным объектам.

Хотя рекомендация «Композиция вместо наследования» обычно отлично подходит для слабого связывания классов, и т. Д. c., В приведенном выше случае, когда мы должны повторно использовать код из отдельных конкретных реализаций , Подход 1 или 2 кажутся лучшими вариантами.

Ответы [ 3 ]

2 голосов
/ 09 мая 2020

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

Например:

class Class1Class2 : Class1, Class2 {
public:
    using Class1::Method1;
    // ...
    using Class2::Method6;
    // ...
};

Частное наследование, хотя технически называется наследованием, очень отличается от наследования publi c, которое само по себе концептуально отличается от подтипов.

В C ++ (и многих других языках, поддерживающих OOP), publi c наследование обычно обеспечивает как подтипирование, так и повторное использование кода . Однако вполне возможно создать класс с некорректным поведением там, где ожидается родительский класс. Это потенциально подрывает выделение подтипов. Также вполне возможно создать класс, который не использует повторно какую-либо реализацию родительского класса. Это потенциально подрывает повторное использование кода.

Концептуально создание подтипов выражается наследованием интерфейса, в то время как наследование реализации - это только один из способов повторного использования кода. Выражение «композиция вместо наследования», насколько я понимаю, касается использования других инструментов для повторного использования кода, потому что наследование реализации часто приводит к плохому коду. Однако на самом деле нет другого способа достичь истинного подтипирования, кроме наследования, поэтому он все еще может быть полезен там 1 .

С другой стороны, частное наследование - это просто странная форма сочинение. Он просто заменяет член частным базовым классом. Преимуществом этого является возможность использовать using, чтобы легко раскрыть части этого «члена», которые вы хотите раскрыть.


1 Мне лично не нравится ни то, ни другое формы (publi c) наследования, предпочитая статический c полиморфизм и утиную типизацию во время компиляции. Однако я могу с удовольствием работать с наследованием интерфейсов, тогда как обычно я держусь подальше от наследования реализации.

1 голос
/ 09 мая 2020

Если вы хотите простую комбинацию, вы можете использовать шаблон как вариант вашего первого предложения:

template <typename ... Bases>
struct Derived : Bases...
{
};

using Class1Class2 = Derived<Class1, Class2>;
using Class1Class2Class3 = Derived<Class1, Class2, Class3>;
1 голос
/ 09 мая 2020

Чтобы сделать вещи более интересными, вы можете использовать CRTP. Вот пример:

template<typename Base>
class ClassA {
public:
    void MethodA1() {static_cast<Base*>(this)->MethodA1_Impl();}
    void MethodA2() {static_cast<Base*>(this)->MethodA2_Impl();}
};

template<typename Base>
class ClassB {
public:
    void MethodB1() {static_cast<Base*>(this)->MethodB1_Impl();}
    void MethodB2() {static_cast<Base*>(this)->MethodB2_Impl();}
};

template<typename Base>
class ClassC {
public:
    void MethodC1() {static_cast<Base*>(this)->MethodC1_Impl();}
    void MethodC2() {static_cast<Base*>(this)->MethodC2_Impl();}
};

class ClassABC: public ClassA<ClassABC>, public ClassB<ClassABC>, public ClassC<ClassABC> {
public:
    //void MethodA1_Impl();
    //void MethodA2_Impl();
    //void MethodB1_Impl();
    //void MethodB2_Impl();
    //void MethodC1_Impl();
    //void MethodC2_Impl();
};

Вы можете раскомментировать и реализовать ЛЮБОЕ подмножество MethodXY_Impl(), и это будет компилироваться. Клиентский код может вызывать любой метод из MethodXY(). Если нет соответствующей реализации - компилятор выдаст ошибку.

...