Термінологія: я буду називати мовну конструкцію interface
як інтерфейс , а інтерфейс типу або об'єкта як поверхню (за відсутності кращого терміна).
Вільне зчеплення може бути досягнуто, якщо об'єкт залежить від абстракції замість конкретного типу.
Правильно.
Це дозволяє зв'язати нещільне з’єднання з двох основних причин: 1 - абстракції рідше змінюються, ніж конкретні типи, що означає, що залежний код менше шансів зламатися. 2 - різні типи бетону можна використовувати під час виконання, оскільки всі вони відповідають абстракції. Нові типи бетону також можуть бути додані пізніше, не потрібно змінювати існуючий залежний код.
Не зовсім коректно. Поточні мови, як правило, не передбачають, що абстракція зміниться (хоча існують певні шаблони дизайну, які можуть впоратися з цим). Відокремлення специфіки від загальних речей - це абстракція. Зазвичай це робиться деяким шаром абстракції . Цей шар може бути змінений на деякі інші особливості, не порушуючи код, який спирається на цю абстракцію - досягається вільне з'єднання. Приклад, який не є OOP: sort
Рутину можна змінити з Quicksort у версії 1 на Tim Sort у версії 2. Код, який залежить лише від сортованого результату (тобто будується на sort
абстракції), відключається від реальної реалізації сортування.
Те, що я назвав поверхнею, є загальною частиною абстракції. Зараз в OOP буває так, що один об'єкт іноді повинен підтримувати кілька абстракцій. Не зовсім оптимальний приклад: 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
і реалізовував інші методи автоматично в рамках цього необхідного методу. Міксини , але, що важливіше, Риси [ 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
.
Багато мов мають деяку підтримку рис, і будь-яка мова, що має "протокол метаоб'єктів (MOP)", може додавати до нього риси. Нещодавнє оновлення Java 8 додало методи за замовчуванням, схожі на ознаки (методи в інтерфейсах можуть мати резервні реалізації, так що необов'язково для класів для реалізації цих методів).
На жаль, ознаки є досить недавнім винаходом (2002), і тому є досить рідкісними для більш широких основних мов.