Для тех, кто до конца не понимает, почему наследование реализации - зло и почему его сравнивают даже с goto.
Вот вам строка: obj.DoSomething. Если я знаю, что в системе нет наследования реализации, я понимаю, что именно будет выполненно. Процедура DoSomething того фактического типа, который имеет obj.
Ответьте на тот же вопрос, если система пронизана наследованием реализации. Попробуйте разобраться, как прыгает поток управления, даже при вызове метода одного объекта. Вызвали из своего метода свой же другой метод - управление упрыгало фиг знает в какого предка...
Если мы имеем нечто вроде "A* obj" (пусть у нас С++), где A это конкретный (не абстрактный) класс, то даже если он от кого-то там унаследовался (по реализации естественно), то узнать что именно случится если будет вызвано obj->DoSomething() довольно просто - в худшем случае придется обойти ациклический ориентированный граф.
А вот если "A" - это интерфейс, то все, алес. Узнать что именно произойдет при вызове obj->DoSomething() не представляется возможным - там может быть абсолютно что угодно. В общем случае единственным способом узнать это - использовать пошаговый отладчик. В точности то же самое, что и в случае goto (и то же что и при использовании процедурных типов).
Когда я вижу в чужом коде активное использование интерфейсов - знаю сразу, будет головная боль и ползание под отладчиком (и не потому что что-то глючит, а потому что иначе разобраться в коде невозможно).
Это ещё один, кстати, пример, откуда потом берётся жирность IDE. Потому что распутывать клубок структуры программы в подобных случаях без поддержки IDE очень трудно.
Ну да, ну да. А если у нас интерфейсы, то хоть через жирную IDE, хоть без оной, распутать клубок не возможно даже теоретически. Поэтому там жиные IDE и не нужны :-)
(на самом деле "жирные" IDE нужны для проектов реального размера, то есть с полмиллиона строк кода)
Второй пример: у вас есть наследование реализации от какого-то типа X. И вдруг вам нужно завести несколько реализаций X. Более простой случай - если реализации не сосуществуют во время выполнения. Например, под разные ОС. Вам придётся иметь две версии одного исходника, как-то их переключать, вместо того, чтобы просто иметь два отдельных класса - реализации базового абстрактного. При наследовании только от абстрактных классов получается придерживаться принципа - каждый вариант реализации существует в виде своего модуля, класса, и т.п. А не дремучая вариативность на уровне исходников.
Ничего не понял. Не вижу разницы. Хоть у меня интерфейс и его реализация, хоть два класса в иерархии наследования реализации, один фиг для операционной системы A у меня будет исходник Xa, для операционной системы B у меня будет Xb, и все. Выбор реализации для сборки ложится на плечи системы сборки. Тот кто отнаследовался от X, тому глубоко будет пофиг что там внизу на самом деле - Xa, или Xb.
Ещё хуже, если вам нужно иметь во время выполнения разные реализации X. Т.е. некоторые экземпляры Y должны иметь в своей базе одну реализацию X, некоторые - другую.
А кто-то предлагал наследование реализации как ЕДИНСТВЕННЫЙ механизм для декомпозиции задачи?
PS. Проблема наследования реализации, да и вообще наследования, не в том что ты описываешь, а в том, что это слишком высокоуровневый и весьма специфичный механизм. Как и любой подобный механизм, оно с блеском решает ряд задач, и с блеском же проваливает другую часть задачек. Это как "контент как интерфейс" в ББ - для узкого ряда задач оно отлично работает, а для других задач оно не применимо в принципе (хотя можно попробовать натянуть, получив в результате неюзабельную переусложненную какашку). Но это же не повод отказываться от подобного мезанизма вообще?