Абстрактный класс или интерфейс?
Чем они отличаются между собой и в чём отличия?
В плане синтаксиса, интерфейс может содержать внутри себя только методы без реализации, свойства, события. В то время как абстрактный класс поддерживает функционал любого другого класса(поля, реализованные методы, делегаты, события, свойства, конструкторы. ), но запрещает создавать экземпляры своего типа. Также нужно помнить, что C# не поддерживает множественное наследование и, соответственно, унаследоваться от нескольких классов не получиться, а вот от нескольких интерфейсов — да.
В плане логики, интерфейсы чаще используют, чтобы придать классу некоторую функциональность, по которой его в дальнейшем можно определять. К примеру, foreach можно применять по отношению к типам, в которых реализован интерфейс IEnumerbale.
Вот пару советов по определению нужного механизма:
- Связь потомка с предком. Любой тип может наследовать только одну реализацию. Если производный тип не может ограничиваться отношением типа «является частным случаем» с базовым типом, нужно применять интерфейс, а не базовый тип. Интерфейс подразумевает отношение «поддерживает функциональность». Например, тип может преобразовывать экземпляры самого себя в другой тип (IConvertible), может создать набор экземпляров самого себя (ISerializable) и т. д. Заметьте, что значимые типы должны наследовать от типа System.ValueType и поэтому не могут наследовать от произвольного базового класса. В этом случае нужно определять интерфейс.
- Простота использования. Разработчику проще определить новый тип, производный от базового, чем создать интерфейс. Базовый тип может предоставлять массу функций, и в производном типе потребуется внести лишь незначительные изменения, чтобы изменить его поведение. При создании интерфейса в новом типе придется реализовывать все члены.
- Четкая реализация. Как бы хорошо ни был документирован контракт, вряд ли будет реализован абсолютно корректно. По сути, проблемы COM связаны именно с этим — вот почему некоторые COM-объекты нормально работают только с Microsoft Word или Microsoft Internet Explorer. Базовый тип с хорошей реализацией основных функций — прекрасная отправная точка, вам останется изменить лишь отдельные части.
- Управление версиями. Когда вы добавляете метод к базовому типу, производный тип наследует стандартную реализацию этого метода без всяких затрат. Пользовательский исходный код даже не нужно перекомпилировать. Добавление нового члена к интерфейсу требует изменения пользовательского исходного кода и его перекомпиляции.
Наконец, нужно сказать, что на самом деле можно определить интерфейс и создать базовый класс, который реализует интерфейс. Например, в FCL определен интерфейс IComparer, и любой тип может реализовать этот интерфейс. Кроме того, FCL предоставляет абстрактный базовый класс Comparer, который реализует этот интерфейс (абстрактно) и предлагает реализацию по умолчанию для необобщенного метода Compare интерфейса IComparer. Применение обеих возможностей дает большую гибкость, поскольку разработчики теперь могут выбрать из двух вариантов наиболее предпочтительный.
results matching » «
No results matching » «
Отличия абстрактного класса от интерфейса (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
206k 28 28 золотых знаков 291 291 серебряный знак 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 определяет «дополнительное» поведение, которое говорит о возможности типов сравнивать значения объектов.
Абстрактный класс и интерфейс 😀
Кажется, когда-то давно читал похожее, когда опытные программисты возмущались вопросом на собеседовании: «в чем разница между абстрактным классом и интерфейсом». Тогда был джуном и возмущений не понял — ну знаешь же, ответь. 🙂
Сейчас сам столкнулся с похожим. Вопрос ставит в ступор. На работе занимаешься чем угодно, кроме сравнивания абстрактных классов с интерфейсами 🙁
Формулировка — сравнить эти две вещи, и что от тебя ждут ответ на это, кажется абсурдом и отключает. Может нужно ходить регулярно на собеседования для поддержания формы?
Подобається Сподобалось 1
До обраного В обраному 1
Найкращі коментарі пропустити
Работать и проходить собеседования — два разных занятия, требующих 2х разных, обычно не пересекающихся, наборов скилов.
Может нужно ходить регулярно на собеседования для поддержания формы?
Конечно нужно. Для поддержания себя в форме, выработки скила проходить собеседования и понимания того, что вообще на рынке происходит.

Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
190 коментарів

Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Мои рассуждения и вопрос знающим и понимающим, надеюсь на советы, поправки и подсказки для правильного понимания.
Можно сказать что интерфейс — набор требований, которым должны соответствовать абсолютно
разные объекты, т.к объекты разные, то и реализация требований для каждого объекта может сильно разнится. Плюс, если нет необходимости соответствовать этим требованиям, нету необходимости реализовывать этот интерфейс.
Пример как вижу я: объект Перевозчик может перевезти любой объект, который реализовывает интерфейс IVolumable: кукуруза сама знает как посчитать свой объем, утюг — сам, человек — сам. Объекты абсолютно разные, реализация разная, но для Перевозчика разницы нету — каждый IVolumable сообщил свой объем, и Перевозчик понимает как их перевезти, сколько их влезет, сколько это стоит. Кукуруза захочет что бы ее перевезли, она должна реализовать IVolumable, человек захочет что бы его перевезли, он должен реализовать IVolumable, утюгу никуда не надо, соответственно и имплементить этот интерфейс мы не будем.
Абстрактный класс — описывает (и просит следовать) общее поведение и свойства для группы схожих объектов-наследников. Ключевая фраза — схожих объектов. Между утюгом, человеком и кукурузой ничего схожего нету, также как и маловероятно что и них будет общий абстрактный класс.
Пример как вижу я: общий абстрактный класс AbstractIron, с единственным методом toIron(). Утюги есть разные, по-этому гладить будут по-разному: низкая температура, высокая, отпаривание и тп, но гладить они должны уметь все, соответственно все должны (пускай по-своему) реализовать toIron().
Общий абстрактный класс AbstractCorn, с единственным методом toRipe(). Разные сорта кукурузы будут созревать по-разному, но созревать они будут все.
Т.е. как это понимаю я — с архитектурной точки зрения (не вдаваясь в подробности копмиляции), интерфейс и абстрактный класс сравнивать некорректно, эти два понятия несут разное назначение. Они чем-то похожи: есть абстрактные методы; у них есть различия: наличие методов с имплементацией, возможность инстанциировать (и пр. особенности зависящие от ЯП).
Исходя из моего скудного опыта: интерфейс — более чаще используется в повседневной разработке, абстрактный класс — более чаще используется при написании фреймверков и каких-то библиотечных решений.
Интерфейс позволяет наделить разные объекты схожим/требуемым поведением, абстрактный класс задает «костяк» для группы схожих объектов.
Прошу поправить мои размышления. Ваш фидбек для меня важен. Возможно кому-то из начинающих также чем-то поможет.
Спасибо
Andrii Zaiats software engineer/architect в VITech 10.06.2020 23:22
Мій декан казав: «Нема дурних питань. Є дурні відповіді».
От ніколи такого питання сам не задавав, але вже кілька разів відповідав. І кожного разу відповіді були різними.
Зараз розумію шо питання прекрасне. Воно позволяє зрозуміти який тон співбесіді варто задавати далі і де будуть сильні і слабкі сторони.
Тільки уявіть скільки відповідей можна придумати. Наприклад:
1. В абстрактному класі можна мати реалізацію в методах, а в інтерфейсі — ні.
Шо ми з того можемо отрмати? Якшо ми Java джуна співбесідуємо то стає ясно шо про Java 8 він ше не прочитав.
2. В абстрактному класі можна мати поля і конструктор, а в інтерфейсі ні.
То вже трохи краще, але бачимо шо людина в першу чергу дивиться на нюанси мови, а не на концепцію. Такому можна довірити писати код, але робити дизайн ше, можливо, зарано.
3. Можна імплементити багато інтерфейсів, але розширяти лише один абстрактний клас.
З моєї точки зору то вже перший крок до ДЗЕНу (якшо не заблукає в тенетах технологій і зможе з часом думку цю розвинути). Можливо, при хорошому наставнику, можна виростити діамант.
4. Відповідь з розряду як від Oleksii Pavlov «хто я» і «шо я роблю» явно вказує — про нюанси мови багато говорити не треба. Маємо справу з художником, а не письменником. В таких випадках я розвішую вуха і стараюся сам чогось нового на співбесіді навчитися.
Правильних відповідей може бути багато. І кожна несе важливу інформацію про кандидата.
Абстрактные классы¶
Если у всех начинающих разработчиков при размышлениях об интерфейсах возникают вопросы "когда и зачем их использовать", то при размышлении об абстрактных классах к ним добавляются "чем они отличаются от интерфейсов и когда та или иная конструкция предпочтительней". Ответы на эти вопросы вы найдете в данной главе, но для начала стоит рассмотреть общие характеристики.
Общие характеристики¶
В 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 .
Ещё часто можно встретить вопрос о замене интерфейсов абстрактными классами. Технически, абстрактный класс, состоящий только из абстрактных членов, может исполнять роль, идеологически отведенную интерфейсу. Но об этом лучше забыть, поскольку для описания открытой части объекта предназначен интерфейс.