05 августа 2008

Интерфейсы, классы и mixin’ы

Я давно замечал, что даже студенты старших курсов с трудом различают наследование класса и реализацию интерфейса. Нет, на синтаксическом уровне, конечно, все понятно. Трудности возникают в момент применения. Ответ на вопрос, что лучше – класс или интерфейс, далеко не всегда очевиден. Причем поводом к применению интерфейсов часто называют множественное наследование, путая причинно-следственные связи. Возможно, это связано с тем, что в качестве базового языка для курса ООП сегодня повсеместно используется C++, хотя, между прочим, Алан Кай в свое время заметил: «Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind».

Итак, на сегодняшний день известны три относительно честных объектно-ориентированных способа повторного использования кода, а именно наследование классов, реализация интерфейсов и mixins. При этом реализация интерфейса является способом повторного использования кода весьма опосредовано. Рассмотрим их поподробнее.

1. Классы – основная категория объектно-ориентированного подхода. Класс, по определению описывает некоторую сущность, ее атрибуты и действия, которые можно над ней выполнять. Уточнить особенности некоторых подвидов данного класса можно путем наследования с последующим дополнением и переопределением его свойств. По сути, класс, если он, конечно же, не абстрактный, содержит всю информацию для того, чтобы можно было создать его экземпляр и однозначно описать его поведение. Существует способ заимствования свойств из нескольких классов, именуемый множественным наследованием (языки C++, python). Применение этого механизма сопряжено с рядом сложностей как лексического, так и онтологического характера. В первом случае проблема возникает при совпадении имен свойств классов-родителей, что приводит к неоднозначности выбора правильного поведения, а во втором – при наследовании от нескольких несовместимых по смыслу сущностей, например, совместное наследование классов кота или собаки с классом автобуса породит «автобак» или «котобусов», что вряд ли будет адекватно описывать предметную область.

2. Интерфейс – объявление определенной функциональности, которую может реализовать данный класс. В данном случае речь идет именно об объявлении, а не об определении в смысле C++. Возможно, разработка данной концепции изначально была призвана оградить от сложностей множественного наследования, но современное понятие «интерфейс» слабо связано с этой проблемой. Главные отличия интерфейса от класса следующие: а) нельзя создавать экземпляры интерфейсов; б) в интерфейсе не описываются переменные, т.е. он не содержит состояния; в) интерфейс не содержит реализации методов, а только указывает на необходимость их реализации в классе. Интерфейс скорее описывает общность поведения различных объектов, а не изменчивость, как это имеет место в случае наследования. Интерфейсы используются в таких языках, как Java и C#, типичные места их применения – реализация итераторов или функций сравнения экземляров классов.

3. Mixin – фрагмент кода, реализующий определенную функциональность. С интерфейсами mixin’ы роднит невозможность создания экземпляров, а с классами – наличие реализации. Такие фрагменты кода могут быть «подмешаны» к классам, предоставляя дополнительные возможности манипуляции с объектом. Mixin’ы могут быть эффективно использованы в языках с динамической типизацией или с автоматическим выводом типов, т.к. иначе они сильно зависят от реализации конкретного класса, к которому они подмешаны. На сегодняшний день mixin’ы присутствуют в языках D, Ruby.

В общем, можно говорить о том, что наследование классов уместно, когда присутствует некоторая изменчивость в рамках одной сущности, интерфейсы и mixin’ы – когда присутствует сходство в поведении нескольких различных сущностей.

Комментариев нет: