интерфейсы
Jan. 2nd, 2007 01:42 pmЭто будет интересно только программистам.
Мне пришло в голову пару дней назад, что нет собственно причины, по которой в Джаве должно быть ключевое слово implements. Наверное, это тривиальная мысль, но мне до сих пор не приходила в голову.
Предположим, у нас есть интерфейс Foo и класс Bar. Предположим, в классе есть определения всех методов, которые перечислены в интерфейсе, с правильными аргументами, типами возврата и другими атрибутами. Значит, компилятор может этот факт в любой момент проверить напрямую, сверив определения класса и интерфейса (причем класс может быть в скомпилированной форме). В любом месте, где мы хотим использовать объект класса Bar (или его подкласса), а ожидают интерфейс Foo - например, в списке аргументов какого-то метода - сейчас компилятор проверяет правильность подстановки, основываясь на том, что в определении Bar написано "implements Foo". Но если бы не было написано, он все равно мог бы проверить правильность подстановки, просто напрямую сравнив типы методов класса и интерфейса.
(Я что-то упускаю тут или правильно все описал?)
Если это ключевое слово не нужно, зачем же оно есть? Зачем дизайнеры языка заставляют программиста специально написать, что класс Bar воплощает интерфейс Foo, если это не дает компилятору новой информации? Я бы сказал - по двум причинам; во-первых, из идеологических соображений, чтобы заставить программиста иметь в голове стройную картинку того, кто что воплощает. Во-вторых, чтобы была дополнительная проверка совместимости класса с интерфейсом в момент определения класса, а не только в момент его использования в качестве интерфейса. Без этой проверки, скажем, ошибка в определении класса, делающая его несовместимым с интерфейсом, обнаружилась бы только в момент использования класса там, где требуется интерфейс, а не во время компиляции самого класса с интерфейсом.
Что было бы лучше, если бы не было слова implements? Предположим, у меня есть некий достаточно простой интерфейс с одним-двумя методами, воплощающий некую идею; и класс, который написал не я и контролирую не я (возможно, только в скомпилированном виде), который воплощает эту идею, с ровно теми же именами методов и типами, но его создатели ничего не знали про мой интерфейс. Сейчас, чтобы их состыковать, мне надо делать дополнительный класс посредине, который прячет в себе объект настоящего класса и делегирует ему все, что нужно. Если бы компилятор не опирался на implements, а проверял настоящие типы, мне бы это не нужно было делать. Оно бы просто работало само по себе.
См. также интересную статью JavaGI: Generalized Interfaces for Java, которая натолкнула меня на эту нехитрую мысль. JavaGI - интересная попытка внести в Джаву идею type classes из Haskell. Предположим, у вас есть интерфейс и класс, скорее всего из разных источников (возможно, только в скомпилированном виде), причем класс как бы воплощает все, что нужно интерфейсу, но не под теми же именами, и возможно с некоторыми тривиальными изменениями. Обычное решение, опять-таки - промежуточный класс, Adapter pattern. Вместо этого предлагается новый синтаксис, который совмещает класс и интерфейс вместе, "объясняя" компилятору, как воплотить методы интерфейса в терминах методов класса, так, что после этого компилятор может подставлять объект класса туда, где нужен интерфейс, как обычно. Нет умножения сущностей в виде ненужного по сути дела промежуточного класса; вместе с тем сохраняются все проверки типизации, обычные для Джавы. Довольно любопытная идея.
no subject
Date: 2007-01-02 11:54 am (UTC)no subject
Date: 2007-01-02 11:56 am (UTC)no subject
Date: 2007-01-02 12:02 pm (UTC)no subject
Date: 2007-01-02 12:06 pm (UTC)Самый простой пример - интерфейс, не содержащий методов, как, например,
java.io.Serializable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html)(обратите внимание на его отличие отjava.io.Externalizable (http://java.sun.com/j2se/1.4.2/docs/api/java/io/Externalizable.html)) и механизм сериализации, основанный на явной имплементации этих интерфейсов.Другой пример - имплементация механизмов
instanceof(а также всего, что связано с reflection).no subject
Date: 2007-01-02 12:16 pm (UTC)no subject
Date: 2007-01-02 12:23 pm (UTC)То есть, сделать matching можно, но в большой системе это может оказаться запредельно дорого.
no subject
Date: 2007-01-02 12:37 pm (UTC)no subject
Date: 2007-01-02 12:54 pm (UTC)Для того, чтобы компилятор мог проверить, что класс Bar действительно воплощает интерфейс Foo.
В реальности-то проверка типизации как правило оказывается реалтаймовой: есть у вас какая-то дырка, откуда лезут объекты, вы каждому из них делаете IFoo foo = someObject as IFoo (в шарповом синтаксисе) и работаете с теми, которые на самом деле поддерживают интерфейс Foo.
Соответственно, как разработчик того места, откуда в дырке появляются объекты, вы хотите защититься от случайного (ошибочного) изменения сигнатуры какого-нибудь из типов, в результате которого он перестанет поддерживать интерфейс. С явным указанием implements вы получаете ошибку на этапе компиляции, с гипотетическим неявным - в лучшем случае инвалид каст при исполнении, в худшем - непонятное поведение программы, у которой вдруг начинает что-то куда-то исчезать.
Ещё техническая причина, о которой вам пытались сказать выше: сигнатура метода void Close(); (включая название) может использоваться в дофига разных интерфейсах и есть неиллюзорная вероятность того, что в какой-то момент нужно будет ручками разруливать что, типа, вот это - IStream.Close - имплементация для интерфейса IStream.
Опять-таки возвращаясь к сценарию "лезут объекты" -- нам, фактически, нужно выделять объекты по некоему признаку (допустим, "являются потоками") и потом вызывать у них какие-то методы характерные для потоков, причём само по себе наличие похожих методов вовсе не гарантирует, что мы имеем дело с потоком, гораздо приятней, когда это в явном виде указано.
Ну и последнее, наверное: если б этого не было в синтаксисе, правильным программистам пришлось бы писать "implements IDisposable" в комментариях =)
no subject
Date: 2007-01-02 01:01 pm (UTC)в java 5 для этого ввели пояснялки через @.
no subject
Date: 2007-01-02 01:08 pm (UTC)Да нет, почему? В C# можно вместо этого лепить атрибуты, но в некоторых случаях -- когда речь идёт именно о _поведении_, с намёком на то, что потом в этом интерфейсе могут и методы появиться -- почему нет?
> А instanceof это вещь сугубо противоречащая философии Джавы (как подмножества философии OO)
А это-то почему? "Нажать на все кнопки на форме" - вполне ООП задача, без instanceof (или аналогичного механизма) не решающаяся.
no subject
Date: 2007-01-02 01:40 pm (UTC)no subject
Date: 2007-01-02 01:42 pm (UTC)2. Декларация типов - важный элемент software design, он позволяет программисту объяснить место каждого типа и их взаимоотношение друг с другом. Подробнее см., например, в "Agile software development: Principles, patterns, and
practices" by Robert C. Martin (http://search.barnesandnoble.com/bookSearch/isbnInquiry.asp?r=1&isbn=0135974445)
no subject
Date: 2007-01-02 01:53 pm (UTC)2. Это да, это подпадает под "идеологию".
no subject
Date: 2007-01-02 02:19 pm (UTC)no subject
Date: 2007-01-02 02:20 pm (UTC)no subject
Date: 2007-01-02 02:38 pm (UTC)Какие проблемы?
Ну и весь software design летит в трубу, потому что, например, если я сучайно назвала мой метод compareTo, то мой класс вдруг implements Comaprabale, но я же при этом не озаботилась следовать правилам написания этого метода!
А чем это вам мешает? Никто же не требует от вас использовать объекты этого класса там, где кто-то хочет иметь Comparable. Собственно, сейчас никто бы и не смог этого сделать, если вы не написали implements.
no subject
Date: 2007-01-02 02:41 pm (UTC)Это да. В C++ это важно. В Джаве, если я правильно понимаю - нет.
Там классы "тяжелые", они и во время выполнения тащат за собой много всякой информации, и организованы и так посложнее, чем строгий vtable а-ля C++, где вызов метода - одна инструкция косвенного вызова.
no subject
Date: 2007-01-02 02:44 pm (UTC)no subject
Date: 2007-01-02 02:45 pm (UTC)no subject
Date: 2007-01-02 02:45 pm (UTC)no subject
Date: 2007-01-02 02:47 pm (UTC)@form_objects.each { |obj| obj.push if obj.respond_to? :push }
Не вижу instanceof ни одного.
+1
Date: 2007-01-02 02:48 pm (UTC)no subject
Date: 2007-01-02 02:48 pm (UTC)Постепенно просыпаясь...
Date: 2007-01-02 02:49 pm (UTC)m(Foo f)
m(Bar b).
Теперь представьте, что у вас есть класс с методом name и вызовите метод m с этим объектом в качестве параметра. Если у вас объявлено, который из интерфейсов он implements, то вызовется правильный m, а если не объявлено, то плохо дело.
Подозреваю также, что будут проблемы с bounded generics, но пример пока не приходит в голову.
no subject
Date: 2007-01-02 02:51 pm (UTC)