Pull to refresh

Comments 13

Мне кажется если в реальном коде встречается такая ситуация, то этот код — плохой.
да 90% вопросов на собеседовании типа что выведет этот код или как сделать используя наследование такой вывод в консоль- это отвратительный код который в жизни если встретился то надо удти убивать автора сразу.

Просто тут сейчас это вопрос на понимание.
Ну да — это одна из тех вещей, которые надо знать, чтобы лучше понимать принципы работы той платформы, на которой ты пишешь, но которую лучше в коде не использовать.
UFO just landed and posted this here
Достаточно запустить исходный пример, чтобы увидеть, что это не так.
UFO just landed and posted this here
>Однако насколько логично, что без ведома разработчика класса Derived после изменений в базовом классе хорошо протестированный код вдруг перестанет работать?

Скажите, а насколько логично, что без ведома разработчика класса Derived в нём появится переопределённый метод

public override void Foo(int i) 
    {
        Console.WriteLine("Derived.Foo(int)");
    }

?

Вы рассматриваете две абсолютно разных ситуации, и одной из них пытаетесь объяснить вторую.

Рассматриваемая ситуация абсурдна. В классе наследнике существует более подходящий метод. Неважно, что он переопределяет метод базового класса — метод явно объявлен в классе наследнике, а не просто скрыто унаследован от базового. По всей логике должен быть вызван именно он.
Примечание к примечанию:
Если закомментировать метод Foo(Integer&) в классе Derived, то в С++ будет вызван Derived::Foo(Object&) (т.е. более подходящий метод базового класса не будет рассматриваться в качестве кандидата)

Если все-таки хочется, чтобы в данном случае позвался метод базового класса, то в Derived нужно добавить:
class Derived : public Base
{
using Base::Foo;
...
};
по-моему, в C# все реализовано правильно.
Вы показали пример в вакууме.
если перейти на более реальный пример, то:

class Program
{
    static void Main(string[] args)
    {
        TextNode d = new TextNode();
        d.Foo(d);

        Node node = new TextNode();
        node.Foo(node);
    }
}

class Node
{
    public virtual void Foo(Node node)
    {
        Console.WriteLine("Node.Foo(Node)");
    }
}

class TextNode : Node
{
    public override void Foo(Node node)
    {
        Console.WriteLine("TextNode.Foo(Node)");
    }

    public void Foo(TextNode textNode)
    {
        Console.WriteLine("TextNode.Foo(TextNode)");
    }
}


выведет:

TextNode.Foo(TextNode)
TextNode.Foo(Node)

т.к. мы создаем экземпляры TextNode, то будут вызваны именно его методы.
а метод с object — не самый удачный пример для показа иерархии классов, где концепция виртуальных методов отрабатывает правильно в C#.
Здесь еще не хватает вызова node.Foo(d); который выведет не то, на что можно бы рассчитывать.
Вообще, если честно, я не понимаю зачем перегружать функцию аргументами наследуемыми один от другого. Попахивает проблемой, которая только и ждет того, чтобы всплыть.

Кроме того, не помню в какой из книг, вроде как у банды четырех и было — есть рекомендация избегать глубоких иерархий наследования. Должен быть либо интерфейс и классы его реализующие, либо абстрактный класс. Иерархия имеет смысл среди интерфейсов и абстрактных классов, чтоб позволить разделить уровни использования конкретных классов.
Изменения в базовом классе, который по сценарию автора «живет своей жизнью», практически гарантировано «убивают» всех наследников, и сабжевый пример тут не влияет совершенно. Если же надо все же связать такие отдельно живущие классы, то та же банда рекомендует предпочесть делегирование наследованию. Если же нужна при этом общая точка доступа — интерфейс.
Тогда изменения в «базовом» классе будут существенно более очевидные и легкоисправимые ошибки вызывать.
Не совсем правильно написал, ведь в базовом классе в этом примере всего один метод.
Пример обратен примеру автора. Здесь совпадает направление наследования у классов и аргументов перегруженного метода. У автора оно противоположное.
А посему нет проблемы, что нужный метод найдется в наследованном классе раньше, чем в базовом.
>Здесь еще не хватает вызова node.Foo(d); который выведет не то, на что можно бы рассчитывать.

node.Foo(d) выведет
TextNode.Foo(Node)

что вполне логично, т.к. мы имеем дело с конкретным наследником.

>Изменения в базовом классе, который по сценарию автора «живет своей жизнью», практически гарантировано «убивают» всех наследников

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

с чем полностью согласен.
поэтому необходимо либо использовать правильную архитектуру с паттернами GoF, либо использовать принцип декомпозиции вместо иерархии.
Мне нравится как перегрузка с наследованием решена в C++. В перегрузке участвуют только методы того типа, у которого мы их вызываем, имена методов базового класса скрываются (hide) независимо от виртуальности. Если разработчик хочет, чтобы имя в производном классе было перегружено совместно с именем из базового класса, он это указывает явно, внесением имени в область видимости производного класса: using base::foo; (unhide). Вот и все, четко и понятно, никаких неожиданностей. Тема перегрузки и тема замещения виртуальной функции в C++ не пересекаются. Могли бы для шарпа что-то подобное придумать.

Пример:
struct base
{
    void foo(int);
};

struct derived : base
{
    // using base::foo; // раскомментить для перегрузки с foo(int)
    void foo(double);
};
Sign up to leave a comment.

Articles