Интерфейсы как тип абстрактного класса c
Один из принципов проектирования гласит, что при создании системы классов надо программировать на уровне интерфейсов, а не их конкретных реализаций. Под интерфейсами в данном случае понимаются не только типы C#, определенные с помощью ключевого слова interface , а определение функционала без его конкретной реализации. То есть под данное определение попадают как собственно интерфейсы, так и абстрактные классы, которые могут иметь абстрактные методы без конкретной реализации.
В этом плане у абстрактных классов и интерфейсов много общего. Нередко при проектировании программ в паттернах мы можем заменять абстрактные классы на интерфейсы и наоборот. Однако все же они имеют некоторые отличия.
Когда следует использовать абстрактные классы:
- Если надо определить общий функционал для родственных объектов
- Если мы проектируем довольно большую функциональную единицу, которая содержит много базового функционала
- Если нужно, чтобы все производные классы на всех уровнях наследования имели некоторую общую реализацию. При использовании абстрактных классов, если мы захотим изменить базовый функционал во всех наследниках, то достаточно поменять его в абстрактном базовом классе. Если же нам вдруг надо будет поменять название или параметры метода интерфейса, то придется вносить изменения и также во всех классы, которые данный интерфейс реализуют.
Когда следует использовать интерфейсы:
- Если нам надо определить функционал для группы разрозненных объектов, которые могут быть никак не связаны между собой.
- Если мы проектируем небольшой функциональный тип
Ключевыми здесь являются первые пункты, которые можно свести к следующему принципу: если классы относятся к единой системе классификации, то выбирается абстрактный класс. Иначе выбирается интерфейс. Посмотрим на примере.
Допустим, у нас есть система транспортных средств: легковой автомобиль, автобус, трамвай, поезд и т.д. Поскольку данные объекты являются родственными, мы можем выделить у них общие признаки, то в данном случае можно использовать абстрактные классы:
public abstract class Vehicle < public abstract void Move(); >public class Car : Vehicle < public override void Move() < Console.WriteLine("Машина едет"); >> public class Bus : Vehicle < public override void Move() < Console.WriteLine("Автобус едет"); >> public class Tram : Vehicle < public override void Move() < Console.WriteLine("Трамвай едет"); >>
Абстрактный класс Vehicle определяет абстрактный метод перемещения Move() , а классы-наследники его реализуют.
Но, предположим, что наша система транспорта не ограничивается вышеперечисленными транспортными средствами. Например, мы можем добавить самолеты, лодки. Возможно, также мы добавим лошадь — животное, которое может также выполнять роль транспортного средства. Также можно добавить дирижабль. Вобщем получается довольно широкий круг объектов, которые связаны только тем, что являются транспортным средством и должны реализовать некоторый метод Move() , выполняющий перемещение.
Так как объекты малосвязанные между собой, то для определения общего для всех них функционала лучше определить интерфейс. Тем более некоторые из этих объектов могут существовать в рамках параллельных систем классификаций. Например, лошадь может быть классом в структуре системы классов животного мира.
Возможная реализация интерфейса могла бы выглядеть следующим образом:
public interface IMovable < void Move(); >public abstract class Vehicle : IMovable < public abstract void Move(); >public class Car : Vehicle < public override void Move() =>Console.WriteLine("Машина едет"); > public class Bus : Vehicle < public override void Move() =>Console.WriteLine("Автобус едет"); > public class Hourse : IMovable < public void Move() =>Console.WriteLine("Лошадь скачет"); > public class Aircraft : IMovable < public void Move() =>Console.WriteLine("Самолет летит"); >
Теперь метод Move() определяется в интерфейсе IMovable, а конкретные классы его реализуют.
Говоря об использовании абстрактных классов и интерфейсов можно привести еще такую аналогию, как состояние и действие. Как правило, абстрактные классы фокусируются на общем состоянии классов-наследников. В то время как интерфейсы строятся вокруг какого-либо общего действия.
Например, солнце, костер, батарея отопления и электрический нагреватель выполняют функцию нагревания или излучения тепла. По большому счету выделение тепла — это единственный общий между ними признак. Можно ли для них создать общий абстрактный класс? Можно, но это не будет оптимальным решением, тем более у нас могут быть какие-то родственные сущности, которые мы, возможно, тоже захотим использовать. Поэтому для каждой вышеперечисленной сущности мы можем определить свою систему классификации. Например, в одной системе классов, которые наследуются от общего астрактного класса, были бы звезды, в том числе и солнце, планеты, астероиды и так далее — то есть все те объекты, которые могут иметь какое-то общее с солнцем состояние. В рамках другой системы классов мы могли бы определить электрические приборы, в том числе электронагреатель. И так, для каждой разноплановой сущности можно было бы составить свою систему классов, исходяющую от определенного абстрактного класса. А для общего действия определить интерфейс, например, IHeatable, в котором бы был метод Heat, и этот интерфейс реализовать во всех необходимых классах.
Таким образом, если разноплановые классы обладают каким-то общим действием, то это действие лучше выносить в интерфейс. А для одноплановых классов, которые имеют общее состояние, лучше определять абстрактный класс.
Интерфейсы как тип абстрактного класса c
Вроде статья начиналась как статья про абстрактные классы и интерфейсы, а потом вдруг — а, кстати, помните пару уровней назад вы ничего не поняли про I/O? вот, держите!
Василь Уровень 25
25 октября 2023
мені одному здалося що стаття більше про ріадери, а не про абстрактні класи та інтерфейси?
Александра 😉 Уровень 32
6 сентября 2023
Всем привет!! Изучая Java, в очередной раз осознала нехватку живого общения с единомышленниками =). Вот и решила создать группу в телеграмм, где Java — студенты смогут не только обсуждать вопросы по изучению Java, но и организовывать живые встречи. Кому интересно, вступайте =) https://t.me/JavaCommunityMoscow
Dmitry Vidonov Уровень 29 Expert
4 сентября 2023
Молодец Aditi! Продуктивная бабца!
chess.rekrut Уровень 25
22 августа 2023
Rustam Уровень 35 Student
31 июля 2023
начал понимать суть интерфейсов, спасибо
Alexander Rozenberg Уровень 32
25 июля 2023
Unholy intromen Уровень 29
14 июля 2023
Автор, выходи за меня.
Сообщество
JavaRush — это интерактивный онлайн-курс по изучению Java-программирования c нуля. Он содержит 1200 практических задач с проверкой решения в один клик, необходимый минимум теории по основам Java и мотивирующие фишки, которые помогут пройти курс до конца: игры, опросы, интересные проекты и статьи об эффективном обучении и карьере Java‑девелопера.
Подписывайтесь
Язык интерфейса
«Программистами не рождаются» © 2024 JavaRush
Скачивайте наши приложения
«Программистами не рождаются» © 2024 JavaRush
Этот веб-сайт использует данные cookie, чтобы настроить персонально под вас работу сервиса. Используя веб-сайт, вы даете согласие на применение данных cookie. Больше подробностей — в нашем Пользовательском соглашении.
Отличия абстрактного класса от интерфейса (abstract class and interface)
Абстрактный класс — это класс, у которого не реализован один или больше методов (некоторые языки требуют такие методы помечать специальными ключевыми словами).
Интерфейс — это абстрактный класс, у которого ни один метод не реализован, все они публичные и нет переменных класса.
Интерфейс нужен обычно когда описывается только интерфейс (тавтология). Например, один класс хочет дать другому возможность доступа к некоторым своим методам, но не хочет себя «раскрывать». Поэтому он просто реализует интерфейс.
Абстрактный класс нужен, когда нужно семейство классов, у которых есть много общего. Конечно, можно применить и интерфейс, но тогда нужно будет писать много идентичного кода.
В некоторых языках (С++) специального ключевого слова для обозначения интерфейсов нет.
Можно считать, что любой интерфейс — это уже абстрактный класс, но не наоборот.
Отслеживать
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков
ответ дан 10 июл 2013 в 8:34
112k 6 6 золотых знаков 93 93 серебряных знака 159 159 бронзовых знаков
А где я писал о «pure virtual методах» ? Я писал о том, что у метода отсутствует реализация. А виртуальный он или нет — это детали реализации языка.
11 июл 2013 в 6:55
Проще сказать что интерфейс это частный случай абстрактного класса
2 дек 2015 в 5:10
В с++ можно наследоваться от произвольного количества абстрактных классов. Употребление слов наследует/реализует — просто соглашение.
31 мая 2016 в 19:00
Java и новый C# имеют несколько расширенные определения интерфейса по сравнению с вашим
22 сен 2017 в 11:17
К слову, для C#8+ Ваш ответ более неактуален ¯_(ツ)_/¯
24 июн 2019 в 14:14
tl;dr: Абстрактный класс — средство разработки классов на нижнем уровне, средство для повторного использования кода; интерфейс — средство выражения семантики класса. Таким образом, это совершенно разные, мало связанные между собой понятия.
Думайте об этом по-другому.
Абстрактный класс — это «заготовка» класса: реализовано большинство методов (включая внутренние), кроме нескольких. Эти несколько нереализованных методов вполне могут быть внутренними методами класса, они лишь уточняют детали имплементации. Абстрактный класс — средство для повторного использования кода, средство, чтобы указать, какой метод обязан быть перекрыт для завершения написания класса.
Интерфейс же — это своего рода контракт: интерфейсы используются в определениях чтобы указать, что объект, который будет использован на самом деле, должен реализовывать (для входных параметров) или будет гарантированно реализовывать (для выходных параметров) набор методов и (что намного важнее!) иметь определённую семантику. Интерфейс вполне может быть и пустым, тем не менее, имплементировать интерфейс означает поддерживать данную семантику.
Абстрактные классы идеологически схожи с шаблонами C++: и те, и другие являются заготовками классов, но шаблону для получения класса нужно специфицировать шаблонные типы, а абстрактному классу — абстрактные методы.
Интерфейсы идеологически схожи с заголовочными файлами C++: они раскрывают методы и скрывают конкретную реализацию.
Вопрос о том, является ли интерфейс или абстрактный класс собственно классом — техническая подробность реализации, зависящая от конкретного языка программирования. Например, в C++ интерфейсы отсутствуют вовсе, и их приходится эмулировать классами без данных. Абстрактный класс в C++ как таковой также отсутствует, но им можно считать любой класс с абстрактными методами. (Отсюда ограничение C++: как минимум 1 абстрактный метод в абстрактном классе.) Также в C++ можно (непрямо) инстанциировать абстрактный класс, вызвать абстрактный метод и (возможно) получить ошибку времени выполнения. В C# интерфейсы и абстрактные классы встроены в язык.
Пример (на C#, конкретный язык значения не имеет):
// общий код для всех животных abstract class АбстрактноеЖивотное < public int Возраст < get; protected set; >public int Вес < get; protected set; >public bool Спит < get; protected set; >public void ПодатьГолос() < if (!Спит && Возраст >ВозрастПрорезанияГолоса) РеализацияПодатьГолос(); > abstract protected void РеализацияПодатьГолос(); readonly protected int ВозрастПрорезанияГолоса; > class Собака : АбстрактноеЖивотное < override protected void РеализацияПодатьГолос() < Гав(); >public void Гав() < // реализация >public Собака() < ВозрастПрорезанияГолоса = 2; >> class Кошка : АбстрактноеЖивотное < override protected void РеализацияПодатьГолос() < Мяу(); >public void Мяу() < // реализация >public Кошка() < ВозрастПрорезанияГолоса = 1; >>
interface IЖивотное < int ИнвентарныйНомер < get; >> class Лев : ОбитательЗоопарка, IЖивотное < // . >class Зебра : ОбитательЗоопарка, IЖивотное < // . >class Сторож : ОбитательЗоопарка < >// . void Инвентаризация() < Listобитатели = // . foreach (var обитатель in обитатели) if (обитатель is IЖивотное) // отделяем животных от неживотных ДобавитьЖивотное((IЖивотное)обитатель); > void ДобавитьЖивотное(IЖивотное животное) // сюда сможет попасть только животное < .
Отслеживать
ответ дан 10 июл 2013 в 12:52
207k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков
IЖивотное я думаю, даром инквизицию отменили. нельзя так код писать. уже хочется на русском - так на русском. Да и у автора был php.
10 июл 2013 в 13:23
@KoVadim: Была бы инквизиция, писал бы на латыни. Почему нельзя? Приведите хотя бы один довод. Это ж пример. А отличие интерфейса от абстрактного класса одно и то же что у PHP, что у C#. (Ну, кроме незначительных технических деталей.)
10 июл 2013 в 15:00
это легкий троллинг, что кто то путает 1с и шарп. а по существу - есть один довод - переключаться долго. Хорошо, у меня капсом, а у людей по две кнопки жать нужно или человек будет смотреть и не понимать, почему переменная класса Сторож не приводиться к типу Стopoж (это для особых любителей загадка)
10 июл 2013 в 17:44
смысл киррилических идентификаторов понимается, когда видишь код с китайскими и арабскими идентификаторами (не забываем, что китайский пишется справа на лево!). Вот рефакторинг такого кода срывает крышу намертво.
10 июл 2013 в 19:28
@VladD, не хочу обидеть, но ваши регулярные ответы аналогичного объема смахивают на графоманию. Читать их интересно, но весь их смысл можно передать куда меньшим количеством слов. Ответ @KoVadim и есть пример такого "рефакторинга" ваших ответов.
10 июл 2013 в 20:47
Мне кажется достаточно любопытным, что данный вопрос помечен тегом «ооп», но при этом во многих ответах явно просачиваются специфические аспекты конкретных языков программирования. Я же постараюсь дать ответ исходя из понятий ООП и лишь потом показать, почему вообще это различие появилось в некоторых языках программирования.
Абстрактные классы и интерфейсы имеют определенное отношение к наследованию, точнее к моделированию мира. С их помощью мы хотим выразить, что у определенной группы вещей в нашей системе есть что-то общее: некоторое общее поведение, которое отличает эту группу штуковин от всех остальных.
Допустим, для примера, мы хотим смоделировать кнопки в интерфейсе пользователя. Поскольку мы говорим об ООП, то мы выделим некоторый тип Кнопки с некоторым набором операций (которые определяют поведение) и скрытого состояния, на которое опирается поведение (да, скрытого состояния может и не быть). При этом мы можем выделить три вида операции:
- Конкретная фиксированная операция, которая должна быть абсолютно стабильно для всех типов кнопок.
- Конкретная операция с поведением по умолчанию (т.е. операция, чье поведение подходит для многих типов кнопок, но могут быть кнопки с другим поведением).
- Декларация операции без конкретной реализации (т.е. операция, чье поведение определить невозможно, поскольку на этом этапе не известно разумное поведение по умолчанию или же операции могут слишком сильно различаться у разных кнопок).
Другими словами, тип Кнопки может содержать невиртуальные методы (non-virtual methods), виртуальные методы (virtual methods) и абстрактные методы (abstract methods).
Наличие разных типов методов является очень важным инструментом моделирования и позволяет весьма точно выражать намерения проектировщика. Например, мы можем добавить невиртуальную операцию «Нажатия на кнопку», которая будет делегировать часть своей работы виртуальному (или абстрактному методу) «Обработать нажатие», но при этом всегда выполнять определенную часть работы (прозорливый читатель увидит в этом описании паттерн «Шаблонный метод»).
После того, как мы определили базовый тип, пришло время определить произвольные типы. И тут начинаются вопросы. Точнее, вопросов никаких не возникает, когда у типа есть лишь один непосредственный базовый тип или все базовые типы содержат лишь декларации операций. Не проблема, унаследовать «Кнопку меню» от «Кнопки» и переопределить метод «Нажать на кнопку». Но что, если наш тип «Кнопка меню» будет отнаследован от двух типов с одной и той же виртуальной операцией? Как переопределить лишь одну, а оставить другую? А как быть клиенту нового типа и различить, какую операцию вызвать? А что если у двух базовых типов есть поле с одним именем? А что если у одного базового типа метод «Нажать кнопку» реализован, а у другого – лишь описан в виде декларации?
Нет, все эти проблемы решаемы, и в С++, и Eiffel, и других языках программирования вы можете довольно гибко контролировать, что и как переопределять, что прятать, что выставлять наружу и как вызвать метод определенного базового типа. Но для авторов некоторых языков программирования подобная сложность показалась излишней, и они пошли на хитрость и отделили типы, которые содержат лишь декларации методов в отдельную категорию, и так появились интерфейсы.
Теперь будет легко провести разницу между тремя понятиями – интферфейса, абстрактного базового класса и конкретного базового класса.
- Интерфейс – описывает некоторое семейство типов и содержит лишь декларации операций (да, я осознанно пишу слово «декларация», а не использую слово «контракт», которое в ООП имеет вполне определенное значение).
- Абстрактный базовый класс описывает некоторое семейство типов, но помимо декларации операций может содержать реализации по умолчанию (виртуальные методы) и фиксированные операции (невиртуальные методы).
- Конкретный класс описывает некоторое семейство типов, которое готово для использования клиентами. Такой класс не может содержать декларации операций и все его операции должны быть либо фиксированными (невиртуальные методы) или содержать реализацию по умолчанию (виртуальные методы). Есть еще один подвид конкретных классов – запечатанный (sealed) класс – это разновидность конкретного класса отнаследоваться от которого невозможно, а значит он может содержать лишь конкретные операции.
Выделение интерфейсов в отдельную категорию полезно не только с точки зрения упрощения реализации языков программирования, но и для выделения разных подходов к моделированию. Так, например, наследование классов моделирует отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «Кнопкой»), а базовые классы обычно содержат определенный функционал, тесно связанный с функционалом производного класса. Базовые классы не просто моделируют группу типов, но и позволяют использовать повторно существующий функционал.
Интерфейсы же, по своей природе обладают меньшей связностью (low coupling), поскольку не обладают конкретным поведением, которое может осложнить жизнь класса-наследника. Интерфейсы также могут моделировать отношение «Является» («Кнопка меню» ЯВЛЯЕТСЯ «IКнопкой»), но могут определять и менее жесткое отношение «Может выполнять роль» (CAN DO). Например, интерфейс IEquatable из BCL определяет «дополнительное» поведение, которое говорит о возможности типов сравнивать значения объектов.
Абстрактные классы¶
Если у всех начинающих разработчиков при размышлениях об интерфейсах возникают вопросы "когда и зачем их использовать", то при размышлении об абстрактных классах к ним добавляются "чем они отличаются от интерфейсов и когда та или иная конструкция предпочтительней". Ответы на эти вопросы вы найдете в данной главе, но для начала стоит рассмотреть общие характеристики.
Общие характеристики¶
В TypeScript объявление абстрактного класса отличается от объявления обычного только добавлением ключевого слова abstract перед ключевым словом class .
abstract class Identifier <>
Абстрактные классы так же, как и обычные классы, могут расширять другие обычные и абстрактные классы и реализовывать интерфейсы.
1 2 3 4 5 6 7 8 9 10 11
interface IInterface <> class StandardClass <> // абстрактный класс расширяет обычный класс и реализует интерфейс abstract class SuperAbstractClass extends StandardClass implements IInterface <> // абстрактный класс расширяет другой абстрактный класс abstract class SubAbstractClass extends SuperAbstractClass <>
Несмотря на то, что абстрактный класс — все же класс, главное его отличие от обычного класса заключается в отсутствии возможности создания его экземпляров. Другими словами, нельзя создать экземпляр абстрактного класса.
1 2 3 4 5 6
abstract class SuperAbstractClass <> class SubStandartClass extends SuperAbstractClass <> // Error, нельзя создавать экземпляры абстрактного класса let v0: SuperAbstractClass = new SuperAbstractClass(); let v1: SuperAbstractClass = new SubStandartClass(); // Ok let v2: SubStandartClass = new SubStandartClass(); // Ok
Абстрактные классы могут содержать абстрактные члены, принадлежность к которым указывается с помощью ключевого слова abstract . Ключевое слово abstract можно применить к полям, свойствам (аксессоры) и методам абстрактного класса. При этом свойства и методы не должны иметь реализацию. В отличие от них, полям, помеченным как абстрактные, может быть присвоено значение по умолчанию.
1 2 3 4 5 6 7
abstract class Identifier public abstract field: string = 'default value'; // реализация допустима public abstract get prop(): string; // реализация недопустима public abstract set prop(value: string); // реализация недопустима public abstract method(): void; // реализация недопустима >
Абстрактный класс, расширяющий другой абстрактный класс, не обязан переопределять все абстрактные члены своего суперкласса. В отличие от абстрактных классов, обычные классы, расширяющие абстрактные классы, обязаны переопределить все поля, свойства и методы, находящиеся в иерархической цепочке и помеченные ключевым словом abstract , если они не были реализованы предками ранее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
abstract class SuperAbstractClass public abstract field: string; // объявление абстрактного поля > // в абстрактных потомках допускается не переопределять абстрактные члены предков abstract class SubAbstractClass extends SuperAbstractClass <> class SubConcreteClass extends SubAbstractClass // конкретный подкласс обязан переопределять абстрактные члены, если они. public field: string; > // . если они не были переопределены в классах-предках class SubSubConcreteClass extends SubConcreteClass <>
Как было сказано ранее, абстрактным полям может быть задано значение по умолчанию, но в этом случае обратиться к нему могут только абстрактные классы в иерархии наследования.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
abstract class SuperAbstractClass // объявление абстрактного поля со значением по умолчанию public abstract field0: string = 'default value'; public abstract field1: string; public abstract field2: string; > abstract class SubAbstractClass extends SuperAbstractClass // переопределение абстрактного поля и инициализация его // значением абстрактного поля, которому было присвоено // значение по умолчанию в абстрактном предке public field1: string = this.field0; > class SuboncreteClass extends SubAbstractClass // конкретному классу необходимо переопределить два абстрактных поля, // так как в предках был переопределен только один член public field0: string; public field2: string; >
Абстрактные члены в полной мере удовлетворяют всем условиям реализации интерфейса. Другими словами, абстрактный класс, декларирующий реализацию интерфейса, может не реализовывать его члены, а лишь пометить их как абстрактные, тем самым переложить реализацию на своих потомков.
1 2 3 4 5 6 7 8 9 10
interface IInterface field: string; method(): void; > abstract class AbstractSuperClass implements IInterface // абстрактный класс декларирует реализацию интерфейса public abstract field: string; // поле без реализации. public abstract method(): void; // . метод без реализации. Тем не менее ошибки не возникает >
Кроме абстрактных членов, абстрактные классы могут содержать обычные члены, обращение к которым ничем не отличается от членов, объявленных в обычных классах.
Как правило, абстрактные классы реализуют только ту логику, которая не будет ни при каких обстоятельствах противоречить логике своих подклассов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
abstract class AbstractSuperClass abstract name: string = 'AbstractSuperClass'; public toString(): string // реализация общего неабстрактного метода return `[object $this.name>]`; > > class FirstConcreteSubClass extends AbstractSuperClass public name: string = 'T2'; // реализуем абстрактное поле > class SecondConcreteSubClass extends AbstractSuperClass public name: string = 'T2'; // реализуем абстрактное поле > let first: FirstConcreteSubClass = new FirstConcreteSubClass(); let second: SecondConcreteSubClass = new SecondConcreteSubClass(); first.toString(); // [object FirstConcreteSubClass] реализация в абстрактном предке second.toString(); // [object SecondConcreteSubClass] реализация в абстрактном предке
Теория¶
Пришло время разобраться в теории абстрактных классов, а именно ответить на вопросы, которые могут возникнуть при разработке программ.
Интерфейс или абстрактный класс — частый вопрос, ответ на который не всегда очевиден. В действительности, это абсолютно разные конструкции, как с точки зрения реализации, так и идеологии. Интерфейсы предназначены для описания публичного api, которое служит для сопряжения с программой. Кроме того, они не должны, а в TypeScript и не могут реализовывать бизнес-логику той части, которую представляют. Они — идеальные кандидаты для реализации слабой связанности (low coupling). При проектировании программ упор должен делаться именно на интерфейсы.
Абстрактные классы, при необходимости, должны реализовывать интерфейсы в той же степени и для тех же целей, что и обычные классы. Их однозначно нужно использовать в качестве базового типа тогда, когда множество логически связанных классов имеет общую для всех логику, использование которой в чистом виде не имеет смысла. Другими словами, если логика, размещенная в классе, не может или не должна выполняться отдельно от потомков, то необходимо запретить создание экземпляров подобных классов.
К примеру, абстрактный класс Animal , реализующий интерфейс IAnimal с двумя членами: свойством isAlive и методом voice , может и должен реализовать свойство isAlive , так как это свойство имеет заранее известное количество состояний (жив или мертв) и не может отличаться в зависимости от потомка. В то время как метод voice (подать голос) как раз таки будет иметь разную реализацию в зависимости от потомков, ведь коты мяукают, а вороны каркают.
Тем не менее, резонно может возникнуть вопрос, а почему бы не вынести этот функционал в обычный, базовый класс?
Абстрактный класс способен не только подсказать архитектору, что данная сущность является абстрактной для предметной области, то есть не является самостоятельной частью, но также не позволит создать экземпляр класса, работа которого может сломать приложение.
Еще раз то же самое, но другими словами. Поскольку базовый класс будет реализовывать логику, предполагаемую интерфейсами, разбитыми по принципу разделения интерфейсов, с помощью которых и будет происходить сопряжение с остальными частями программы, то существует возможность попадания его экземпляра в места, предполагающие логику, отсутствующую в нем. То есть высокоуровневая логика, присущая только потомкам, может быть сокрыта за менее специфичным интерфейсом, реализуемым самим базовым классом. Чтобы избежать подобных сценариев, допускающих возникновение ошибок во время выполнения, необходимо запретить создание экземпляров подобных классов. (Принцип разделения интерфейсов рассматривается в главе Interface)
Кроме того, абстрактный класс с помощью абстрактных членов не даст разработчику забыть реализовать необходимую логику в потомках.
Но и это ещё не все. Интерфейс IAnimal в реальности будет составным типом. То есть, он будет принадлежать к типу ILiveable , описывающему свойство isAlive и типу IVoiceable , описывающему метод voice . Реализовать подобное с помощью абстрактного класса не получится, так как класс может расширять только один другой класс, в то время как интерфейсы могут расширять множество других интерфейсов, и следовательно, принадлежать ко множеству типов данных одновременно. Как раз это и демонстрирует интерфейс IAnimal , расширяя интерфейсы ILiveable и IVoiceable .
Ещё часто можно встретить вопрос о замене интерфейсов абстрактными классами. Технически, абстрактный класс, состоящий только из абстрактных членов, может исполнять роль, идеологически отведенную интерфейсу. Но об этом лучше забыть, поскольку для описания открытой части объекта предназначен интерфейс.