Существует ли другое обоснование использования абстрактных классов / интерфейсов в C ++ и Java?

13

Согласно Хербу Саттеру, следует предпочитать абстрактные интерфейсы (все чисто виртуальные функции) абстрактным классам в C ++, чтобы максимально отделить реализацию. Хотя я лично считаю, что это правило очень полезно, я недавно присоединился к команде со многими программистами на Java, и в коде Java это руководство, по-видимому, не существует. Функции и их реализации очень часто находятся в абстрактных классах. Так что я неправильно понял Херба Саттера даже для C ++ или есть общая разница в использовании абстрактных функций в C ++ по сравнению с Java. Являются ли абстрактные классы с кодом реализации более понятным в Java, чем в C ++, и если да, то почему?

Мартин
источник
1
У меня были некоторые сомнения, и я, наконец, изложил это здесь, потому что это может быть связано с некоторыми принципами проектирования, которые мне не хватает в java oo. Таким образом, речь идет не о общих советах, а о правильном и неправильном использовании языка
Martin
Интерфейсы должны быть чисто виртуальными. Идея абстрактных классов состоит в том, что они частично реализованы, и это зависит от реализации, чтобы заполнить пробелы, не повторяя код без необходимости (например, зачем иметь write (byte) и write (int) в каждом подклассе, когда вы можете иметь абстрактный класс call write (byte) from write (int))
1
Возможно связано: stackoverflow.com/q/1231985/484230 дает причину предпочитать абстрактные классы в Java. Для C ++ эта причина, похоже, не выполняется из-за наличия свободных функций, которые могут добавлять функциональность на уровне интерфейса
Martin
1
Я думаю, что золотое правило состоит в том, чтобы «сделать не листовые классы абстрактными», но это не предъявляет никаких «только чистых» или «пустых» требований.
Kerrek SB
1
Если это работает для вас, это работает для вас. Я действительно не понимаю, почему люди паникуют, когда их код больше не придерживается последних мнений.
Джеймс

Ответы:

5

ООП имеет состав и замену.

C ++ имеет множественное наследование, специализацию шаблонов, встраивание и семантику значения / перемещения / указателя.

Java имеет единственное наследование и интерфейсы, семантику встраивания и ссылки.

Обычный способ, которым школа ООП использует эти языки, - это использование наследования для замены объекта и встраивания для композиции. Но вам также нужен общий предок и способ приведения во время выполнения (в C ++ называется dynamic_cast, в Java просто запрашивает интерфейс у другого).

Ява делает все это своими собственными java.lang.Objectкорнями. C ++ не имеет предопределенного общего корня, поэтому вы должны по крайней мере определить его, чтобы прийти к одной и той же «картинке» (но это ограничивает некоторые возможности C ++ ...).

После этого возможность иметь полиморфизм во время компиляции (например, CRTP) и семантическое значение может предложить и другие альтернативы тому, как концепция «объекта ООП» может быть перенесена в программу C ++.

Вы даже можете представить себе ересь, использующую вложение и неявное преобразование для управления заменой и частным наследованием для управления композицией, фактически инвертируя традиционную школьную парадигму. (Конечно, этот путь на 20 лет моложе другого, поэтому не ожидайте широкой поддержки сообщества в этом)

Или вы можете представить себе виртуальную общую базу для всех классов: интерфейс формы (без реализации) и конечные классы (полностью реализованные), проходящие через частично реализованные интерфейсы и четные интерфейсные кластеры, используя «доминирование» как диспетчеризацию от интерфейса до реализаций через «многоуровневую структуру». -параллелограмм "Схема наследования.

Сравнение ООП с java с С ++ при условии, что существует только один и единственный способ ООП, ограничивает возможности обоих языков.

Принуждение C ++ к строгому соблюдению идиом кодирования Java денатурирует C ++, так как принуждение Java вести себя как C ++ -подобный язык денатурирует Java.

