Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Как реализовать обязательный вызов метода базового класса?

#cpp #ооп


Столкнулся с интересной проблемой при написании программы на с++, есть класс родитель,
есть его наследник, в наследнике я переопределяю какую-то функцию, но хочу вызвать
и функцию базового класса, решение очевидное и простое

class A
{
public:
    virtual void Func()
    {
        //do something
    }
}

class B : A
{
public:
    void Func() override
    {
        A::Func();
        //aditional logic
    }
}


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

class A
{
public:
    virtual void Func() final
    {
        //do something
        FuncInternal();
    }
protected:
    virtual void FuncInternal() = 0;
}

class B : A
{
    void FuncInternal() override
    {
        //aditional logic
    }
}


Теперь всегда отработает сначала метод базового класса, потом наследника, но понял
что если уровень наследования будет больше 2 то мне будет нужно вводить FuncInternal1,
FuncInternal2 и т.д. И вот тут я завис, как мне решить задачу по избавлению от дублирования
вызова базовой функции во всех потомках таким образом, чтоб реализация не отличалась
в зависимости от количества наследников.

Думаю что я что-то упускаю из общих знаний ООП, привязка к языку не обязательна.
Спасибо

UPDATE1

Источник проблемы в задумке сделать абстрактный класс-интерфейс, к примеру, IRenderable,
и если есть объект который наследуется от такого класса Monster : IRenderable, то я
уверен что у Monster будет функция рендер и что при обращению к этой функции будет
проведен некий единый набор действий описанных в IRenderable.
    


Ответы

Ответ 1



Поскольку вы пишете, что конкретный язык вам безразличен, вот вам пример с честной рефлексией на C#. Идея: вызывать «родительскую» имплементацию не нужно, вместо этого каждый из порождённых классов может, если захочет, добавить свою имплементацию метода. Базовый метод вызывает все имплементации по очереди. Классы выглядят при этом так: class C1 { public void F(int x) { // вызываем в цикле все имплементации foreach (var impl in Util.GetAllImplementations>(this, "FImpl")) impl(x); } // имплементация в C1 private void FImpl(int x) => Console.WriteLine($"From C1::F({x})"); } class C2 : C1 { // тут нету имплементации } class C3 : C2 { // имплементация в C1 private void FImpl(int x) => Console.WriteLine($"From C3::F({x})"); } Класс-утилита (ужасы рефлексии): static class Util { static public IEnumerable
GetAllImplementations
(object obj, string name) { Type[] argTypes = GetArgTypes(typeof(DT)); for (Type curr = obj.GetType(); curr != null; curr = curr.BaseType) { var method = curr.GetMethod( name, BindingFlags.NonPublic | BindingFlags.Instance, null, argTypes, null); if (method == null) continue; yield return (DT)(object)Delegate.CreateDelegate(typeof(DT), obj, method); } } static Type[] GetArgTypes(Type delegateType) { if (delegateType == typeof(Action)) return new Type[0]; if (!delegateType.IsGenericType) throw new ArgumentException("Expected delegate type"); var typedef = delegateType.GetGenericTypeDefinition(); var isFunc = (typedef.Name.StartsWith("Func`")); var isAction = (typedef.Name.StartsWith("Action`")); if ((!isFunc && !isAction) || typedef.Namespace != "System") throw new ArgumentException("Expected delegate type"); var argTypes = delegateType.GetGenericArguments(); if (isFunc) Array.Resize(ref argTypes, argTypes.Length - 1); return argTypes; } } Запускаем, получаем результат: class Program { static void Main(string[] args) { new C3().F(1); } } From C3::F(1) From C1::F(1) Уверен, что на C++ можно получить такой же результат при помощи какой-нибудь шаблонной магии.

Комментариев нет:

Отправить комментарий