Есть ли смысл для интерфейсов в динамических языках?
В статических языках, таких как Java, вам нужны интерфейсы, потому что иначе система типов просто не позволит вам делать определенные вещи. Но в динамических языках, таких как PHP и Python, вы просто пользуетесь утиной типизацией.
PHP поддерживает интерфейсы. У Руби и Питона их нет. Таким образом, вы можете явно жить счастливо без них.
Я в основном делал свою работу на PHP и никогда не использовал возможность определять интерфейсы. Когда мне нужен набор классов для реализации определенного общего интерфейса, я просто описываю это в документации.
Так что ты думаешь? Разве вам не лучше вообще не использовать интерфейсы в динамических языках?
17 ответов
Я думаю об этом больше как уровень удобства. Если у вас есть функция, которая принимает "подобный файлу" объект и вызывает только метод read(), то неудобно - даже ограничивать - заставлять пользователя реализовывать некоторый интерфейс File. Так же просто проверить, есть ли у объекта метод чтения.
Но если ваша функция ожидает большой набор методов, проще проверить, поддерживает ли объект интерфейс, чем проверить поддержку каждого отдельного метода.
Да, есть смысл
Если вы явно не используете интерфейсы, ваш код все еще использует объект, как если бы он реализовал определенные методы, просто неясно, что такое невысказанный интерфейс.
Если вы определили функцию для принятия интерфейса (скажем, в PHP), то она потерпит неудачу раньше, и проблема будет с вызывающей стороной, а не с методом, выполняющим работу. Как правило, неудача раньше - хорошее правило для подражания.
Интерфейсы фактически добавляют некоторую степень динамической гибкости, подобной языку, статическим языкам, в которых они есть, например Java. Они предлагают способ запроса объекта, контракты которого он реализует во время выполнения.
Эта концепция хорошо вписывается в динамические языки. Конечно, в зависимости от вашего определения слова "динамический", оно даже включает Objective-C, который довольно широко использует протоколы в Какао.
В Ruby вы можете спросить, отвечает ли объект заданному имени метода. Но это довольно слабая гарантия того, что он будет делать то, что вы хотите, особенно с учетом того, как мало слов используется снова и снова, полная подпись метода не учитывается и т. Д.
В Ruby я могу спросить
object.respond_to? :sync
Так что, да, у него есть метод с именем "синхронизация", что бы это ни значило.
В Objective-C я мог бы спросить что-то похожее, то есть "выглядит ли это / ходьба / крякает как нечто, что синхронизируется?":
[myObject respondsToSelector:@selector(sync)]
Еще лучше, ценой некоторого многословия, я могу спросить кое-что более определенное, то есть "это выглядит / походка / шарлатанство как что-то, что синхронизируется с MobileMe?":
[myObject respondsToSelector:@selector(sync:withMobileMeAccount:)]
Это утка, печатающая до уровня вида.
Но действительно спросить объект, обещает ли он реализовать синхронизацию с MobileMe...
[receiver conformsToProtocol:@protocol(MobileMeSynchronization)]
Конечно, вы могли бы реализовать протоколы, просто проверив наличие ряда селекторов, которые вы считаете определением протокола / утки, и достаточно ли они конкретны. В какой момент протокол является просто сокращением для большого количества уродливых responseds_to? запросы и некоторые очень полезные синтаксические сахара для использования компилятором /IDE.
Интерфейсы / протоколы - это еще одно измерение метаданных объекта, которое можно использовать для реализации динамического поведения при обработке этих объектов. В Java компилятор просто требует такого рода вещей для обычного вызова метода. Но даже динамические языки, такие как Ruby, Python, Perl и т. Д., Реализуют понятие типа, которое выходит за рамки просто "на какие методы реагирует объект". Отсюда и ключевое слово класса. Javascript - единственный действительно широко используемый язык без этой концепции. Если у вас есть классы, то интерфейсы тоже имеют смысл.
По общему признанию, это более полезно для более сложных библиотек или иерархий классов, чем в большинстве приложений, но я думаю, что концепция полезна на любом языке.
Кроме того, кто-то еще упомянул миксин. Рубиновые миксины - это способ обмена кодом - например, они относятся к реализации класса. Интерфейсы / протоколы о интерфейсе класса или объекта. Они действительно могут дополнять друг друга. У вас может быть интерфейс, который определяет поведение, и один или несколько миксинов, которые помогают объекту реализовать это поведение.
Конечно, я не могу думать о каких-либо языках, которые действительно имеют оба отличных языковых свойства. В тех, у кого есть mixin, в том числе mixin обычно подразумевает интерфейс, который он реализует.
Если у вас нет жестких ограничений безопасности (поэтому никто не получит доступ к вашим данным так, как вы этого не хотите), и у вас есть хорошая документация или хорошо обученные программисты (поэтому им не нужен интерпретатор / компилятор, чтобы сказать им, что делай), то нет, это бесполезно.
Для большинства проектов среднего размера все, что вам нужно, - это утка.
У меня сложилось впечатление, что у Python нет интерфейсов. Насколько я знаю в Python, вы не можете заставить метод быть реализованным во время компиляции именно потому, что это динамический язык.
Существуют интерфейсные библиотеки для Python, но я не использовал ни одну из них.
В Python также есть Mixins, так что вы можете создать класс Interface, определив Mixin как имеющий pass
для каждой реализации метода, но это не дает вам особой ценности.
Я думаю, что использование интерфейсов больше зависит от того, сколько людей будет использовать вашу библиотеку. Если это только вы или небольшая команда, тогда документация и соглашение будут хорошими, а наличие интерфейсов будет препятствием. Если это публичная библиотека, то интерфейсы гораздо полезнее, потому что они заставляют людей предоставлять правильные методы, а не просто подсказки. Таким образом, интерфейсы, безусловно, являются ценной функцией для написания публичных библиотек, и я полагаю, что отсутствие (или, по крайней мере, де-акцент) является одной из многих причин, по которым динамические языки используются чаще для приложений, а языки со строгим типом - для больших библиотек.
В таком языке, как PHP, где несуществующий вызов метода приводит к фатальной ошибке и выводит из строя все приложение, тогда да, интерфейсы имеют смысл.
В таком языке, как Python, где вы можете перехватывать и обрабатывать недопустимые вызовы методов, это не так.
Python 3000 будет иметь абстрактные базовые классы. Стоит прочитать.
Рене, пожалуйста, прочитайте мой ответ на вопрос "Лучшие практики для проектирования больших систем на динамическом языке" здесь, на Stackru. Я обсуждаю некоторые преимущества предоставления свободы динамических языков для экономии усилий при разработке и облегчения введения новых программистов в проект. Интерфейсы при правильном использовании значительно способствуют написанию надежного программного обеспечения.
Одно из применений "интерфейса" Java - разрешить строго типизированные миксины в Java. Вы смешиваете надлежащий суперкласс плюс любые дополнительные методы, реализованные для поддержки интерфейса.
У Python есть множественное наследование, поэтому ему не нужно изобретать интерфейс, чтобы разрешить методы из нескольких суперклассов.
Мне, однако, нравятся некоторые преимущества строгой типизации - прежде всего, я фанат раннего обнаружения ошибок. Я пытаюсь использовать "интерфейсное" абстрактное определение суперкласса.
class InterfaceLikeThing( object ):
def __init__( self, arg ):
self.attr= None
self.otherAttr= arg
def aMethod( self ):
raise NotImplementedError
def anotherMethod( self ):
return NotImplemented
Это формализует интерфейс - в некотором смысле. Это не дает абсолютных доказательств того, что подкласс соответствует ожиданиям. Однако, если подкласс не может реализовать требуемый метод, мои модульные тесты не пройдут с очевидным NotImplemented
возвращаемое значение или NotImplementedError
исключение.
В дополнение к другим ответам я просто хочу указать, что в Javascript есть ключевое слово instanceof, которое будет возвращать true, если данный экземпляр находится где-нибудь в цепочке прототипов данного объекта.
Это означает, что если вы используете свой "интерфейсный объект" в цепочке прототипов для своих "объектов реализации" (оба являются просто объектами для JS), то вы можете использовать instanceof, чтобы определить, реализует ли он его. Это не помогает аспекту правоприменения, но помогает в аспекте полиморфизма, который является одним из распространенных применений для интерфейсов.
Как я вижу программист PHP, интерфейс в основном используется в качестве контракта. Это позволяет вам сказать, что все, что использует этот интерфейс, ДОЛЖНО реализовывать данный набор функций.
Я не знаю, если это все, что полезно, но я нашел это немного камнем преткновения, пытаясь понять, что такое интерфейсы.
Ну, конечно, было бы проще проверить, поддерживает ли данный объект весь интерфейс, а не просто не вылетать при вызове одного или двух методов, которые вы используете в начальном методе, например, для добавления объекта во внутренний список.
Утиная типизация обладает некоторыми преимуществами интерфейсов, то есть проста в использовании везде, но механизм обнаружения все еще отсутствует.
Это все равно что сказать, что вам не нужны явные типы в динамически типизированном языке. Почему бы вам не сделать все "вар" и не документировать их типы в другом месте?
Это ограничение, налагаемое на программиста программистом. Вам становится труднее выстрелить себе в ногу; дает вам меньше места для ошибок.
Ну, во-первых, правильно, что в Ruby нет интерфейса как есть, но есть миксин, который каким-то образом берет лучшее из обоих интерфейсов и абстрактных классов из других языков.
Основная цель интерфейса - убедиться, что ваш объект ДОЛЖЕН реализовывать ВСЕ методы, присутствующие в самом интерфейсе.
Конечно, интерфейс никогда не бывает обязательным, даже в Java вы можете представить, что работаете только с классами и используете рефлексию для вызова методов, когда вы не знаете, с каким объектом вы манипулируете, но он подвержен ошибкам и его не следует использовать в много способов.
Если вы чувствуете, что должны, вы можете реализовать своего рода интерфейс с функцией, которая сравнивает методы / атрибуты объекта с заданной сигнатурой. Вот очень простой пример:
file_interface = ('read', 'readline', 'seek')
class InterfaceException(Exception): pass
def implements_interface(obj, interface):
d = dir(obj)
for item in interface:
if item not in d: raise InterfaceException("%s not implemented." % item)
return True
>>> import StringIO
>>> s = StringIO.StringIO()
>>> implements_interface(s, file_interface)
True
>>>
>>> fp = open('/tmp/123456.temp', 'a')
>>> implements_interface(fp, file_interface)
True
>>> fp.close()
>>>
>>> d = {}
>>> implements_interface(d, file_interface)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in implements_interface
__main__.InterfaceException: read not implemented.
Конечно, это не гарантирует очень много.