интерфейсы
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)
From:(no subject)
From: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)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2007-01-02 12:23 pm (UTC)То есть, сделать matching можно, но в большой системе это может оказаться запредельно дорого.
no subject
Date: 2007-01-02 12:37 pm (UTC)(no subject)
From:+1
Date: 2007-01-02 02:48 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: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)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: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)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:Постепенно просыпаясь...
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:55 pm (UTC)Да, здесь будет ошибка во время компиляции, требующая разрешить конфликт. Но чем это принципиально отличается от ситуации, когда сейчас у вас есть класс, который заявляет, что он implements и Foo, и Bar?
Место для конфликтов есть всегда :)
(no subject)
From:(no subject)
From:cинглтон с поддержкой разрушающего копирования :)
From:Re: cинглтон с поддержкой разрушающего копирования :)
From:no subject
Date: 2007-01-02 03:13 pm (UTC)То есть можно будет получить из дырки объект и повесить на него любой подходящий интерфейс при выполнении программы.
no subject
Date: 2007-01-04 06:59 am (UTC)вот что там есть релевантное данному посту, так это type inference of lambda expressions.
no subject
Date: 2007-01-02 03:24 pm (UTC)Есть у меня какой-то типа интерфейс, который я вдруг решил изменить.
Так я его меняю и компилятор мне тут же выкидывает список классов, которые его теперь не поддерживают, хотя должны. А без явного указания пойдут совершенно другие ошибки и не все.
no subject
Date: 2007-01-02 03:27 pm (UTC)no subject
Date: 2007-01-02 03:30 pm (UTC)Я бы как раз сказал, что дело в том, что идентичность класса/интерфейса основана на имени, а не на структуре. Т.е. если я напишу два класса с совершенно идентичными методами, но назову их по-разному, это два разных класса, и там, где хотят один из них, второй не пройдет.
(no subject)
From:no subject
Date: 2007-01-02 05:26 pm (UTC)IMHO - для такого языка как Java слишком либерально. Интерфейс - это не только типы методов, но и семантика их работы. Когда человек пишет implements - он понимает что он делает и может за это отвечать. Когда кто-то меняет реализацию - он смотрит на список implements и знает из него, в каких пределах он это может делать.
Кстати, вероятность случайного совпадения типов методом кажется мне небольшой. Уж не делается ли у Вас интерфейс из существующего класса?
no subject
Date: 2007-01-02 05:48 pm (UTC)А то, что Вы предлагаете - это ИМХО половинчатое решение. В комментах было уже несколько раз сказано, что это и необходимость думать при написании, и рефакторинг, и отлов ошибок/опечаток, и бОльшая ясность при чтении, кстати (вот читая чужой код, как узнать что какой интерфейс реализует? по комментариям разве что, да их не все пишут :-( ), и многое другое.
Ну и плюс, это сложнее для компилятора. Так что, ИМХО, implements в Java быть. А для динамической типизации есть соответствующие языки. Все-таки compile-time errors - это лучше, чем ночь мучительного дебага.
no subject
Date: 2007-01-02 06:59 pm (UTC)Прошу прощения за занудство, но динамическая типизация - это отнюдь не то же самое, что отсутствие типизации. Собственно, единственный известный мне язык, в котором отсутствует типизация - это ассемблер (да и то это не совсем так, правильнее сказать, что там все данные одного типа - байты).
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2007-01-02 05:51 pm (UTC)сравнения сигнатур классов в момент исполенния программы что ударит по производительности.
Я часто использую так называемые tagging interfaces, которые не требуют реализации никаких методов, но позволяют классифицировать объекты по принадлежности к ним. Это удобно. Конечно это можно было бы сделать иначе, и interface тут чиста синтаксический сахар, но
это удобно.
no subject
Date: 2007-01-02 10:28 pm (UTC)думаю начать использовать эту фразу в качестве варианта "держите меня семеро".
из уважения к правам человека.
no subject
Date: 2007-01-02 10:35 pm (UTC)(no subject)
From:offtopic
Date: 2007-01-03 01:28 am (UTC)В нём, кажется, исправлено практически всё, что мне мешало в C, и введено много из других полезных языков, тем не менее, получается быстроходный native code.
Re: offtopic
Date: 2007-01-03 06:57 am (UTC)http://shootout.alioth.debian.org/debian/benchmark.php?test=all?=dlang
стоит присмотреться.
no subject
Date: 2007-01-03 04:47 am (UTC)Делигировать ручками ничего не надо. Всё делегируется автоматически (кроме конструктора).
class CuteWidget extends Widget implements Cute
{
}
no subject
Date: 2007-01-03 11:26 pm (UTC)no subject
Date: 2007-01-03 11:26 pm (UTC)no subject
Date: 2007-01-04 06:13 am (UTC)даже удивительно, что такой гениальный программист авва не понимает таких простых вещей.
no subject
Date: 2007-01-04 11:20 am (UTC)Устал повторять, что те же проверки можно сделать во время компиляции.
например, существуют пустые интерфейсы. какой смысл в пустом интерфейсе, а?
Действительно, какой? Это хак, происходящий от отсутствия в языке traits, type classes, или прочих способов приписать какой-то custom атрибут типу. Интерфейс должен быть контрактом. Пустой контракт - не контракт, или в данном случае "контракт по имени". Контракт по имени - хак. Я умею делать X, ты умеешь делать X, давай дружить, что с того, что это ничего не значит.
даже удивительно, что такой гениальный программист авва не понимает таких простых вещей.
Следите за языком, пожалуйста.
(no subject)
From:(no subject)
From:(no subject)
From: