Абстрактные классы¶
Если у всех начинающих разработчиков при размышлениях об интерфейсах возникают вопросы "когда и зачем их использовать", то при размышлении об абстрактных классах к ним добавляются "чем они отличаются от интерфейсов и когда та или иная конструкция предпочтительней". Ответы на эти вопросы вы найдете в данной главе, но для начала стоит рассмотреть общие характеристики.
Общие характеристики¶
В TypeScript объявление абстрактного класса отличается от объявления обычного только добавлением ключевого слова abstract
перед ключевым словом class
.
1 |
|
Абстрактные классы так же, как и обычные классы, могут расширять другие обычные и абстрактные классы и реализовывать интерфейсы.
1 2 3 4 5 6 7 8 9 10 11 |
|
Несмотря на то, что абстрактный класс — все же класс, главное его отличие от обычного класса заключается в отсутствии возможности создания его экземпляров. Другими словами, нельзя создать экземпляр абстрактного класса.
1 2 3 4 5 6 |
|
Абстрактные классы могут содержать абстрактные члены, принадлежность к которым указывается с помощью ключевого слова abstract
. Ключевое слово abstract
можно применить к полям, свойствам (аксессоры) и методам абстрактного класса. При этом свойства и методы не должны иметь реализацию. В отличие от них, полям, помеченным как абстрактные, может быть присвоено значение по умолчанию.
1 2 3 4 5 6 7 |
|
Абстрактный класс, расширяющий другой абстрактный класс, не обязан переопределять все абстрактные члены своего суперкласса. В отличие от абстрактных классов, обычные классы, расширяющие абстрактные классы, обязаны переопределить все поля, свойства и методы, находящиеся в иерархической цепочке и помеченные ключевым словом abstract
, если они не были реализованы предками ранее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Как было сказано ранее, абстрактным полям может быть задано значение по умолчанию, но в этом случае обратиться к нему могут только абстрактные классы в иерархии наследования.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Абстрактные члены в полной мере удовлетворяют всем условиям реализации интерфейса. Другими словами, абстрактный класс, декларирующий реализацию интерфейса, может не реализовывать его члены, а лишь пометить их как абстрактные, тем самым переложить реализацию на своих потомков.
1 2 3 4 5 6 7 8 9 10 |
|
Кроме абстрактных членов, абстрактные классы могут содержать обычные члены, обращение к которым ничем не отличается от членов, объявленных в обычных классах.
Как правило, абстрактные классы реализуют только ту логику, которая не будет ни при каких обстоятельствах противоречить логике своих подклассов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Теория¶
Пришло время разобраться в теории абстрактных классов, а именно ответить на вопросы, которые могут возникнуть при разработке программ.
Интерфейс или абстрактный класс — частый вопрос, ответ на который не всегда очевиден. В действительности, это абсолютно разные конструкции, как с точки зрения реализации, так и идеологии. Интерфейсы предназначены для описания публичного api, которое служит для сопряжения с программой. Кроме того, они не должны, а в TypeScript и не могут реализовывать бизнес-логику той части, которую представляют. Они — идеальные кандидаты для реализации слабой связанности (low coupling). При проектировании программ упор должен делаться именно на интерфейсы.
Абстрактные классы, при необходимости, должны реализовывать интерфейсы в той же степени и для тех же целей, что и обычные классы. Их однозначно нужно использовать в качестве базового типа тогда, когда множество логически связанных классов имеет общую для всех логику, использование которой в чистом виде не имеет смысла. Другими словами, если логика, размещенная в классе, не может или не должна выполняться отдельно от потомков, то необходимо запретить создание экземпляров подобных классов.
К примеру, абстрактный класс Animal
, реализующий интерфейс IAnimal
с двумя членами: свойством isAlive
и методом voice
, может и должен реализовать свойство isAlive
, так как это свойство имеет заранее известное количество состояний (жив или мертв) и не может отличаться в зависимости от потомка. В то время как метод voice
(подать голос) как раз таки будет иметь разную реализацию в зависимости от потомков, ведь коты мяукают, а вороны каркают.
Тем не менее, резонно может возникнуть вопрос, а почему бы не вынести этот функционал в обычный, базовый класс?
Абстрактный класс способен не только подсказать архитектору, что данная сущность является абстрактной для предметной области, то есть не является самостоятельной частью, но также не позволит создать экземпляр класса, работа которого может сломать приложение.
Еще раз то же самое, но другими словами. Поскольку базовый класс будет реализовывать логику, предполагаемую интерфейсами, разбитыми по принципу разделения интерфейсов, с помощью которых и будет происходить сопряжение с остальными частями программы, то существует возможность попадания его экземпляра в места, предполагающие логику, отсутствующую в нем. То есть высокоуровневая логика, присущая только потомкам, может быть сокрыта за менее специфичным интерфейсом, реализуемым самим базовым классом. Чтобы избежать подобных сценариев, допускающих возникновение ошибок во время выполнения, необходимо запретить создание экземпляров подобных классов. (Принцип разделения интерфейсов рассматривается в главе Interface)
Кроме того, абстрактный класс с помощью абстрактных членов не даст разработчику забыть реализовать необходимую логику в потомках.
Но и это ещё не все. Интерфейс IAnimal
в реальности будет составным типом. То есть, он будет принадлежать к типу ILiveable
, описывающему свойство isAlive
и типу IVoiceable
, описывающему метод voice
. Реализовать подобное с помощью абстрактного класса не получится, так как класс может расширять только один другой класс, в то время как интерфейсы могут расширять множество других интерфейсов, и следовательно, принадлежать ко множеству типов данных одновременно. Как раз это и демонстрирует интерфейс IAnimal
, расширяя интерфейсы ILiveable
и IVoiceable
.
Ещё часто можно встретить вопрос о замене интерфейсов абстрактными классами. Технически, абстрактный класс, состоящий только из абстрактных членов, может исполнять роль, идеологически отведенную интерфейсу. Но об этом лучше забыть, поскольку для описания открытой части объекта предназначен интерфейс.