По просьбе exizt я расширяю свой комментарий для более длинного ответа.
Самая большая ошибка, которую допускают люди, - верить, что интерфейсы - это просто пустые абстрактные классы. Интерфейс - это способ сказать программисту: «Мне все равно, что вы мне дадите, если он следует этому соглашению».
Библиотека .NET служит прекрасным примером. Например, когда вы пишете функцию, которая принимает IEnumerable<T>
, то, что вы говорите: «Мне все равно, как вы храните свои данные. Я просто хочу знать, что я могу использовать foreach
цикл.
Это приводит к некоторому очень гибкому коду. Внезапно, чтобы быть интегрированным, все, что вам нужно сделать, это играть по правилам существующих интерфейсов. Если реализация интерфейса затруднительна или сбивает с толку, возможно, это намек на то, что вы пытаетесь засунуть квадратный колышек в круглое отверстие.
Но затем возникает вопрос: «А как насчет повторного использования кода? Мои преподаватели CS сказали мне, что наследование было решением всех проблем повторного использования кода и что наследование позволяет вам писать один раз и использовать везде, и это вылечит менангит в процессе спасения сирот». из восходящих морей и не было бы больше слез и так далее и тому подобное и т. д. и т. д. "
Использование наследования только потому, что вам нравится звучание слов «повторное использование кода», является довольно плохой идеей. Руководство по стилю кода от Google делает этот момент довольно лаконичным:
Композиция часто более уместна, чем наследование. ... [B], поскольку код, реализующий подкласс, распространяется между базовым и подклассом, может быть сложнее понять реализацию. Подкласс не может переопределять функции, которые не являются виртуальными, поэтому подкласс не может изменить реализацию.
Чтобы проиллюстрировать, почему наследование не всегда является ответом, я собираюсь использовать класс MySpecialFileWriter †. Человек, который слепо верит, что наследование является решением всех проблем, будет утверждать, что вы должны пытаться наследовать FileStream
, чтобы не дублировать FileStream
код. Умные люди признают, что это глупо. Вы должны просто иметь FileStream
объект в своем классе (как локальную переменную или переменную-член) и использовать его функциональность.
FileStream
Пример может показаться надуманным, но это не так . Если у вас есть два класса, которые оба одинаково реализуют один и тот же интерфейс, то у вас должен быть третий класс, который инкапсулирует любую дублированную операцию. Ваша цель должна состоять в том, чтобы написать классы, которые являются автономными многократно используемыми блоками, которые могут быть собраны вместе, как legos.
Это не означает, что наследства следует избегать любой ценой. Есть много моментов, которые нужно рассмотреть, и большинство из них будет рассмотрено при исследовании вопроса «Композиция против наследования». У нашего собственного Stack Overflow есть несколько хороших ответов на эту тему.
В конце концов, настроениям вашего коллеги не хватает глубины или понимания, необходимых для принятия взвешенного решения. Исследуйте предмет и выясните это для себя.
† При иллюстрации наследования каждый использует животных. Это бесполезно. За 11 лет разработки я никогда не писал класс с именем Cat
, поэтому я не собираюсь использовать его в качестве примера.
alligator
Реализацияeat
отличается, конечно, тем, что она принимаетcat
иdog
параметры.