ptainai, произносится «Peh-tain-eye».
Тихое собрание.
Чтение из Книги Дизайнерских Узоров.
«18: 3. И Джон сел, и он запрограммировал, и он отпустил. И услышали громкий звук огромной радости. Но вот Заказчик был непостоянным и тогда изменил требования, и Джон, удрученный, был вынужден снова программировать. — воскликнул он. — Почему вы отказались от нашей дорожной карты? Почему вы должны очень злобно удалить функцию, указанную только для того, чтобы требовать этого архитектурного фаллоимитатора на его месте?
«Затем воздух наполнился свирепостью и светом, и Заказчик гремел», — говорил я вам о разъединении, но вы доставляли грязь. Я говорил вам об интерфейсе, но вы обеспечивали реализацию. Я говорил вам об изменениях, но вы обеспечивали жесткость наиболее замороженным. Любишь контракт с фиксированной ценой, beeatch? »
Это слово Заказчика.
Спасибо GoF.
Тем временем вернемся в реальность …
На самом деле, раздел 1.6, стр. 18, абзац 3 шаблонов «Банды четырех» гласит:
«Есть два преимущества в манипулировании объектами исключительно с точки зрения интерфейса, определенного абстрактными классами:
«1. Клиенты не знают о конкретных типах объектов, которые они используют, до тех пор, пока эти объекты придерживаются того интерфейса, который ожидают клиенты.
«2. Клиенты не знают о классах, которые реализуют эти объекты. Клиенты знают только об абстрактных классах, определяющих интерфейс.
«Это настолько сильно уменьшает зависимости реализации между подсистемами, что приводит к следующему принципу многократно ориентированного объектно-ориентированного проектирования: программа для интерфейса, а не для реализации».
(Отсюда и название, кстати.)
Здесь мы находим один из самых мощных, наиболее уважаемых принципов управления зависимостями исходного кода Java.
Почему мы заботимся о зависимостях исходного кода?
Одной из причин является волновой эффект: изменение одного программного обеспечения вызывает изменение в другом месте, которое, в свою очередь, вызывает изменение в другом месте и т. Д. Все эти изменения влекут за собой затраты. Плохо структурированные системы затемняют и дополняют зависимости, накапливая волновые эффекты и, следовательно, снижая предсказуемость потенциальных затрат. Хорошо структурированные системы с четкими и ухоженными зависимостями способствуют этой предсказуемости.
Уменьшение количества зависимостей реализации способствует снижению вероятности волновых эффектов. Приведенные выше шаблоны проектирования утверждают, что программирование для интерфейса, а не для реализации уменьшает зависимости реализации, и, следовательно, из этого следует, что ptainai уменьшает вероятность возникновения волновых эффектов.
Но так ли это?
Ваш интерфейс не мой интерфейс.
Это старый каштан: интерфейс является общим термином для множества запросов , на которые предприятие может реагировать, в то время как Java — интерфейс (что выделенные курсивом, так что если вы видите обычный шрифт там , то вы собираетесь быть один запутался щенок) в определен как, «… ссылочный тип, похожий на класс, который может содержать только константы, сигнатуры методов и вложенные типы. Тела методов отсутствуют».
Конечно, и интерфейс, и интерфейс пересекаются, но все же имеют достаточное различие, чтобы можно было перефразировать наш центральный вопрос: уменьшает ли интерфейс вероятность возникновения волновых эффектов больше, чем интерфейс, который не является интерфейсом ?
Более конкретно, давайте скажем, что у нас есть два пакета, p1
и p2
, и p1
имеет некоторые зависимости исходного кода p2
. Является ли p1
менее склонны к пульсации эффектов от p2
если она зависит от множества интерфейса s в , p2
а не в зависимости от вместо набора классов в p2
?
Чтобы понять, как волновой эффект может различать эти два, мы должны изучить изменения, которые могут произойти p2
и могут просочиться p1
. Пять категорий представляют себя.
1. Оптовое дополнение.
Во-первых, могут быть реализованы некоторые совершенно новые функциональные возможности, p2
позволяющие экспортировать новый набор сигнатур методов либо в существующие интерфейсные классы, либо в виде новых интерфейсных интерфейсов .
Ясно, что исходный интерфейс был интерфейсом или классом, не накладывает необходимых ограничений на эту новую функциональность. Чтобы использовать это, клиент должен измениться. Таким образом, p2
экспорт интерфейса, а не класса, не уменьшает вероятность возникновения волнового эффекта в этом случае.
2. Оптовое удаление.
Во-вторых, мы можем удалить некоторые функции из p2
. Здесь, если p1
зависит от удаленной функциональности, будь то интерфейс или класс, то волновой эффект будет генерировать стоимость. В этом случае также не удается провести различие между ними, хотя в нем хорошо показано, почему функциональность всегда должна предоставлять минимальный интерфейс.
3. Изменение подписи.
В-третьих, сигнатура метода в существующей p2
функциональности может измениться. Еще раз, принадлежит ли эта подпись к интерфейсу или классу, не имеет значения: клиент, который зависит от измененной подписи, должен измениться.
4. Смена кузова.
В-четвертых, тело метода в существующей p2
функциональности может измениться. Возможно, здесь интерфейс может достичь победы, учитывая, что интерфейсы , в отличие от классов, не содержат тел методов и, таким образом, интерфейсы защищены от целой категории изменений, которым подвержены классы.
Не так быстро.
Поскольку методы в классах Java являются процедурно абстрактными, доступными только для клиентов через их сигнатуры: клиент не может выбрать точку входа в вызываемый метод и выполнить произвольную операцию в нем, он может только вызвать сигнатуру. Таким образом, если изменение в теле метода не может изменить сигнатуру, то это изменение остается невидимым для клиентских методов.
Конечно, изменение в теле метода может привести к изменению сигнатуры или даже к открытию метода и его распаду на ряд методов, которым клиент должен подвергнуться, но затем преобразует изменение тела в один из другие категории изменений, обсуждаемые выше, ни одна из которых не отличает интерфейс от класса с точки зрения волнового эффекта.
Еще раз: ничья.
5. Замена кузова.
Наконец, и что наиболее интересно, метод, реализующий данную сигнатуру, p2
может поменяться местами с меткой другого, реализующего ту же сигнатуру. Теперь, конечно, интерфейс выигрывает, так как ему не хватает знаний о классе, реализующем сигнатуры его методов, тогда как класс реализации обязательно включает в себя такие знания.
Опять не так быстро. Хотя клиент может иметь доступ к методу в классе, он не может быть уверен, какой класс реализует этот метод: все семейство других классов может быть производным от класса, к которому у клиента есть доступ, и любой из этих подклассов может переопределить метод ,
Если мы определяем «зависимость от реализации», которая подразумевает знание реализующего класса или метода, то здесь не возникает никаких необходимых зависимостей реализации. В действительности, Java делает довольно сложным программирование для реализации, как показывает тривиальный пример.
public void welcome(Person person) { System.out.println("Hello, " + person.getName()); }
In the snippet above, is Person
a class or an interface? And even if Person
is a class, is it the class that implements the getName()
method or is that method overridden and implemented in some other subclass? The client does not know. Java’s prescient designers ensured that the client does not need to know. Invoking getName()
‘s signature in the Person
entity does not guarantee execution of getName()
‘s method in the Person
entity.
The glaring exceptions to all this, of course, are the non-overridable methods (for example, final methods) and the constructor.
If the client depends on any non-overridable method or constructor then the client has exact knowledge of the implementing method: that implementing method cannot swap with another without affecting the client; using an interface carries no such penalty. Here, finally, the interface gathers its velvet cloak, swivels and plonks its royal behind on its cushioned throne.
A slightly unimpressed audience notes, however, that no inherent property of the interface has forged this magical shield capable of repelling ripple-effects better than class’s rusted armour. Rather it is the properties of the class that exist over and above those of interface that cause ripple-effect concerns.
Yet no one has mandated that these properties be expressed in the published interfaces between packages.
Final methods have their place but the very concerns discussed here have seduced many programmers into chosing flexibility instead, rendering final methods something of a niche market. Constructors, for their part, often find themselves farmed out to factories and system-external instantiation-engines, again precisely to avoid their appearing in published interfaces. Plain-ol’ overridable methods overwhelmingly represent the means by which most functionality is invoked, so class-inheritance could — in theory — play precisely the role that interface-inheritance plays in the ripple-effect game.
Yet in practice it does not.
The interface‘s superiority stems, rather, from the crushing inheritance-restrictions that afflict the class.
A class may derive from — and inherit the methods of — only one superclass. Introduced to curb the chaos that was perceived to dog multiple inheritance, this straight-jacket has proved such a snug fit as to have practically eradicated implementation-inheritance from modern Java systems, most programmers instead favouring composition. interface-inheritance reigns because Java’s designers hobbled class-inheritance (even if for all the right reasons). Yet it is inheritance — shared by interface and class alike and certainly no peculiarity of the interface — that offers the means of by-passing this one category of ripple-effect. Here, finally, lies the source of the interface‘s resistance to ripple-effect.
Pragmatists celebrate.
Programmers program.
Purists worry.
They worry that some misunderstand the nature of the interface, ascribing to it mythical powers of ripple-effect immunity that vastly outstretch its reach. interfaces prove themselves over classes in one category of change only and this a relatively minor one. Most interface-change explodes in the form of first- and second-category events with entire classes and methods popping into being and vanishing, or, worse still, shattering asunder leaving ragged clients to comb the rubble for lost functionality.
A skewed view of interface‘s advantages causes some programmers to focus on interfaces rather than on interface-focus, to consider an interface acceptable simply because it comprises only interfaces rather than because it offers a fanatically minimised, «Surface area,» to its clients.
interface‘s remain one of the great weapons in the Java-programmer’s arsenal and Design Patterns one of the great manuals of war. Misinterpretations of that Good Book — written in a pre-Java vocabulary — should not, however, lure programmer’s into language-specific thinking when generality was its essence.
Summary.
Should you program to an interface, not an implementation? Absolutely.
Should you program to an interface, not an implementation? Absolutely.
Does doing the latter equate with doing the former? Absolutely not.
ptainai ain’t ptainai.
Photo credit attribution.
CC image Part of glass painting from around 1610 A.D. courtesy of arnybo on Flickr.
CC image Magic No Mystery 1 courtesy of plaisanter~ on Flickr.