Дело не в «чувствительности», а в разных «механизмах агрегации», которыми обладают два языка, и по-разному объединяют их, что делает некоторые идиомы более выгодными в одном языке, чем в другом, и наоборот.

Эмилио Гаравалья
источник
1
Я думаю, что этот ответ очень интересен, так как он кратко описывает языковые особенности как инструмент для oo и принципы дизайна только как помощь, а не доктрина. Однако вам не нужен общий корень, если вы хотите сделать oo в c ++. Это просто неправильно, в том числе и потому, что у вас есть операторы и шаблоны (которые являются очень мощной альтернативой основному древовидному дизайну Java, как вы также указали). Кроме того, ваши очки являются наиболее значимыми во всех ответах
Martin
1
@Martin: в «техническом смысле» вы правы, но если вам нужен полиморфизм времени выполнения (поскольку фактический тип создаваемых объектов зависит от входных данных программы), то «root» («a» - это статья, а не ярлык для » один и только ") - это то, что делает все объекты" двоюродными братьями ", а иерархия - прогуливаемая во время выполнения. Разные корни происходят от разных предков, не связанных друг с другом. Является ли это «хорошим» или «плохим» - это вопрос контекста, а не идиомы.
Эмилио Гаравалья
Это правда. Я думал, что вы имеете в виду искусственное распространение одного общего корня для всей программы на С ++, и рассматривал его как дефект, которого нет, по сравнению с Java. Но после редактирования вы ясно даете понять. Еще раз спасибо
Мартин
12

Принцип справедлив для обоих языков, но вы не проводите справедливое сравнение. Вы должны сравнить чистые абстрактные классы C ++ с интерфейсами Java.

Даже в C ++ у вас могут быть абстрактные классы, в которых реализованы некоторые функции, но они происходят от чистого абстрактного класса (без реализаций). В Java у вас будут те же абстрактные классы (с некоторыми реализациями), которые могут быть получены из интерфейсов (без реализаций).

Лучиан Григоре
источник
Поэтому, когда вы предпочитаете абстрактный класс, а не интерфейсный класс в C ++. Я всегда выбирал интерфейс плюс функции, не являющиеся членами в C ++.
Мартин
1
@ Мартин, который зависит от дизайна. В основном, всегда предпочитают интерфейс. Но у « всегда » правил есть исключения ...
Лучиан Григор
Правда, но в коде Java я вижу абстрактные классы, в значительной степени представляющие большинство. Может ли это быть из-за того, что свободные функции, работающие на интерфейсах, невозможны в Java?
Мартин
3
@ Мартин хорошо свободных функций вообще не возможен в Java, так что это может быть причиной, да. Хорошее место! Ответил на свой вопрос! Вы можете добавить ответ самостоятельно, я думаю, что это так.
Лучиан Григоре
4

Как правило, те же принципы ОО верны для Java и C ++. Однако одно большое отличие состоит в том, что C ++ поддерживает множественное наследование, тогда как в Java вы можете наследовать только от одного класса. Это главная причина, по которой у Java есть интерфейсы, которые, я считаю, дополняют отсутствие множественного наследования и, возможно, ограничивают то, что вы можете с ним делать (так как много критики вызывает злоупотребление множественным наследованием). Так что, вероятно, по мнению программистов на Java, существует более сильное различие между абстрактными классами и интерфейсами. Абстрактные классы используются для совместного использования и наследования поведения, в то время как интерфейсы просто используются для добавления дополнительной функциональности. Помните, что в Java вы можете наследовать только от одного класса, но у вас может быть много интерфейсов. В C ++ , однако, чистые абстрактные классы (т.е. «C ++ интерфейс») являются используется для совместного использования и наследования поведения в отличие от цели интерфейса Java (хотя вам все еще необходимо реализовать функции), поэтому использование отличается от интерфейсов Java.

Джесси Гуд
источник
0

Иногда имеет смысл иметь некоторую реализацию по умолчанию. Например, общий метод PrintError (string msg), который применим ко всем подклассам.

virtual PrintError(string msg) { cout << msg; }

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

Научная фантастика
источник