Терминология: я буду ссылаться на языковую конструкцию interface
как на интерфейс , а на интерфейс типа или объекта как на поверхность (из-за отсутствия лучшего термина).
Слабая связь может быть достигнута, если объект зависит от абстракции, а не от конкретного типа.
Верный.
Это допускает слабую связь по двум основным причинам: 1 - абстракции менее подвержены изменениям, чем конкретные типы, что означает, что зависимый код с меньшей вероятностью будет поврежден. 2 - различные конкретные типы могут быть использованы во время выполнения, потому что все они соответствуют абстракции. Новые конкретные типы также могут быть добавлены позже без необходимости изменять существующий зависимый код.
Не совсем правильно. Текущие языки обычно не ожидают, что абстракция изменится (хотя есть некоторые шаблоны проектирования, чтобы справиться с этим). Отделение специфики от общих вещей - это абстракция. Обычно это делается каким-то слоем абстракции . Этот уровень можно изменить на некоторые другие особенности, не нарушая код, основанный на этой абстракции - достигается слабая связь. Пример, sort
отличный от ООП: подпрограмма может быть изменена с быстрой сортировки в версии 1 на сортировку Тима в версии 2. Таким образом, код, который зависит только от сортируемого результата (т.е. основывается на sort
абстракции), отделен от фактической реализации сортировки.
То, что я назвал поверхностью выше, является общей частью абстракции. Теперь в ООП случается, что один объект иногда должен поддерживать несколько абстракций. Не совсем оптимальный пример: Java java.util.LinkedList
поддерживает как List
интерфейс, который относится к абстракции «упорядоченная, индексируемая коллекция», так и поддерживает Queue
интерфейс, который (в грубом смысле) относится к абстракции «FIFO».
Как объект может поддерживать несколько абстракций?
В C ++ нет интерфейсов, но есть множественное наследование, виртуальные методы и абстрактные классы. Затем абстракция может быть определена как абстрактный класс (то есть класс, который не может быть немедленно создан), который объявляет, но не определяет виртуальные методы. Классы, которые реализуют специфику абстракции, могут затем наследоваться от этого абстрактного класса и реализовывать необходимые виртуальные методы.
Проблема в том, что множественное наследование может привести к проблеме ромба , где порядок, в котором классы ищутся для реализации метода (MRO: порядок разрешения метода), может привести к «противоречиям». Есть два ответа на это:
Определите нормальный порядок и отклоните те заказы, которые не могут быть разумно линеаризованы. C3 MRO достаточно разумный и хорошо работает. Был опубликован в 1996 году.
Пройдите по простому маршруту и отклоните множественное наследование.
Java выбрал последний вариант и выбрал единственное поведенческое наследование. Однако нам все еще нужна способность объекта поддерживать несколько абстракций. Следовательно, должны использоваться интерфейсы, которые не поддерживают определения методов, а только объявления.
В результате MRO очевидно (достаточно взглянуть на каждый суперкласс по порядку), и что наш объект может иметь несколько поверхностей для любого числа абстракций.
Это оказывается довольно неудовлетворительным, потому что довольно часто немного поведения является частью поверхности. Рассмотрим Comparable
интерфейс:
interface Comparable<T> {
public int cmp(T that);
public boolean lt(T that); // less than
public boolean le(T that); // less than or equal
public boolean eq(T that); // equal
public boolean ne(T that); // not equal
public boolean ge(T that); // greater than or equal
public boolean gt(T that); // greater than
}
Это очень удобно (хороший API с множеством удобных методов), но утомительно для реализации. Мы хотели бы, чтобы интерфейс включал cmp
и реализовывал другие методы автоматически в терминах этого одного обязательного метода. Mixins , но, что более важно, Traits [ 1 ], [ 2 ] решают эту проблему, не попадая в ловушки множественного наследования.
Это делается путем определения состава признаков, так что признаки фактически не участвуют в MRO - вместо этого определенные методы объединяются в реализующий класс.
Comparable
Интерфейс может быть выражено в Scala , как
trait Comparable[T] {
def cmp(that: T): Int
def lt(that: T): Boolean = this.cmp(that) < 0
def le(that: T): Boolean = this.cmp(that) <= 0
...
}
Когда класс затем использует эту черту, другие методы добавляются в определение класса:
// "extends" isn't different from Java's "implements" in this case
case class Inty(val x: Int) extends Comparable[Inty] {
override def cmp(that: Inty) = this.x - that.x
// lt etc. get added automatically
}
Так Inty(4) cmp Inty(6)
было бы -2
и Inty(4) lt Inty(6)
будет true
.
Многие языки имеют некоторую поддержку признаков, и любой язык, который имеет «Metaobject Protocol (MOP)», может иметь добавленные признаки. В недавнем обновлении Java 8 добавлены методы по умолчанию, которые похожи на признаки (методы в интерфейсах могут иметь запасные реализации, поэтому для реализации классов необязательно реализовывать эти методы).
К сожалению, черты являются довольно недавним изобретением (2002), и, таким образом, довольно редко встречаются в более крупных основных языках.